diff --git a/scripts/fsp-server.py b/scripts/fsp-server.py index 6ffd370..56d708b 100755 --- a/scripts/fsp-server.py +++ b/scripts/fsp-server.py @@ -311,6 +311,17 @@ def main(): uploads = {} # peer -> staged bytes, committed by CC_INSTALL service = [] # per-request service time, to split the round trip + # ⚠️ A plain bytes/elapsed average is NOT the capability of the path: the + # game reads in bursts, so any window that contains idle time understates + # what the link can do. Tracking the gap between consecutive requests + # separates the two -- gaps under IDLE_GAP are the round trip (we are + # streaming), longer ones are the game not asking. + IDLE_GAP = 0.010 + active_time = 0.0 # seconds actually spent streaming + active_bytes = 0 + idle_time = 0.0 + intervals = [] # inter-request gaps while active + t_prev = None served = 0 t_first = None t_report = time.time() @@ -510,6 +521,15 @@ def main(): reads += 1 now = time.time() service.append(now - t_req) + if t_prev is not None: + gap = now - t_prev + if gap <= IDLE_GAP: + active_time += gap + active_bytes += len(data) + intervals.append(gap) + else: + idle_time += gap + t_prev = now if t_first is None: t_first = now print(f"[{peer[0]}] GET_FILE {path!r} streaming started") @@ -520,16 +540,26 @@ def main(): el = max(now - t_first, 1e-9) svc = sorted(service) med = svc[len(svc) // 2] * 1000 if svc else 0.0 - rtt = (el / reads) * 1000 if reads else 0.0 - print(f" streaming {served/el/1024:7.0f} KB/s " - f"{served/2**20:8.1f} MiB in {el:6.1f}s " - f"{reads/el:5.0f} reads/s {len(data)} B/reply") - # One request in flight, so the observed interval IS the round - # trip. Splitting it shows whether WE are the slow part. - print(f" round trip {rtt:5.2f} ms, of which this server " - f"{med:5.2f} ms (median) -> " - f"{'SERVER-bound' if med > rtt/2 else 'console-bound'}") + iv = sorted(intervals) + rtt = iv[len(iv) // 2] * 1000 if iv else 0.0 + duty = 100 * active_time / el + cap = (active_bytes / active_time / 1024) if active_time else 0 + print(f" average {served/el/1024:7.0f} KB/s over {el:6.1f}s " + f"({served/2**20:7.1f} MiB, {len(data)} B/reply)") + # The capability figure: bytes served divided by the time + # actually spent serving, with idle gaps excluded. This is + # what the path can do; the average above is that times the + # duty cycle, i.e. how much the game happened to ask for. + print(f" CAPABLE {cap:7.0f} KB/s while streaming " + f"duty {duty:5.1f}% idle {idle_time:5.1f}s") + # One request in flight, so the median gap IS the round trip. + print(f" round trip {rtt:5.2f} ms median, of which this " + f"server {med:5.2f} ms -> " + f"{'SERVER-bound' if med > rtt / 2 else 'console-bound'}" + f"; ceiling at this RTT = " + f"{(len(data)/(rtt/1000))/1024 if rtt else 0:.0f} KB/s") service.clear() + intervals.clear() t_report = now continue