Created
May 23, 2020 00:04
-
-
Save charles-l/5cd5eaaa54d2eaecb4195cd31ad14f63 to your computer and use it in GitHub Desktop.
An eBPF frame tracer tool written in Python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
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