aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/022-tpcc-grouped/owner-samples.py
blob: 5415cf691df294d694143e92dc11a9bf135f0f94 (plain)
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
#!/usr/bin/env python3
"""Where pure-serve's owner thread is: its stack sampled with gdb (a poor man's profiler; perf is not on this Pi).

    owner-samples.py SAMPLES OUT.txt

Waits for a pure-serve with a pure-owner thread, then attaches gdb SAMPLES
times, a little apart, and keeps the owner's stack each time. Prints how
many samples had each serve.c phase on the stack, and the innermost frames.
"""
import collections
import re
import subprocess
import sys
import time
from pathlib import Path

samples, out = int(sys.argv[1]), Path(sys.argv[2])


def owner():
    for proc in Path('/proc').iterdir():
        if not proc.name.isdigit():
            continue
        try:
            if (proc / 'comm').read_text().strip() != 'pure-serve':
                continue
            for task in (proc / 'task').iterdir():
                if (task / 'comm').read_text().strip() == 'pure-owner':
                    return int(proc.name), int(task.name)
        except OSError:
            continue
    return None


found = None
while not found:
    found = owner()
    time.sleep(0.2)
pid, tid = found
time.sleep(10)  # past loading, into the run
stacks = []
for _ in range(samples):
    text = subprocess.run(['gdb', '-batch', '-nx', '-p', str(pid), '-ex', 'set pagination off',
                           '-ex', 'thread apply all bt 40'], capture_output=True, text=True).stdout
    # The owner's section: from its "Thread ... (LWP tid)" header to the next thread's.
    match = re.search(rf'Thread [^\n]*\(LWP {tid}\)[^\n]*\n(.*?)(?=\nThread |\Z)', text, re.S)
    if match:
        frames = re.findall(r'^#\d+\s+(?:0x[0-9a-f]+ in )?(\S+)', match.group(1), re.M)
        stacks.append(frames)
    time.sleep(0.3)
out.write_text('\n'.join(' < '.join(s) for s in stacks) + '\n')
phases = collections.Counter()
inner = collections.Counter()
for frames in stacks:
    names = set(frames)
    phase = ('waiting' if names & {'pthread_cond_wait', 'pthread_cond_timedwait', '__futex_abstimed_wait_common'}
             and not names & {'execute'} else
             'publish' if 'publish' in names else
             'checkpoint_round' if 'checkpoint_round' in names else
             'feed_outlets' if 'feed_outlets' in names else
             'save' if 'save_burst' in names else
             'hand_back' if 'hand_back' in names else
             'apply' if 'apply_effect' in names else
             'other')
    phases[phase] += 1
    inner[frames[0] if frames else '?'] += 1
print(f'{len(stacks)} samples of the owner (pid {pid}, thread {tid})')
for phase, n in phases.most_common():
    print(f'  {phase:<18} {n:>4}  {n / max(len(stacks), 1):.0%}')
print('innermost frames:')
for name, n in inner.most_common(15):
    print(f'  {name:<40} {n:>4}')