1"""Chapter 5 practice: latency statistics, Little's law, fan-out tail maths and a
2queue simulation (Level 3 Build), and the 'averaging p99s' dashboard bug (Level 4 Debug).
3Standard library only. Run: python3 ch05_latency.py"""
4import math
5import random
6
7
8def percentile(samples, p):
9 """Nearest-rank percentile: the smallest value with at least p% of samples at or below it."""
10 if not samples:
11 raise ValueError("no samples")
12 ordered = sorted(samples)
13 rank = max(1, math.ceil(p / 100 * len(ordered)))
14 return ordered[rank - 1]
15
16
17def summarize(samples):
18 return {
19 "mean": sum(samples) / len(samples),
20 "p50": percentile(samples, 50),
21 "p95": percentile(samples, 95),
22 "p99": percentile(samples, 99),
23 "max": max(samples),
24 }
25
26
27def littles_law(throughput_per_s, latency_s):
28 """L = lambda x W: requests in flight = arrival rate x time each one spends inside."""
29 return throughput_per_s * latency_s
30
31
32def fanout_slow_probability(p_slow, n):
33 """Chance that at least one of n parallel calls is slow, if each is slow with probability p_slow."""
34 return 1 - (1 - p_slow) ** n
35
36
37def simulate_queue(arrival_rate, service_rate, n=200_000, seed=1):
38 """One server, random (Poisson) arrivals, random (exponential) service times.
39 Returns the average time a request spends waiting plus being served."""
40 rng = random.Random(seed)
41 t = 0.0
42 server_free_at = 0.0
43 total = 0.0
44 for _ in range(n):
45 t += rng.expovariate(arrival_rate)
46 start = max(t, server_free_at)
47 server_free_at = start + rng.expovariate(service_rate)
48 total += server_free_at - t
49 return total / n
50
51
52# ---- Level 4: the dashboard bug ---------------------------------------------------------
53
54def make_servers(seed=7):
55 """Ten servers, 1,000 requests each. Nine are healthy; server 9 has a slow disk."""
56 rng = random.Random(seed)
57 servers = []
58 for s in range(10):
59 lat = [rng.uniform(20, 60) for _ in range(1000)] # healthy: 20-60 ms
60 if s == 9:
61 lat = [rng.uniform(20, 60) if rng.random() > 0.3 else rng.uniform(800, 1200)
62 for _ in range(1000)] # 30% very slow
63 servers.append(lat)
64 return servers
65
66
67def buggy_overall_p99(servers):
68 return sum(percentile(s, 99) for s in servers) / len(servers) # BUG: averages percentiles
69
70
71def fixed_overall_p99(servers):
72 merged = [x for s in servers for x in s] # fix: percentile of all samples
73 return percentile(merged, 99)
74
75
76def run_checks():
77 lat = [10] * 90 + [50] * 9 + [2000]
78 s = summarize(lat)
79 assert s["p50"] == 10 and s["p95"] == 50 and s["p99"] == 50 and s["max"] == 2000
80 assert abs(s["mean"] - 33.5) < 1e-9 # one slow request drags the mean far above p50
81
82 assert littles_law(5000, 0.2) == 1000
83 assert abs(fanout_slow_probability(0.01, 1) - 0.01) < 1e-12
84 assert abs(fanout_slow_probability(0.01, 100) - 0.634) < 0.001
85
86 # Queue maths: average time in system = 1 / (service_rate - arrival_rate).
87 for util in (0.5, 0.9):
88 mu = 100.0
89 lam = util * mu
90 expected = 1 / (mu - lam)
91 got = simulate_queue(lam, mu)
92 assert abs(got - expected) / expected < 0.15, (util, got, expected)
93
94 # Level 4
95 servers = make_servers()
96 bad = buggy_overall_p99(servers)
97 good = fixed_overall_p99(servers)
98 assert good > 3 * bad, (bad, good)
99
100 print(f"All checks pass. Averaged p99s: {bad:.0f} ms. Real p99: {good:.0f} ms.")
101 print(f"Queue at 50% busy: {simulate_queue(50, 100) * 1000:.0f} ms, at 90%: "
102 f"{simulate_queue(90, 100) * 1000:.0f} ms (service time 10 ms).")
103
104
105if __name__ == "__main__":
106 run_checks()