|
| 1 | +# eBPF Tutorial: Python Stack Profiler |
| 2 | + |
| 3 | +Profile Python applications at the OS level using eBPF to capture native and Python call stacks, helping identify performance bottlenecks in Python programs including data science workloads, web servers, and ML inference. |
| 4 | + |
| 5 | +> The complete source code: <https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/trace/python-stack-profiler> |
| 6 | +
|
| 7 | +## Overview |
| 8 | + |
| 9 | +Python profiling traditionally relies on instrumentation (cProfile) or sampling within the interpreter (py-spy). These approaches have limitations: |
| 10 | +- **cProfile**: High overhead, requires code modification |
| 11 | +- **py-spy**: Samples from userspace, may miss short-lived functions |
| 12 | +- **perf**: Captures native stacks but can't see Python function names |
| 13 | + |
| 14 | +This tutorial shows how to use eBPF to capture both native C stacks AND Python interpreter stacks, giving you complete visibility into where your Python application spends time. |
| 15 | + |
| 16 | +## What You'll Learn |
| 17 | + |
| 18 | +1. How to attach eBPF probes to Python processes |
| 19 | +2. Walking Python interpreter frame structures from kernel space |
| 20 | +3. Extracting Python function names, filenames, and line numbers |
| 21 | +4. Combining native and Python stacks for complete profiling |
| 22 | +5. Generating flamegraphs for Python applications |
| 23 | + |
| 24 | +## Prerequisites |
| 25 | + |
| 26 | +- Linux kernel 5.15+ (for BPF ring buffer support) |
| 27 | +- Python 3.8+ running on your system |
| 28 | +- Root access (for loading eBPF programs) |
| 29 | +- Understanding of stack traces and profiling concepts |
| 30 | + |
| 31 | +## Building and Running |
| 32 | + |
| 33 | +```bash |
| 34 | +make |
| 35 | +sudo ./python-stack |
| 36 | +``` |
| 37 | + |
| 38 | +## How It Works |
| 39 | + |
| 40 | +The profiler samples Python processes at a regular interval (e.g., 49Hz to avoid lock-step with scheduler). For each sample: |
| 41 | + |
| 42 | +1. **Capture native stack**: Use BPF stack helpers to get kernel and userspace stacks |
| 43 | +2. **Identify Python threads**: Check if the process is running Python interpreter |
| 44 | +3. **Walk Python frames**: Read PyFrameObject chain from CPython internals |
| 45 | +4. **Extract symbols**: Get function names, filenames, line numbers from PyCodeObject |
| 46 | +5. **Aggregate data**: Count stack occurrences for flamegraph generation |
| 47 | + |
| 48 | +## Python Internals |
| 49 | + |
| 50 | +CPython's frame structure (simplified): |
| 51 | + |
| 52 | +```c |
| 53 | +struct _frame { |
| 54 | + struct _frame *f_back; // Previous frame |
| 55 | + PyCodeObject *f_code; // Code object |
| 56 | + int f_lineno; // Current line number |
| 57 | +}; |
| 58 | + |
| 59 | +struct PyCodeObject { |
| 60 | + PyObject *co_filename; // Source filename |
| 61 | + PyObject *co_name; // Function name |
| 62 | +}; |
| 63 | +``` |
| 64 | + |
| 65 | +## Example Output |
| 66 | + |
| 67 | +``` |
| 68 | +python-script.py:main;process_data;expensive_function 247 |
| 69 | +python-script.py:main;load_model;torch.load 189 |
| 70 | +python-script.py:main;preprocess;np.array 156 |
| 71 | +``` |
| 72 | + |
| 73 | +Each line shows the stack trace and sample count. |
| 74 | + |
| 75 | +## Use Cases |
| 76 | + |
| 77 | +- **ML/AI workloads**: Profile PyTorch, TensorFlow, NumPy operations |
| 78 | +- **Web servers**: Find bottlenecks in Flask, Django, FastAPI |
| 79 | +- **Data processing**: Optimize pandas, polars operations |
| 80 | +- **General Python**: Any Python application performance analysis |
| 81 | + |
| 82 | +## Next Steps |
| 83 | + |
| 84 | +- Extend to capture GIL contention |
| 85 | +- Add Python object allocation tracking |
| 86 | +- Integrate with other eBPF metrics (CPU, memory) |
| 87 | +- Build flamegraph visualization |
| 88 | + |
| 89 | +## References |
| 90 | + |
| 91 | +- [CPython Internals](https://realpython.com/cpython-source-code-guide/) |
| 92 | +- [Python Frame Objects](https://docs.python.org/3/c-api/frame.html) |
| 93 | +- [eBPF Stack Traces](https://www.brendangregg.com/blog/2016-01-20/ebpf-offcpu-flame-graph.html) |
0 commit comments