summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/udp_scaling.py64
1 files changed, 44 insertions, 20 deletions
diff --git a/tests/udp_scaling.py b/tests/udp_scaling.py
index b5048cd..7691fcb 100644
--- a/tests/udp_scaling.py
+++ b/tests/udp_scaling.py
@@ -14,10 +14,16 @@ loop, which is what a default build does.
14LOAD_THREADS and WINDOW decide how much load is offered. Both matter. Too 14LOAD_THREADS and WINDOW decide how much load is offered. Both matter. Too
15little and the tracker idles while the figure measures round trips; too much 15little and the tracker idles while the figure measures round trips; too much
16and the kernel drops datagrams, every lost reply costs the generator a 16and the kernel drops datagrams, every lost reply costs the generator a
17timeout, and the figure collapses. The generator prints how many were lost: 17timeout, and the figure collapses.
18tune until that is near zero and the throughput has stopped rising. 18
19Three columns say which side the number came from. "cores" is the tracker's
20cpu, "load" the generator's. Whichever is near its machine's core count is
21the one being measured. "lost" counts datagrams that went unanswered: once
22it leaves zero the throughput figure is timeouts, not service, and the run
23needs a smaller WINDOW.
19""" 24"""
20import os 25import os
26import resource
21import signal 27import signal
22import socket 28import socket
23import subprocess 29import subprocess
@@ -73,6 +79,13 @@ def cpu_seconds(pid):
73 return total 79 return total
74 80
75 81
82def reaped_cpu_seconds():
83 """Cpu of every child already waited for. The generator is the only one
84 that starts and finishes inside a measured run."""
85 spent = resource.getrusage(resource.RUSAGE_CHILDREN)
86 return spent.ru_utime + spent.ru_stime
87
88
76def stop(tracker): 89def stop(tracker):
77 tracker.send_signal(signal.SIGINT) 90 tracker.send_signal(signal.SIGINT)
78 try: 91 try:
@@ -82,17 +95,28 @@ def stop(tracker):
82 tracker.wait() 95 tracker.wait()
83 96
84 97
98def parse(line):
99 """replies/s and the count of announces that were never answered"""
100 words = line.split()
101 if not words or not words[0].replace(".", "").isdigit():
102 return None, None
103 lost = 0
104 if "lost," in words:
105 lost = int(words[words.index("lost,") - 1])
106 return float(words[0]), lost
107
108
85def main(): 109def main():
86 if not os.path.exists(LOADER): 110 if not os.path.exists(LOADER):
87 raise SystemExit("build the generator first: cc -O2 -pthread -o %s tests/udp_load.c" % LOADER) 111 raise SystemExit("build the generator first: cc -O2 -pthread -o %s tests/udp_load.c" % LOADER)
88 112
89 print(" tracker %s, %d load threads x %d in flight, %d torrents, %.0f s x %d runs" 113 print(" tracker %s, %d load threads x %d in flight, %d torrents, %.0f s x %d runs"
90 % (TRACKER, LOAD_THREADS, WINDOW, TORRENTS, SECONDS, REPEATS)) 114 % (TRACKER, LOAD_THREADS, WINDOW, TORRENTS, SECONDS, REPEATS))
91 print(" %-8s %12s %12s %12s %7s %s" 115 print(" %-8s %12s %7s %7s %7s %10s %s"
92 % ("workers", "median/s", "lowest", "highest", "cores", "vs 0 workers")) 116 % ("workers", "median/s", "spread", "cores", "load", "lost", "vs 0 workers"))
93 baseline = None 117 baseline = None
94 for index, workers in enumerate(WORKER_COUNTS): 118 for index, workers in enumerate(WORKER_COUNTS):
95 rates, cores = [], [] 119 results = []
96 for repeat in range(REPEATS): 120 for repeat in range(REPEATS):
97 port = PORT_BASE + index * 16 + repeat 121 port = PORT_BASE + index * 16 + repeat
98 tracker, log = start(port, workers) 122 tracker, log = start(port, workers)
@@ -101,34 +125,34 @@ def main():
101 # place, so the measured run is renewals rather than insertions 125 # place, so the measured run is renewals rather than insertions
102 subprocess.run([LOADER, "127.0.0.1", str(port), str(LOAD_THREADS), "2", str(TORRENTS), str(WINDOW)], 126 subprocess.run([LOADER, "127.0.0.1", str(port), str(LOAD_THREADS), "2", str(TORRENTS), str(WINDOW)],
103 stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 127 stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
104 spent, wall = cpu_seconds(tracker.pid), time.time() 128 spent, offered, wall = cpu_seconds(tracker.pid), reaped_cpu_seconds(), time.time()
105 result = subprocess.run( 129 result = subprocess.run(
106 [LOADER, "127.0.0.1", str(port), str(LOAD_THREADS), str(SECONDS), str(TORRENTS), str(WINDOW)], 130 [LOADER, "127.0.0.1", str(port), str(LOAD_THREADS), str(SECONDS), str(TORRENTS), str(WINDOW)],
107 capture_output=True, text=True, 131 capture_output=True, text=True,
108 ) 132 )
109 used = cpu_seconds(tracker.pid) 133 used, drove, elapsed = cpu_seconds(tracker.pid), reaped_cpu_seconds(), max(time.time() - wall, 1e-9)
110 finally: 134 finally:
111 stop(tracker) 135 stop(tracker)
112 log.close() 136 log.close()
113 head = result.stdout.strip().split() 137 rate, lost = parse(result.stdout.strip())
114 if head and head[0].replace(".", "").isdigit(): 138 if rate is None:
115 rates.append(float(head[0])) 139 continue
116 if used is not None and spent is not None: 140 cores = (used - spent) / elapsed if (used is not None and spent is not None) else None
117 cores.append((used - spent) / max(time.time() - wall, 1e-9)) 141 results.append((rate, cores, (drove - offered) / elapsed, lost))
118 142
119 if not rates: 143 if not results:
120 print(" %-8s no result" % workers) 144 print(" %-8s no result" % workers)
121 continue 145 continue
122 rates.sort() 146 results.sort()
123 median = rates[len(rates) // 2] 147 rate, cores, load, lost = results[len(results) // 2]
124 if baseline is None: 148 if baseline is None:
125 baseline = median 149 baseline = rate
126 # A spread wider than the differences between rows means the rows are 150 # A spread wider than the differences between rows means the rows are
127 # not telling you anything 151 # not telling you anything
128 busy = ("%7.2f" % (sorted(cores)[len(cores) // 2])) if cores else " ?" 152 spread = 100.0 * (results[-1][0] - results[0][0]) / rate
129 print(" %-8s %12.0f %12.0f %12.0f %s %.2fx (spread %.0f%%)" 153 print(" %-8s %12.0f %6.0f%% %7s %7.2f %10d %.2fx"
130 % (workers, median, rates[0], rates[-1], busy, median / baseline, 154 % (workers, rate, spread, ("%.2f" % cores) if cores else "?", load, lost, rate / baseline))
131 100.0 * (rates[-1] - rates[0]) / median)) 155 sys.stdout.flush()
132 156
133 157
134if __name__ == "__main__": 158if __name__ == "__main__":