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
|
#!/usr/bin/env python3
"""Count real idle poll calls before/after deadline-driven HTTP sleeping."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import select
import signal
import subprocess
import tarfile
import tempfile
import time
def observe(binary, seconds):
with tempfile.TemporaryDirectory(prefix='pure-idle-poll-') as name:
root = Path(name)
trace = root / 'poll.log'
tracer = subprocess.Popen(['strace', '-qq', '-e', 'trace=poll,ppoll', '-o',
str(trace), str(binary), '--store',
str(root / 'store'), '--port', '0'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
start_new_session=True)
try:
ready, _, _ = select.select([tracer.stdout], [], [], 10)
assert ready, 'HTTP host did not become ready under strace'
message = json.loads(tracer.stdout.readline())
assert message['port'] > 0
children = Path(f'/proc/{tracer.pid}/task/{tracer.pid}/children')
pids = children.read_text().split()
assert len(pids) == 1, pids
time.sleep(seconds)
os.kill(int(pids[0]), signal.SIGTERM)
_, error = tracer.communicate(timeout=10)
assert tracer.returncode == 0, (tracer.returncode, error[-1000:])
captured = trace.read_text()
calls = [line for line in captured.splitlines()
if line.startswith(('poll(', 'ppoll('))]
values = []
for line in calls:
if line.startswith('ppoll('):
if re.search(r'\],\s*\d+,\s*NULL,', line):
values.append(-1)
elif match := re.search(r'\{tv_sec=(\d+), tv_nsec=(\d+)\}', line):
values.append(int(match.group(1)) * 1000 +
int(match.group(2)) // 1_000_000)
elif match := re.search(r',\s*(-?\d+)\)\s+=', line):
values.append(int(match.group(1)))
return {'poll_calls': len(calls), 'timeout_values_ms': sorted(set(values)),
'trace_excerpt': captured[:500],
'binary_sha256': hashlib.sha256(binary.read_bytes()).hexdigest()}
finally:
if tracer.poll() is None:
os.killpg(tracer.pid, signal.SIGTERM)
try:
tracer.communicate(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(tracer.pid, signal.SIGKILL)
tracer.communicate(timeout=5)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--before', type=Path, required=True)
parser.add_argument('--after', type=Path, required=True)
parser.add_argument('--output', type=Path)
args = parser.parse_args()
seconds = 1.25
with tempfile.TemporaryDirectory(prefix='pure-idle-before-') as directory:
baseline = args.before.resolve()
if baseline.name.endswith('.tar.gz'):
with tarfile.open(baseline, 'r:gz') as archive:
body = archive.extractfile('pure-document/bin/pure-connections-fixed').read()
baseline = Path(directory) / 'pure-connections-fixed'
baseline.write_bytes(body)
baseline.chmod(0o755)
before = observe(baseline, seconds)
after = observe(args.after.resolve(), seconds)
assert before['poll_calls'] >= 10, before
assert after['poll_calls'] <= 2, after
assert -1 in after['timeout_values_ms'], after
result = {'profile': 'idle-http-host-poll-syscalls',
'observation_seconds': seconds, 'before': before, 'after': after,
'deadline_driven_idle_wait': 'pass'}
body = json.dumps(result, indent=2) + '\n'
if args.output:
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(body)
print(body, end='')
if __name__ == '__main__':
main()
|