Skip to content

Commit bfe8d2b

Browse files
committed
Added pybar-memsnap to repo
1 parent 233659a commit bfe8d2b

2 files changed

Lines changed: 94 additions & 2 deletions

File tree

docs/content/ipc/_index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ Tracks Python memory allocations. The first call starts tracing and records a ba
5757

5858
The `top` field controls how many allocation sites are returned (default: 30).
5959

60-
The helper script `pybar-memsnap` automates this workflow — it records a baseline, waits 5 minutes, then prints the diff:
60+
The helper script `pybar-memsnap` (found in `scripts/` in the repo) automates this workflow — it records a baseline, waits 5 minutes, then prints the diff:
6161

6262
```bash
63-
pybar-memsnap
63+
python3 scripts/pybar-memsnap
6464
```
6565

6666
### Widget instance counter

scripts/pybar-memsnap

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Description: Measure live Python object growth over time via pybar's
4+
debug IPC commands. Requires pybar to be running with --debug.
5+
Author: thnikk
6+
7+
Usage:
8+
pybar-memsnap baseline + wait 5 min + diff
9+
pybar-memsnap --quick baseline + wait 1 min + diff (less accurate)
10+
"""
11+
import argparse
12+
import json
13+
import socket
14+
import sys
15+
import time
16+
import os
17+
18+
SOCK = os.path.expanduser('~/.cache/pybar/pybar.sock')
19+
20+
21+
def ipc(cmd):
22+
""" Send a command to pybar's IPC socket and return the response. """
23+
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
24+
s.connect(SOCK)
25+
s.sendall((json.dumps(cmd) + '\n').encode())
26+
data = b''
27+
while True:
28+
chunk = s.recv(65536)
29+
if not chunk:
30+
break
31+
data += chunk
32+
if b'\n' in data:
33+
break
34+
return json.loads(data.decode().strip())
35+
36+
37+
def parse_counts(lines):
38+
""" Parse object count lines into a dict of {type: count}. """
39+
result = {}
40+
for line in lines:
41+
parts = line.strip().split('x ', 1)
42+
if len(parts) == 2:
43+
result[parts[1]] = int(parts[0])
44+
return result
45+
46+
47+
def run(wait):
48+
""" Take a baseline count, wait, then print the diff. """
49+
print('Taking baseline object count...', flush=True)
50+
r0 = ipc({'action': 'objcount', 'top': 200})
51+
if r0.get('status') != 'ok':
52+
print(f"Error: {r0.get('message', 'unknown error')}", file=sys.stderr)
53+
sys.exit(1)
54+
counts0 = parse_counts(r0['counts'])
55+
total0 = sum(counts0.values())
56+
print(f'Baseline: {total0} total objects', flush=True)
57+
58+
print(f'Waiting {wait}s...', flush=True)
59+
time.sleep(wait)
60+
61+
print('Taking second object count...', flush=True)
62+
r1 = ipc({'action': 'objcount', 'top': 200})
63+
counts1 = parse_counts(r1['counts'])
64+
total1 = sum(counts1.values())
65+
print(f'After: {total1} total objects')
66+
67+
diff = {}
68+
for t in set(counts0) | set(counts1):
69+
d = counts1.get(t, 0) - counts0.get(t, 0)
70+
if d != 0:
71+
diff[t] = d
72+
73+
print(f'\n=== Object count diff over {wait}s ===')
74+
print('(positive = grew, negative = shrank)')
75+
for name, delta in sorted(diff.items(), key=lambda x: -x[1])[:40]:
76+
bar_len = min(abs(delta) // 10, 40)
77+
bar = ('+' if delta > 0 else '-') * bar_len
78+
print(f' {delta:+6d} {name:40s} {bar}')
79+
80+
81+
def main():
82+
parser = argparse.ArgumentParser(
83+
description='Profile pybar memory growth via debug IPC.')
84+
parser.add_argument(
85+
'--quick', action='store_true',
86+
help='Use a 60s window instead of 300s (less accurate)')
87+
args = parser.parse_args()
88+
run(wait=60 if args.quick else 300)
89+
90+
91+
if __name__ == '__main__':
92+
main()

0 commit comments

Comments
 (0)