Skip to content

Instantly share code, notes, and snippets.

@charles-l
Created May 23, 2020 00:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save charles-l/5cd5eaaa54d2eaecb4195cd31ad14f63 to your computer and use it in GitHub Desktop.
Save charles-l/5cd5eaaa54d2eaecb4195cd31ad14f63 to your computer and use it in GitHub Desktop.
An eBPF frame tracer tool written in Python
'''
Expects probes to be defined in the target process, e.g.:
import stapsdt
provider = stapsdt.Provider('game')
frame_begin_probe = provider.add_probe('frame_begin')
frame_end_probe = provider.add_probe('frame_end')
provider.load()
...
while should_run_main_loop:
frame_begin_probe.fire()
...
frame_end_probe.fire()
Unfortunately there's no way to run BPF without using root. I wanted to create a
GUI frontend for the frame profiler that allows realtime plotting, nice visualization,
etc, but since it runs as root, this doesn't work well (running matplotlib under root
segfaults on my machine -_-).
So... this doesn't seem like it's viable. If I wanted to do system-level
tracing then dump it to a file and analyze later, it might work. But I want
something a bit more light weight for gamedev.
If I were to continue putting more time into this, I'd probably expend on it
by dumping the call stack between frame start/end and aggregating it over time.
For now, I'm going to leave it as it is -- a simple example of getting the time
between two probes.
'''
from bcc import BPF, USDT
import sys
import ctypes as ct
# based on:
# https://www.collabora.com/news-and-blog/blog/2019/05/14/an-ebpf-overview-part-5-tracing-user-processes/
# and the ugc bcc util
bpf = """
#include <uapi/linux/ptrace.h>
struct frame_t {
u64 start;
u64 elapsed;
};
BPF_PERF_OUTPUT(events);
BPF_HASH(entry, u64, u64); // pid -> start time
int trace_frame_begin(struct pt_regs *ctx) {
u64 pid = bpf_get_current_pid_tgid();
u64 start_time = bpf_ktime_get_ns();
entry.update(&pid, &start_time);
return 0;
}
int trace_frame_end(struct pt_regs *ctx) {
u64 end = bpf_ktime_get_ns();
u64 pid = bpf_get_current_pid_tgid();
u64 *start_time = entry.lookup(&pid);
if(!start_time) {
return 0;
}
struct frame_t fr = {0};
fr.start = *start_time;
fr.elapsed = end - *start_time;
events.perf_submit(ctx, &fr, sizeof(fr));
return 0;
}
"""
class FrameEvent(ct.Structure):
_fields_ = [
("start", ct.c_ulonglong),
("elapsed", ct.c_ulonglong),
]
u = USDT(pid=int(sys.argv[1]))
u.enable_probe(probe="frame_begin", fn_name="trace_frame_begin")
u.enable_probe(probe="frame_end", fn_name="trace_frame_end")
b = BPF(text=bpf, usdt_contexts=[u])
def print_event(cpu, data, size):
event = ct.cast(data, ct.POINTER(FrameEvent)).contents
print(event.start, event.elapsed)
b['events'].open_perf_buffer(print_event)
while 1:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
break
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment