Free cloud gaming changed shape this week. On October 21, 2026, Xbox started letting Insiders test cloud gaming with ads and no Game Pass subscription, plus prepaid Cloud Playtime packs from $3.99 for 10 hours. NVIDIA’s pages still describe a free GeForce NOW entry tier, and Amazon’s listings say Prime members get a rotating Luna selection at no extra cost. That makes three ways to play for $0, and none of them fixes a bad connection.
The bad connection is where most free-tier experiments die. You sign up, sit in a queue, sit through an ad, and then the stream stutters because your Wi-Fi jitters, not because the service is weak. This tutorial builds a small Python tool called cloudlag that measures latency, jitter, packet loss and bufferbloat on your own line, then logs the queue and ad time of every free session. If you only want the account steps for the new test, our Xbox free-with-ads setup walkthrough covers them. Here we measure first and choose second.
You will finish with a 240-line, standard-library-only script, four commands that log to CSV files, and a clear answer on which free tier your network can actually carry. Plan on 13 steps and about 40 minutes. The tool is yours to keep, and re-running it takes under two minutes.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What Is Actually Free in Cloud Gaming on October 23, 2026
Start with the facts, because the free options are moving fast and some of them are not yet public.
Xbox is the newest entrant and the least settled, and tech-ish covered the same test on October 23. Xbox’s own announcement and several outlets agree that the free ad-supported option is a test for Xbox Insiders without Game Pass, not a general launch. Availability, purchasing options, eligible games and features vary by market, device, account and preview ring. Xbox says a wider rollout of the paid packs is planned, but the details are not confirmed yet, and that is a stated plan, not a shipped product.
The paid side gives you a price yardstick for what “free” is worth. Packs are described as ad-free, with shorter waits and higher-quality streaming, and the hours stay available until you use them. Gamereactor’s report adds the other half of the story: monthly hour caps arrive for Game Pass cloud streaming in November.
| Xbox route (Oct. 23, 2026) | Price | Cloud time | Ads | Status |
|---|---|---|---|---|
| Free cloud gaming test | $0 | Not published | Yes | Insider test only |
| Cloud Playtime, 10 hours | $3.99 | 10 hours | No | Insider preview |
| Cloud Playtime, 25 hours | $7.99 | 25 hours | No | Insider preview |
| Cloud Playtime, 50 hours | $11.99 | 50 hours | No | Insider preview |
| Game Pass Essential | Plan price applies | 5 hours per month from November | No | Announced limit |
| Game Pass Premium | $14.99 per month | 10 hours per month from November | No | Announced limit |
| Highest Game Pass tier | $22.99 per month | 15 hours per month from November | No | Announced limit |
Do the division and the packs land at roughly $0.40, $0.32 and $0.24 per hour, from smallest to largest. PC Game Pass, listed at $13.99 per month, does not include cloud gaming according to Xbox’s listing.
On the other side of the market, NVIDIA’s pages describe GeForce NOW as an active service with a free entry tier next to paid memberships and Day Passes. The company’s blog shows Call of Duty: Modern Warfare 4 arriving on GeForce NOW at launch on October 22, the kind of night free-tier queues tend to grow. We cover that service’s free account flow in our GeForce NOW free tier with Steam guide. Amazon sits in the middle. A product listing dated October 16 says Prime members can play Fortnite and a rotating monthly selection through Luna with no additional cost, and Amazon Game Studios published a “One Year of Amazon Luna” post on October 23. Our Amazon Luna free tier setup shows the account side.
Notice what is missing from every one of those descriptions. No service publishes how long a free session lasts today, how long the queue runs, or how many ads you will watch. Those numbers change weekly, so this tutorial treats them as data you collect rather than facts you read.
Why Measure Before You Pick a Free Cloud Gaming Tier
Cloud gaming works by sending your button presses to a remote machine and streaming the video back. Every millisecond between you and that machine lands on top of the game’s own response time. A steady 30 ms feels fine. A connection that averages 30 ms but spikes to 150 ms every few seconds feels broken.
Four numbers describe that behavior. Median latency is the typical round trip. The 95th-percentile latency (p95) shows how bad the spikes get. Jitter measures how much consecutive samples differ from each other. Loss counts the probes that never came back. A fifth number, bufferbloat, is the extra latency that appears when something else on your network is using the line.
Vendors publish very little here. NVIDIA’s GeForce NOW system requirements page says it recommends a hardwired Ethernet connection or a router with a 5 GHz Wi-Fi connection, and then adds a hard rule: “We also require less than 80ms of network latency from an NVIDIA data center.” That is the only vendor-published latency number we could verify for the three services. In the sources we checked on October 23, Xbox publishes no minimum bandwidth, no maximum latency and no pass/fail benchmark for its cloud service, and Amazon’s pages gave none either.
So cloudlag works with one hard limit and four house rules. Treat the house rules as starting points and tighten them if you play competitive shooters.
| Metric | Limit in cloudlag | Origin | What it protects |
|---|---|---|---|
| Median round trip | 40 ms | House rule | Everyday responsiveness |
| p95 round trip | 80 ms | Matches NVIDIA’s published ceiling | Spikes during fights |
| Jitter | 8 ms | House rule | Even frame pacing |
| Packet loss | Not listed | House rule | Visual artifacts, stalls |
| Bufferbloat | 30 ms added under load | House rule | Play while family streams |
One limit deserves honesty up front. cloudlag times a TCP handshake to a well-connected front-door host, such as a CDN edge near you. That tells you whether your home network and ISP are healthy. It does not measure the path to a specific game data center. NVIDIA’s in-app network test remains the authority for GeForce NOW, and Step 12 shows where to use it.
Prerequisites and Versions
You need very little. The whole tool runs on the Python standard library, so there is nothing to install with pip and nothing to break when a package updates.
- Python 3.10 or newer. We built and tested the code on Python 3.12.3.
- Windows 11, macOS or a current Ubuntu release. Any terminal works.
- A wired connection and a Wi-Fi connection on the same machine, so you can compare them.
- About 40 minutes, with two of the later steps needing an evening session.
- Optional accounts for the services you want to try: an Xbox account (plus Xbox Insider enrollment for the free test), an NVIDIA account, or an Amazon Prime membership.
The test machine also has to be able to run the services at all. NVIDIA’s requirements page lists a device floor for GeForce NOW, and these lines are copied from the page as we read it on October 23, 2026. Check the live page before you rely on them.
| Device | GeForce NOW minimum listed by NVIDIA |
|---|---|
| PC processor | Dual-core x86 or x64 CPU at 2.0 GHz or faster |
| Mac | Any Mac introduced in 2009 or later |
| Safari on macOS | Safari 16.4 or later |
| Chromebook | At least 4GB RAM |
| Android phone or tablet | 1GB of available memory |
| iPhone or iPad | OS version 15.4 or later |
Old laptops and TVs often clear that floor but fail on Wi-Fi, which is why our free cloud gaming on a Chromebook, smart TV or old PC guide pairs well with the numbers you will collect here.
Build the Tool in Six Steps
You will write one file, in six pieces. Paste each block below the previous one, in order. The finished file has the same contents as the listing in the final section.
Step 1: Create the Project Folder and Check Python (2 min)
Make a folder, confirm your Python version, and create the empty file.
mkdir cloudlag
cd cloudlag
python3 --version
touch cloudlag.py
On Windows, use py --version and New-Item cloudlag.py in PowerShell. A healthy result looks like this.
Python 3.12.3
Anything from 3.10 up matches what we tested. Older releases may run the script, but we did not try them, so upgrade if you see 3.9 or lower.
Step 2: Write the Latency Probe Core (5 min)
The first piece resolves a hostname once, then times TCP connections to the resulting address. Resolving once matters. If you resolve on every probe, DNS lookups pollute the timings and you end up measuring your resolver instead of your line.
#!/usr/bin/env python3
"""cloudlag: check whether a connection is fit for free cloud gaming tiers."""
import argparse
import csv
import socket
import statistics
import threading
import time
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
DATA_DIR = Path.home() / ".cloudlag"
PROBES_CSV = DATA_DIR / "probes.csv"
SESSIONS_CSV = DATA_DIR / "sessions.csv"
DEFAULT_HOSTS = ["www.nvidia.com", "www.xbox.com", "www.amazon.com", "1.1.1.1"]
LOAD_URL = "https://speed.cloudflare.com/__down?bytes=50000000"
# NVIDIA publishes one hard number: under 80 ms to its data center.
# Everything else below is our own house rule, so tune it.
GFN_MAX_MS = 80.0
LIMITS = {"median_ms": 40.0, "p95_ms": 80.0, "jitter_ms": 8.0,
"loss_pct": 1.0, "bloat_ms": 30.0}
def resolve(host, port):
return socket.getaddrinfo(host, port, type=socket.SOCK_STREAM)[0][4][0]
def tcp_rtt(ip, port, timeout):
start = time.perf_counter()
try:
with socket.create_connection((ip, port), timeout=timeout):
return (time.perf_counter() - start) * 1000.0
except OSError:
return None
def sample(ip, port, count, interval, timeout):
results = []
for _ in range(count):
results.append(tcp_rtt(ip, port, timeout))
time.sleep(interval)
return results
def summarize(samples):
ok = [s for s in samples if s is not None]
stats = {"sent": len(samples),
"loss_pct": 100.0 * (len(samples) - len(ok)) / max(len(samples), 1)}
if not ok:
return {**stats, "min_ms": None, "median_ms": None, "p95_ms": None,
"max_ms": None, "jitter_ms": None}
ordered = sorted(ok)
p95 = ordered[min(len(ordered) - 1, round(0.95 * (len(ordered) - 1)))]
diffs = [abs(a - b) for a, b in zip(ok, ok[1:])]
return {**stats, "min_ms": ordered[0], "median_ms": statistics.median(ok),
"p95_ms": p95, "max_ms": ordered[-1],
"jitter_ms": statistics.mean(diffs) if diffs else 0.0}
A few choices deserve a sentence each. The tcp_rtt function uses a TCP handshake because it works the same on Windows, macOS and Linux without admin rights, whereas ICMP ping needs different parsing on every platform. The summarize function returns None values when every probe fails, so a dead host reports 100 percent loss instead of crashing. And the LIMITS dictionary sits at the top so you can tune it without hunting through functions.
Step 3: Add the Loaded-Latency Test (4 min)
A quiet line often looks perfect. The problem shows up when a download starts. This piece runs two background download streams while it probes, then reports how much the latency rose and how much throughput the line delivered.
def saturate(url, seconds, stop, counter):
deadline = time.monotonic() + seconds
while time.monotonic() < deadline and not stop.is_set():
try:
with urllib.request.urlopen(url, timeout=5) as resp:
while time.monotonic() < deadline and not stop.is_set():
chunk = resp.read(65536)
if not chunk:
break
counter["bytes"] += len(chunk)
except OSError:
time.sleep(0.5)
def loaded_sample(ip, port, count, interval, timeout, url, streams=2):
stop = threading.Event()
counter = {"bytes": 0}
seconds = count * interval + 2
workers = [threading.Thread(target=saturate, args=(url, seconds, stop, counter),
daemon=True) for _ in range(streams)]
started = time.monotonic()
for w in workers:
w.start()
time.sleep(1.0) # let the download ramp up before probing
results = sample(ip, port, count, interval, timeout)
stop.set()
elapsed = max(time.monotonic() - started, 0.001)
return results, counter["bytes"] * 8 / 1e6 / elapsed
The one-second sleep lets the downloads ramp up before the first probe. Without it, the first few samples look clean and pull the median down.
Step 4: Add the Grading Rules (3 min)
Next comes a function that compares a result to your limits and a tiny formatter. Keep it separate from the probe code, so you can grade old CSV rows later without re-running anything.
def verdict(row):
checks = []
for key, limit in LIMITS.items():
value = row.get(key)
if value is None:
continue
checks.append((key, value, limit, value <= limit))
return checks
def fmt(value, digits=1):
return "-" if value is None else f"{value:.{digits}f}"
Step 5: Add CSV Storage and the Probe Command (4 min)
This piece appends every run to ~/.cloudlag/probes.csv and wires the probe, the loaded test and the NVIDIA 80 ms check into one command.
def append_row(path, fields, row):
DATA_DIR.mkdir(parents=True, exist_ok=True)
is_new = not path.exists()
with path.open("a", newline="") as fh:
writer = csv.DictWriter(fh, fieldnames=fields)
if is_new:
writer.writeheader()
writer.writerow(row)
PROBE_FIELDS = ["ts", "label", "host", "sent", "loss_pct", "min_ms", "median_ms",
"p95_ms", "max_ms", "jitter_ms", "loaded_median_ms", "bloat_ms",
"down_mbps"]
def cmd_probe(args):
for host in args.host or DEFAULT_HOSTS:
try:
ip = resolve(host, args.port)
except OSError as exc:
print(f"{host}: DNS failed ({exc})")
continue
idle = summarize(sample(ip, args.port, args.count, args.interval, args.timeout))
row = {"ts": datetime.now(timezone.utc).isoformat(timespec="seconds"),
"label": args.label, "host": host, **idle,
"loaded_median_ms": None, "bloat_ms": None, "down_mbps": None}
if not args.no_load and idle["median_ms"] is not None:
loaded, mbps = loaded_sample(ip, args.port, args.count, args.interval,
args.timeout, args.load_url)
busy = summarize(loaded)
row["loaded_median_ms"] = busy["median_ms"]
row["down_mbps"] = mbps
if busy["median_ms"] is not None:
row["bloat_ms"] = max(busy["median_ms"] - idle["median_ms"], 0.0)
append_row(PROBES_CSV, PROBE_FIELDS, row)
print(f"\n{host} [{args.label}] ({ip})")
for key, value, limit, ok in verdict(row):
print(f" {'PASS' if ok else 'FAIL'} {key:<10} {value:8.1f} (limit {limit})")
if row["median_ms"] is not None:
tag = "under" if row["median_ms"] < GFN_MAX_MS else "OVER"
print(f" NVIDIA 80 ms rule: median {row['median_ms']:.1f} ms is {tag}")
if row["down_mbps"] is not None:
print(f" throughput during load: {row['down_mbps']:.0f} Mbps")
The CSV is the point of the whole exercise. One run proves little. Twenty runs across three connection types and four times of day prove a lot.
Step 6: Add the Report, Session Log and CLI (5 min)
The last piece reads the CSV back, groups runs by label and host, and adds a second log for free-tier sessions. First the report and the session commands.
def read_rows(path):
if not path.exists():
raise SystemExit(f"No data yet: run a probe first ({path})")
with path.open(newline="") as fh:
return list(csv.DictReader(fh))
def num(value):
return float(value) if value not in ("", None) else None
def cmd_report(args):
groups = {}
for row in read_rows(PROBES_CSV):
groups.setdefault((row["label"], row["host"]), []).append(row)
print(f"{'label':<12}{'host':<18}{'runs':>5}{'median':>9}{'p95':>8}"
f"{'jitter':>8}{'loss%':>7}{'bloat':>8} verdict")
for (label, host), rows in sorted(groups.items()):
def avg(key):
vals = [num(r[key]) for r in rows if num(r[key]) is not None]
return statistics.mean(vals) if vals else None
merged = {k: avg(k) for k in LIMITS}
ok = all(passed for *_, passed in verdict(merged))
print(f"{label:<12}{host:<18}{len(rows):>5}{fmt(merged['median_ms']):>9}"
f"{fmt(merged['p95_ms']):>8}{fmt(merged['jitter_ms']):>8}"
f"{fmt(merged['loss_pct']):>7}{fmt(merged['bloat_ms']):>8} "
f"{'READY' if ok else 'FIX FIRST'}")
SESSION_FIELDS = ["ts", "service", "queue_s", "ads_s", "play_min", "note"]
def cmd_log(args):
append_row(SESSIONS_CSV, SESSION_FIELDS,
{"ts": datetime.now(timezone.utc).isoformat(timespec="seconds"),
"service": args.service, "queue_s": args.queue_s,
"ads_s": args.ads_s, "play_min": args.play_min, "note": args.note})
print("logged")
def cmd_sessions(args):
groups = {}
for row in read_rows(SESSIONS_CSV):
groups.setdefault(row["service"], []).append(row)
print(f"{'service':<22}{'n':>3}{'queue s':>9}{'ads s':>8}{'play min':>10}{'play share':>12}")
for service, rows in sorted(groups.items()):
queue = statistics.mean(float(r["queue_s"]) for r in rows)
ads = statistics.mean(float(r["ads_s"]) for r in rows)
play = statistics.mean(float(r["play_min"]) for r in rows)
total = (queue + ads) / 60.0 + play
print(f"{service:<22}{len(rows):>3}{queue:>9.0f}{ads:>8.0f}{play:>10.1f}"
f"{100 * play / total:>11.1f}%")
Then the command-line entry point that ties everything together.
def main():
parser = argparse.ArgumentParser(prog="cloudlag")
sub = parser.add_subparsers(dest="cmd", required=True)
p = sub.add_parser("probe", help="measure latency, jitter, loss and bufferbloat")
p.add_argument("--host", action="append", help="repeat for several hosts")
p.add_argument("--label", default="untagged", help="e.g. wired, wifi5, wifi6-evening")
p.add_argument("--port", type=int, default=443)
p.add_argument("--count", type=int, default=50)
p.add_argument("--interval", type=float, default=0.2)
p.add_argument("--timeout", type=float, default=1.0)
p.add_argument("--load-url", default=LOAD_URL)
p.add_argument("--no-load", action="store_true")
p.set_defaults(func=cmd_probe)
r = sub.add_parser("report", help="compare every saved run by label and host")
r.set_defaults(func=cmd_report)
s = sub.add_parser("log", help="record one free-tier session")
s.add_argument("--service", required=True)
s.add_argument("--queue-s", type=float, default=0)
s.add_argument("--ads-s", type=float, default=0)
s.add_argument("--play-min", type=float, required=True)
s.add_argument("--note", default="")
s.set_defaults(func=cmd_log)
t = sub.add_parser("sessions", help="summarize queue, ad and play time")
t.set_defaults(func=cmd_sessions)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()
Save the file. You now have cloudlag.py with four commands: probe, report, log and sessions.
Check That the Tool Works (Step 7, 2 min)
Before you trust any number, test the tool against a target whose answer you already know. Your own machine answers in a fraction of a millisecond. Open a second terminal in the same folder and start a throwaway web server.
python3 -m http.server 8765 --bind 127.0.0.1
Back in the first terminal, probe it without the load test.
python3 cloudlag.py probe --host 127.0.0.1 --port 8765 --label selftest --count 15 --interval 0.1 --no-load
python3 cloudlag.py report
We ran exactly this on Python 3.12.3 and got the following. Your decimals will differ slightly.
127.0.0.1 [selftest] (127.0.0.1)
PASS median_ms 0.2 (limit 40.0)
PASS p95_ms 0.2 (limit 80.0)
PASS jitter_ms 0.0 (limit 8.0)
PASS loss_pct 0.0 (limit 1.0)
NVIDIA 80 ms rule: median 0.2 ms is under
label host runs median p95 jitter loss% bloat verdict
selftest 127.0.0.1 1 0.2 0.2 0.0 0.0 - READY
Stop the throwaway server with Ctrl+C. Then delete the self-test row if you want a clean history, by removing the selftest line from ~/.cloudlag/probes.csv in any text editor.
Run the Measurements
Now the real work. The goal is a small table of results you can compare, so label every run honestly.
Step 8: Record the Wired Baseline (5 min)
Plug the machine into the router with an Ethernet cable and close everything that uses the network: cloud sync, game launchers, streaming tabs, phone backups. Then run the default probe, which tests four hosts.
python3 cloudlag.py probe --label wired-idle
Each host takes roughly 25 seconds, because the tool runs 50 idle probes and then 50 loaded probes. The defaults ping www.nvidia.com, www.xbox.com, www.amazon.com and 1.1.1.1. Those are the front doors of the three services plus a neutral public resolver, so you can tell a service-specific slowdown from a general one. Here is what a healthy wired result looks like. These are illustrative values, not a benchmark, and yours will differ.
www.nvidia.com [wired-idle] (203.0.113.40)
PASS median_ms 14.2 (limit 40.0)
PASS p95_ms 17.9 (limit 80.0)
PASS jitter_ms 1.8 (limit 8.0)
PASS loss_pct 0.0 (limit 1.0)
PASS bloat_ms 9.4 (limit 30.0)
NVIDIA 80 ms rule: median 14.2 ms is under
throughput during load: 312 Mbps
The address in that sample comes from a documentation range, so do not look for it. Your output will show the real address.
Step 9: Add Wi-Fi, Evening and Router-Only Runs (6 min)
Unplug the cable, join your 5 GHz network, and stand where you would actually play. Run the same command with a different label.
python3 cloudlag.py probe --label wifi5-idle
Now isolate the Wi-Fi hop from the internet. Most routers answer on their web interface port, and that gives you a latency reading to the router alone. Replace the address with your router’s.
python3 cloudlag.py probe --host 192.168.1.1 --port 80 --label wifi5-router --no-load
If the router-only median is high or jittery, the problem lives in your living room, not at your ISP. If it is clean while the internet hosts are slow, the ISP or the route is the culprit. Finally, repeat the Wi-Fi run between 7 and 10 in the evening, when neighbors are streaming, and label it wifi5-evening. Phones and tablets deserve their own pass later, and our free cloud gaming on iPhone and Android guide covers that setup.
Step 10: Read the Report (3 min)
Run the report to put every label side by side.
python3 cloudlag.py report
An illustrative result after four runs looks like this.
label host runs median p95 jitter loss% bloat verdict
wired-idle www.nvidia.com 1 14.2 17.9 1.8 0.0 9.4 READY
wifi5-idle www.nvidia.com 1 16.8 41.5 6.9 0.0 22.7 READY
wifi5-evening www.nvidia.com 1 19.3 112.0 14.6 2.0 58.3 FIX FIRST
wifi5-router 192.168.1.1 1 3.1 24.8 9.7 0.0 - FIX FIRST
Read it from left to right. The evening Wi-Fi row fails on p95, jitter, loss and bloat at once, while the median looks fine. That pattern is the classic trap. A median-only speed test would have called this connection healthy. The router-only row shows a 3 ms median but nearly 10 ms of jitter, which points at the radio link rather than the ISP.
Fix What the Numbers Show (Step 11, 10 min)
Match your failing metric to the likely cause, fix one thing at a time, and re-run with a new label after each change. Changing three settings at once teaches you nothing.
| Failing pattern | Likely cause | First fix |
|---|---|---|
| High jitter on Wi-Fi, clean on wired | Crowded channel or weak signal | Move to 5 GHz, change channel, move closer |
| High bloat only under load | Router queue fills up | Enable smart queue management (SQM) or QoS |
| Loss on every host | Bad cable, flaky link or overloaded router | Swap the cable, reboot, test wired |
| High median to one service only | Routing path to that CDN | Retest later, try another DNS resolver |
| Clean idle, bad evening | Neighbor or household contention | Schedule play off-peak or add QoS |
| Router-only jitter | Wi-Fi radio problem | Wired connection or a closer access point |
NVIDIA’s own advice lines up with the table. Its requirements page recommends “a hardwired Ethernet connection, or a router with a 5 GHz WiFi connection.” If you can run a cable, do that and skip the rest. Where you cannot, router-level SQM (fq_codel or CAKE on routers that support them) is the most effective fix for bufferbloat. Consumer routers ship it under names like Smart Queue, Adaptive QoS or Game Mode, so look for those labels.
After each change, run python3 cloudlag.py probe --label wifi5-after-qos and compare in the report. You want the bloat column to drop first. The median barely moves, and that is fine, because SQM exists to protect you under load.
Test Each Free Cloud Gaming Service From the Inside (Step 12, 8 min)
Your numbers tell you the home network is ready. Now check the services themselves, and log what each one costs in time.
For GeForce NOW, NVIDIA’s requirements page points to a knowledge base article on testing your network, and the client app includes a network test. Run it before your first session and compare its latency to your www.nvidia.com median. If the app reports under 80 ms from an NVIDIA data center, you meet the one rule NVIDIA states. If cloudlag looks great and the in-app result does not, the route to the data center is the issue, and no home fix will change it.
For Xbox, the free test needs Insider enrollment, and a missing option is the normal state for most accounts today. Enroll through the Xbox Insider Hub if you want in, and expect the test to show up only for eligible accounts and markets. Since Xbox publishes no latency target, your own session log is the best evidence you will get. Gamereactor’s report also reminds Game Pass subscribers that November caps their cloud time, so a log of real hours helps there too.
For Amazon Luna, you need Prime and a supported device. Amazon’s own pages gave no latency number we could verify, so lean on your measurements and the log below. Our Luna, GeForce NOW and Xbox Cloud Gaming head-to-head helps with the library side.
Now log each session right after it ends. Use seconds for queue and ad time, and minutes for play.
python3 cloudlag.py log --service "GeForce NOW free" --queue-s 240 --ads-s 0 --play-min 52 --note "evening, wifi5"
python3 cloudlag.py log --service "Xbox free test" --queue-s 30 --ads-s 45 --play-min 61 --note "wired"
python3 cloudlag.py log --service "Luna via Prime" --queue-s 15 --ads-s 0 --play-min 58
python3 cloudlag.py sessions
After a week of entries, the summary prints the share of your time that was actual play. Illustrative output:
service n queue s ads s play min play share
GeForce NOW free 4 255 0 49.5 92.1%
Luna via Prime 3 20 0 56.7 98.3%
Xbox free test 5 38 52 58.2 97.0%
Choose Your Free Cloud Gaming Tier (Step 13, 5 min)
Pair the two data sets. The probe results say whether your network qualifies. The session log says what each free tier charges you in waiting. Then use this decision grid.
| What your data shows | What to do |
|---|---|
| All labels READY, play share above 95 percent | Stay free. Any service works, so pick by library |
| READY on wired, FIX FIRST on Wi-Fi | Play wired, or fix Wi-Fi before paying anyone |
| READY everywhere, long queues on one service | Move to a service with shorter waits at your play time |
| READY everywhere, ads eat more than 10 percent of each session | Price a paid pack against your weekly hours |
| FIX FIRST everywhere | Do not subscribe to anything yet |
The paid-pack math from earlier is your reference point. If you watch ads for 45 seconds before a 60-minute session, you lose well under two percent to ads. If you play 8 to 10 hours a month, the 10-hour pack at $3.99 may cost less than the time you lose in queues, and the longer packs cost less per hour. Heavy players should compare against subscriptions in our GeForce NOW, Xbox Cloud Gaming and Boosteroid comparison, and anyone still choosing a service should skim our free cloud gaming setup roundup.
Remember the Xbox caveat. Cloud Playtime and the free test are Insider-stage features today, and Xbox described the paid rollout as planned for November. Reread your own log in a month before you commit to any plan built on them.
Common Pitfalls
Most bad conclusions from this kind of testing trace back to a handful of mistakes.
- Testing once, at 3 p.m. A single idle afternoon run hides evening congestion. Collect at least one run per time-of-day label before judging.
- Leaving background traffic on. A cloud backup or game update during the idle test inflates every metric. Close launchers, pause sync, and ask the household to stay off video for five minutes.
- Testing over a VPN. A VPN adds a hop and changes the route, so your numbers describe the VPN, not your line. Turn it off for the baseline.
- Trusting the median alone. The median hides spikes. Read p95, jitter and loss together, as the evening example in Step 10 shows.
- Running the loaded test on a metered link. Two streams can pull hundreds of megabytes in a minute on a fast line. Use
--no-loadon mobile hotspots and capped plans. - Comparing hosts on different continents. A host far away will always look slower. Compare like with like, and keep the neutral resolver as your control.
- Treating the Insider test as general availability. Xbox’s free ad-supported option is a test, and November is a stated plan. Do not build a household budget on it.
For broader reading on where each service fits, start from our gaming coverage hub.
Troubleshooting
Nine problems cover almost every failure we have seen with a script like this.
- “DNS failed” for every host. Your machine has no working resolver or no network. Open any website to confirm, then try
--host 1.1.1.1, which skips DNS entirely. - 100 percent loss to one host, fine to others. That host may block or rate-limit port 443 from your network, or your firewall is interfering. Test
--port 80or pick another host. - Throughput during load reads 0 Mbps. The load URL is blocked, unreachable or intercepted by a proxy. Set
--load-urlto any large file you can reach, or skip the load test with--no-load. - Bufferbloat always shows as 0. Either your router already runs SQM, which is good news, or the download never ramped up. Check that the throughput line shows a believable speed.
- Results change wildly between runs. Raise
--countto 100 and keep the machine idle. Short runs cannot capture p95 reliably. - “No data yet” from report or sessions. You have not run a probe or log command on this machine, or you changed the home directory. The files live in
~/.cloudlag/. - “python3 is not recognized” on Windows. Use
pyinstead, or install Python from the official site and tick the option that adds it to PATH. - The Xbox free option never appears. That is expected for non-Insider accounts and unsupported markets. Confirm enrollment in the Xbox Insider Hub and check again after the November rollout Xbox has described.
- Latency is excellent but the stream still stutters. The bottleneck may be the service, the route to the data center, or your device’s decoder. Run the in-app test and try a wired connection or a different client.
- The CSV looks wrong in a spreadsheet. Some locales expect semicolons as separators. Use the import wizard and set comma as the delimiter.
Advanced Tips
Once the basics work, a few additions turn a one-off check into a monitor.
Schedule it. Add a cron job on macOS or Linux, or a Task Scheduler entry on Windows, that runs python3 cloudlag.py probe --label scheduled --no-load --count 30 every hour. After two weeks you will have a map of exactly when your ISP degrades, which beats guessing why Thursday nights are bad.
Lengthen the sample before big decisions. Fifty probes give a rough p95. For a purchase or a cancellation, run --count 200 --interval 0.25 once at peak time.
Test the home-streaming path too. If you stream from your own gaming PC, point --host at that machine’s address and a port it listens on to see the LAN or VPN path. Our self-hosted Sunshine, Moonlight and Tailscale setup pairs naturally with this, because Tailscale can add its own latency.
Compare IPv4 and IPv6. The tool uses whichever address the resolver returns first. Pass an IPv6 literal with --host to test that path separately, since some networks route the two very differently.
Plot it. Import probes.csv into any spreadsheet, filter by label, and chart p95 and jitter over time. Spikes you cannot see in the report appear immediately on a line chart.
Raise the bar for shooters. Tighten LIMITS to a 25 ms median and 5 ms jitter if you play fast-paced titles. These are your preferences, not vendor requirements.
The Complete Project
The finished tool is one file plus two CSV logs that the script creates itself.
cloudlag/
cloudlag.py # the six pieces from Steps 2 to 6, in order
~/.cloudlag/
probes.csv # one row per host per probe run
sessions.csv # one row per free-tier session
If you saved the pieces separately, join them in the order they appear in Steps 2 through 6, then compile-check the result.
python3 -m py_compile cloudlag.py && echo OK
Keep this cheat sheet next to the monitor.
python3 cloudlag.py probe --label wired-idle
python3 cloudlag.py probe --label wifi5-evening
python3 cloudlag.py probe --host 192.168.1.1 --port 80 --label wifi5-router --no-load
python3 cloudlag.py report
python3 cloudlag.py log --service "GeForce NOW free" --queue-s 240 --play-min 52
python3 cloudlag.py sessions
Here is the whole workflow in one sentence: measure wired, measure Wi-Fi, fix the worst metric, log real sessions for a week, then let the play-share column decide whether free is good enough.
FAQ
Is Xbox Cloud Gaming really free now? Only partly. As of October 23, 2026, Xbox is testing a free, ad-supported option for Xbox Insiders without Game Pass. It is not a general release, and availability varies by market, device and account.
What latency do I need for free cloud gaming? NVIDIA states that it requires under 80 ms of network latency from an NVIDIA data center for GeForce NOW. Xbox and Amazon published no equivalent number in the sources we checked, so cloudlag uses stricter house rules for everyday comfort.
Does cloudlag measure the exact path to a game server? No. It times connections to well-connected front-door hosts, which tells you whether your home network and ISP are healthy. Use each service’s in-app network test for the final check.
How much does Xbox Cloud Playtime cost? The introductory Insider prices are $3.99 for 10 hours, $7.99 for 25 hours and $11.99 for 50 hours. Xbox says a broader rollout is planned for November 2026.
Will the load test use a lot of data? It can. Two download streams for roughly 12 seconds can move hundreds of megabytes on a fast line. Add --no-load on metered connections.
Is Wi-Fi good enough for cloud gaming? It can be, if the numbers say so. NVIDIA recommends wired Ethernet or a 5 GHz Wi-Fi router, and your own Wi-Fi labels in the report show whether your signal behaves.
Do I need to know Python to use this? No. Paste the six pieces into one file and run the commands. The script uses only the standard library, so there is nothing else to install.





