Skip to content

Commit c0557c1

Browse files
committed
Add prototype for working set script
1 parent a08a073 commit c0557c1

File tree

1 file changed

+248
-0
lines changed

1 file changed

+248
-0
lines changed

working_set.py

+248
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
#!/usr/bin/env python
2+
#
3+
# working_set Track how frequently data buffers are accessed. This can help
4+
# estimating how much hot (and less hot) data a database has to
5+
# calculate values for options like shared_buffers.
6+
# For now tracking is happening via BufTableLookup, which means
7+
# we also include prefetch, but do not include local access.
8+
#
9+
# usage: working_set $PG_BIN/postgres [-d] [-p PID] [-i INTERVAL]
10+
# [-c CONTAINER_ID] [-n NAMESPACE]
11+
12+
from __future__ import print_function
13+
from time import sleep
14+
15+
import argparse
16+
import ctypes as ct
17+
import signal
18+
19+
from bcc import BPF
20+
21+
import utils
22+
23+
24+
text = """
25+
#include <linux/ptrace.h>
26+
27+
typedef unsigned int Oid;
28+
typedef unsigned int uint32;
29+
30+
typedef struct RelFileNode
31+
{
32+
Oid spcNode; /* tablespace */
33+
Oid dbNode; /* database */
34+
Oid relNode; /* relation */
35+
} RelFileNode;
36+
37+
typedef enum ForkNumber
38+
{
39+
InvalidForkNumber = -1,
40+
MAIN_FORKNUM = 0,
41+
FSM_FORKNUM,
42+
VISIBILITYMAP_FORKNUM,
43+
INIT_FORKNUM
44+
45+
/*
46+
* NOTE: if you add a new fork, change MAX_FORKNUM and possibly
47+
* FORKNAMECHARS below, and update the forkNames array in
48+
* src/common/relpath.c
49+
*/
50+
} ForkNumber;
51+
52+
typedef uint32 BlockNumber;
53+
54+
typedef struct buftag
55+
{
56+
RelFileNode rnode; /* physical relation identifier */
57+
ForkNumber forkNum;
58+
BlockNumber blockNum; /* blknum relative to begin of reln */
59+
} BufferTag;
60+
61+
struct key_t {
62+
int spcNode;
63+
int dbNode;
64+
int relNode;
65+
u32 blockNum;
66+
u64 namespace;
67+
char name[TASK_COMM_LEN];
68+
};
69+
70+
#define HASH_SIZE 2^14
71+
72+
BPF_PERF_OUTPUT(events);
73+
74+
BPF_HASH(buffers, struct key_t, long);
75+
76+
static inline __attribute__((always_inline)) void get_key(struct key_t* key) {
77+
bpf_get_current_comm(&(key->name), sizeof(key->name));
78+
}
79+
80+
int probe_buf_table_lookup(struct pt_regs *ctx, BufferTag *buf_tag, uint32 hashcode)
81+
{
82+
struct key_t key = {};
83+
get_key(&key);
84+
85+
SAVE_NAMESPACE
86+
87+
CHECK_NAMESPACE
88+
89+
key.spcNode = buf_tag->rnode.spcNode;
90+
key.dbNode = buf_tag->rnode.dbNode;
91+
key.relNode = buf_tag->rnode.relNode;
92+
key.blockNum = buf_tag->blockNum;
93+
94+
events.perf_submit(ctx, &key, sizeof(key));
95+
96+
unsigned long zero = 0, *val;
97+
val = buffers.lookup_or_init(&key, &zero);
98+
(*val) += 1;
99+
100+
return 0;
101+
}
102+
"""
103+
104+
105+
# signal handler
106+
def signal_ignore(sig, frame):
107+
print()
108+
109+
110+
def attach(bpf, args):
111+
binary_path = args.path
112+
pid = args.pid
113+
114+
bpf.attach_uprobe(
115+
name=binary_path,
116+
sym="BufTableLookup",
117+
fn_name="probe_buf_table_lookup",
118+
pid=pid)
119+
120+
121+
def pre_process(bpf_text, args):
122+
bpf_text = utils.replace_namespace(bpf_text, args)
123+
return bpf_text
124+
125+
126+
def output(bpf, fmt="plain"):
127+
if fmt == "plain":
128+
print()
129+
counts = {}
130+
131+
total = 0
132+
133+
for (k, v) in bpf.get_table('buffers').items():
134+
name = k.name.decode("ascii")
135+
if not name.startswith("postgres"):
136+
return
137+
138+
total += v.value
139+
counts[v.value] = counts.get(v.value, 0) + 1
140+
141+
if not counts:
142+
return
143+
144+
rings = [
145+
("0-5",
146+
utils.size(sum([
147+
v for k, v in counts.items()
148+
if 0 < k < 5
149+
] * 8192))),
150+
("5-50",
151+
utils.size(sum([
152+
v for k, v in counts.items()
153+
if 5 < k < 50
154+
] * 8192))),
155+
("50-600",
156+
utils.size(sum([
157+
v for k, v in counts.items()
158+
if 50 < k < 600
159+
] * 8192))),
160+
(">600",
161+
utils.size(sum([
162+
v for k, v in counts.items()
163+
if 600 < k
164+
] * 8192))),
165+
]
166+
167+
for range_limits, value in rings:
168+
print("{}:\t{}".format(range_limits, value))
169+
170+
print("Total access: {}".format(total))
171+
bpf.get_table('buffers').clear()
172+
173+
174+
class Data(ct.Structure):
175+
_fields_ = [("spcNode", ct.c_int),
176+
("dbNode", ct.c_int),
177+
("relNode", ct.c_int),
178+
("blockNum", ct.c_int),
179+
("namespace", ct.c_ulonglong),
180+
("name", ct.c_char * 16)]
181+
182+
183+
def run(args):
184+
print("Attaching...")
185+
debug = 4 if args.debug else 0
186+
bpf = BPF(text=pre_process(text, args), debug=debug)
187+
attach(bpf, args)
188+
exiting = False
189+
190+
def print_event(cpu, data, size):
191+
event = ct.cast(data, ct.POINTER(Data)).contents
192+
name = event.name.decode("ascii")
193+
if not name.startswith("postgres"):
194+
return
195+
print("Event: name {} spcNode {} dbNode {} relNode {} blockNum {}".format(
196+
name, event.spcNode, event.dbNode, event.relNode, event.blockNum))
197+
198+
if args.debug:
199+
bpf["events"].open_perf_buffer(print_event)
200+
201+
print("Listening...")
202+
while True:
203+
try:
204+
sleep(args.interval)
205+
output(bpf)
206+
207+
if args.debug:
208+
bpf.perf_buffer_poll()
209+
except KeyboardInterrupt:
210+
exiting = True
211+
# as cleanup can take many seconds, trap Ctrl-C:
212+
signal.signal(signal.SIGINT, signal_ignore)
213+
214+
if exiting:
215+
print()
216+
print("Detaching...")
217+
print()
218+
break
219+
220+
output(bpf)
221+
222+
223+
def parse_args():
224+
parser = argparse.ArgumentParser(
225+
description="Summarize cache references & misses by postgres backend",
226+
formatter_class=argparse.RawDescriptionHelpFormatter)
227+
parser.add_argument("path", type=str, help="path to PostgreSQL binary")
228+
parser.add_argument(
229+
"-p", "--pid", type=int, default=-1,
230+
help="trace this PID only")
231+
parser.add_argument(
232+
"-c", "--container", type=str,
233+
help="trace this container only")
234+
parser.add_argument(
235+
"-n", "--namespace", type=int,
236+
help="trace this namespace only")
237+
parser.add_argument(
238+
"-i", "--interval", type=int, default=5,
239+
help="after how many seconds output the result")
240+
parser.add_argument(
241+
"-d", "--debug", action='store_true', default=False,
242+
help="debug mode")
243+
244+
return parser.parse_args()
245+
246+
247+
if __name__ == "__main__":
248+
run(parse_args())

0 commit comments

Comments
 (0)