-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbenchmark.py
147 lines (115 loc) · 4.15 KB
/
benchmark.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import json
import shlex
import subprocess
import sys
import threading
import time
import requests
setups = [
""
# "--worker-class=gevent --worker-connections=5000 --workers 8",
# "--worker-class=gevent --worker-connections=5000 --workers 32",
# '--worker-class=gevent --worker-connections=5000 --workers 64',
# '--worker-class=gevent --worker-connections=1000 --workers 8',
# '--worker-class=gevent --worker-connections=1000 --workers 32',
# '--worker-class=gevent --worker-connections=1000 --workers 64',
# '--workers 8',
# '--workers 32',
# '--workers 64',
# '--workers 8 --threads 2',
# '--workers 8 --threads 4',
# '--workers 8 --threads 8',
# '--workers 16 --threads 2',
# '--workers 16 --threads 4',
# '--workers 32 --threads 2',
]
results = dict()
def r(process, **kwargs):
print(f">>> {process}")
return subprocess.run(shlex.split(process), **kwargs)
def docker(*command):
cmd = ["docker"] + list(command)
print(f">>> {cmd}")
try:
p = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return [ln for ln in p.stdout.decode().split("\n") if ln]
except Exception:
print(p.stdout.decode())
print(p.stderr.decode())
raise
def get_raw_stats(container_id):
cols = [c.strip() for c in docker("stats", "--no-stream", container_id)[1].split(" ") if c]
return {
"CPUPerc": cols[2],
"MemUsage": cols[3],
}
def parse_cpuperc(s):
return float(s.rstrip("%")) / 100
def parse_memusage_to_mb(s):
s = s.split("/")[0].strip()
if s.endswith("MiB"):
return float(s.rstrip("MiB"))
elif s.endswith("GiB"):
return float(s.rstrip("GiB")) * 1024
else:
raise Exception(f"Unexpected memusage value: {s}")
def empty_measurements():
return {
"Amount of used CPU cores": [],
"MB of used memory": [],
}
def collect_metrics_in_the_background(container_id, measurements, stop):
# Print docker stats for the container for as long as it is running
while True:
current_stats = get_raw_stats(container_id)
measurements["Amount of used CPU cores"].append(parse_cpuperc(current_stats["CPUPerc"]))
measurements["MB of used memory"].append(parse_memusage_to_mb(current_stats["MemUsage"]))
if stop():
return
time.sleep(1)
def wait_for_port(port):
while True:
try:
requests.get(f"http://localhost:{port}/asdfasdxczc1231412").status_code == 404
break
except Exception:
pass
time.sleep(0.1)
if __name__ == "__main__":
assert len(sys.argv) == 2, "Usage: monitor.py <image_name>"
image_name = sys.argv[1]
r(f"docker build . -t {image_name}")
for setup in setups:
try:
p = r(
f"docker run -d -p 8080:8080 {image_name} --db=/tmp/db.sqlite3",
stdout=subprocess.PIPE,
)
container_id = p.stdout.decode().strip()
wait_for_port(8080)
# Give it some time, gunicorn opens the ports before workers are booted
time.sleep(5)
stopped = False
metrics = empty_measurements()
metrics_thread = threading.Thread(
target=collect_metrics_in_the_background,
args=(container_id, metrics, lambda: stopped),
)
metrics_thread.start()
r("k6 run tests/k6/test.js --vus 10 --duration 10s")
stopped = True
metrics_thread.join()
with open("artifacts/benchmark.json") as f:
report = json.loads(f.read())
results[setup] = {
"reqps": report["metrics"]["http_reqs"]["values"]["rate"],
"latency": report["metrics"]["http_req_duration"]["values"],
"resources": metrics,
}
finally:
try:
the_container_id = container_id
except NameError:
the_container_id = p.stdout.decode().strip()
r(f"docker rm -f {the_container_id}")
print(json.dumps(results, indent=2))