# 🐍 pyusdt 🐝
A Python profiler using USDT (User-level Statically Defined Tracing) probes for low-overhead performance monitoring.
## Overview
pyusdt instruments Python code execution with USDT probes that can be traced using `bpftrace`. It uses Python's `sys.monitoring` API for efficient function-level tracing.
This tool is particularly designed to enable bpftrace workflows where traces need to span both kernel and userspace, allowing you to correlate Python function calls with kernel events in a single trace session.
**Zero-overhead when not traced**: pyusdt uses dynamic callback registration combined with USDT semaphores. When no tracer is attached, monitoring callbacks are not registered with `sys.monitoring`, resulting in essentially zero performance impact on your Python code. When bpftrace or another tracer attaches, callbacks are automatically enabled within ~100ms.
## Requirements
- Python 3.12+ (for `sys.monitoring` API)
- Linux with USDT support
- bpftrace
## Building
Make sure gcc and Python development headers are installed.
Compile the USDT probe extension:
```bash
make
```
This creates `libpyusdt.so`, a Python C extension module with embedded USDT probes.
## Usage
Run any Python script with USDT monitoring:
```bash
python -m pyusdt <script.py> [args...]
```
Example:
```bash
python -m pyusdt sleep.py
```
### Configuration
Adjust the polling interval for tracer detection (default: 100ms):
```bash
# Check for attached tracers every 50ms
PYUSDT_CHECK_MSEC=50 python -m pyusdt sleep.py
# Check every 500ms (lower overhead, slower tracer detection)
PYUSDT_CHECK_MSEC=500 python -m pyusdt sleep.py
```
## Tracing with bpftrace
Use the included bpftrace script to trace function calls:
```bash
sudo bpftrace sample.bt -c "python -m pyusdt sleep.py"
```
Or attach to a running process:
```bash
# In terminal 1:
python -m pyusdt sleep.py
# In terminal 2:
sudo bpftrace sample.bt -p $(pgrep -f "python -m pyusdt")
```
### Example bpftrace Script
Here's a simple one-liner to trace Python function entries:
```bash
sudo bpftrace -e 'usdt:./libpyusdt.so:pyusdt:PY_START { printf("%s (%s:%d)\n", str(arg0), str(arg1), arg2); }' -c "python -m pyusdt sleep.py"
```
**Note:** When using `pip install pyusdt`, the library path will be different (installed in your Python site-packages). Use `-p <PID>` to attach to a running process instead of specifying the library path, and bpftrace will automatically find the loaded library.
**Available USDT probes:**
- `PY_START` - Function entry: `(function, file, line, offset)`
- `PY_RESUME` - Generator/coroutine resume: `(function, file, line, offset)`
- `PY_RETURN` - Function return: `(function, file, line, offset, retval)`
- `PY_YIELD` - Generator yield: `(function, file, line, offset, yieldval)`
- `CALL` - Function call: `(function, file, line, offset, callable)`
- `LINE` - Line execution: `(function, file, line)`
The included `sample.bt` script traces all 6 probe types with detailed output.
## Testing
Run the test suite:
```bash
make test
```
## How it Works
1. `libpyusdt.so` - Python C extension module with USDT probe definitions and `sys.monitoring` integration
2. `pyusdt/__init__.py` - Minimal Python wrapper that imports the C extension
3. `pyusdt/__main__.py` - Entry point for `python -m pyusdt` execution
4. `sample.bt` - bpftrace script to display traced function calls
5. `usdt.h` - Header-only USDT library from [libbpf/usdt](https://github.com/libbpf/usdt)
When the `pyusdt` module is imported, the C extension starts a background thread that polls USDT semaphores to detect when a tracer (like bpftrace) attaches. Only when a tracer is active does pyusdt register callbacks with Python's `sys.monitoring` API (see [PEP 669](https://peps.python.org/pep-0669/)). When the tracer detaches, callbacks are automatically unregistered.
The following monitoring events are captured and exposed as USDT probes when tracing is active:
- **PY_START** - Function entry
- **PY_RESUME** - Generator/coroutine resumption
- **PY_RETURN** - Function return with return value
- **PY_YIELD** - Generator yield with yielded value
- **CALL** - Function calls
- **LINE** - Line-by-line execution
Each event triggers its corresponding USDT probe with relevant context (function name, filename, line number, and event-specific data).
### Zero-Overhead Design
pyusdt achieves true zero overhead when not being traced through:
1. **USDT Semaphores**: The libbpf/usdt library uses kernel-managed semaphores that are incremented when tracers attach
2. **Dynamic Callback Registration**: A background thread polls semaphores every 100ms and only registers `sys.monitoring` callbacks when needed
3. **Automatic Enable/Disable**: When bpftrace attaches, monitoring activates within ~100ms; when it detaches, monitoring stops immediately
This means you can leave pyusdt enabled in production with virtually no performance impact until you need to trace.
## References
- [PEP 669 - Low Impact Monitoring for CPython](https://peps.python.org/pep-0669/)
- [sys.monitoring documentation](https://docs.python.org/3/library/sys.monitoring.html)
- [libbpf/usdt - Header-only USDT library](https://github.com/libbpf/usdt)
Raw data
{
"_id": null,
"home_page": null,
"name": "pyusdt",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.12",
"maintainer_email": null,
"keywords": "profiling, tracing, usdt, bpftrace, ebpf, monitoring",
"author": "Nathan Scott",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/b3/94/b5605a954d5369b8f3cd1f908b35bc8a4b6424807da2a9f388da74695492/pyusdt-0.1.1.tar.gz",
"platform": null,
"description": "# \ud83d\udc0d pyusdt \ud83d\udc1d\n\nA Python profiler using USDT (User-level Statically Defined Tracing) probes for low-overhead performance monitoring.\n\n## Overview\n\npyusdt instruments Python code execution with USDT probes that can be traced using `bpftrace`. It uses Python's `sys.monitoring` API for efficient function-level tracing.\n\nThis tool is particularly designed to enable bpftrace workflows where traces need to span both kernel and userspace, allowing you to correlate Python function calls with kernel events in a single trace session.\n\n**Zero-overhead when not traced**: pyusdt uses dynamic callback registration combined with USDT semaphores. When no tracer is attached, monitoring callbacks are not registered with `sys.monitoring`, resulting in essentially zero performance impact on your Python code. When bpftrace or another tracer attaches, callbacks are automatically enabled within ~100ms.\n\n## Requirements\n\n- Python 3.12+ (for `sys.monitoring` API)\n- Linux with USDT support\n- bpftrace\n\n## Building\n\nMake sure gcc and Python development headers are installed.\nCompile the USDT probe extension:\n\n```bash\nmake\n```\n\nThis creates `libpyusdt.so`, a Python C extension module with embedded USDT probes.\n\n## Usage\n\nRun any Python script with USDT monitoring:\n\n```bash\npython -m pyusdt <script.py> [args...]\n```\n\nExample:\n\n```bash\npython -m pyusdt sleep.py\n```\n\n### Configuration\n\nAdjust the polling interval for tracer detection (default: 100ms):\n\n```bash\n# Check for attached tracers every 50ms\nPYUSDT_CHECK_MSEC=50 python -m pyusdt sleep.py\n\n# Check every 500ms (lower overhead, slower tracer detection)\nPYUSDT_CHECK_MSEC=500 python -m pyusdt sleep.py\n```\n\n## Tracing with bpftrace\n\nUse the included bpftrace script to trace function calls:\n\n```bash\nsudo bpftrace sample.bt -c \"python -m pyusdt sleep.py\"\n```\n\nOr attach to a running process:\n\n```bash\n# In terminal 1:\npython -m pyusdt sleep.py\n\n# In terminal 2:\nsudo bpftrace sample.bt -p $(pgrep -f \"python -m pyusdt\")\n```\n\n### Example bpftrace Script\n\nHere's a simple one-liner to trace Python function entries:\n\n```bash\nsudo bpftrace -e 'usdt:./libpyusdt.so:pyusdt:PY_START { printf(\"%s (%s:%d)\\n\", str(arg0), str(arg1), arg2); }' -c \"python -m pyusdt sleep.py\"\n```\n\n**Note:** When using `pip install pyusdt`, the library path will be different (installed in your Python site-packages). Use `-p <PID>` to attach to a running process instead of specifying the library path, and bpftrace will automatically find the loaded library.\n\n**Available USDT probes:**\n- `PY_START` - Function entry: `(function, file, line, offset)`\n- `PY_RESUME` - Generator/coroutine resume: `(function, file, line, offset)`\n- `PY_RETURN` - Function return: `(function, file, line, offset, retval)`\n- `PY_YIELD` - Generator yield: `(function, file, line, offset, yieldval)`\n- `CALL` - Function call: `(function, file, line, offset, callable)`\n- `LINE` - Line execution: `(function, file, line)`\n\nThe included `sample.bt` script traces all 6 probe types with detailed output.\n\n## Testing\n\nRun the test suite:\n\n```bash\nmake test\n```\n\n## How it Works\n\n1. `libpyusdt.so` - Python C extension module with USDT probe definitions and `sys.monitoring` integration\n2. `pyusdt/__init__.py` - Minimal Python wrapper that imports the C extension\n3. `pyusdt/__main__.py` - Entry point for `python -m pyusdt` execution\n4. `sample.bt` - bpftrace script to display traced function calls\n5. `usdt.h` - Header-only USDT library from [libbpf/usdt](https://github.com/libbpf/usdt)\n\nWhen the `pyusdt` module is imported, the C extension starts a background thread that polls USDT semaphores to detect when a tracer (like bpftrace) attaches. Only when a tracer is active does pyusdt register callbacks with Python's `sys.monitoring` API (see [PEP 669](https://peps.python.org/pep-0669/)). When the tracer detaches, callbacks are automatically unregistered.\n\nThe following monitoring events are captured and exposed as USDT probes when tracing is active:\n\n- **PY_START** - Function entry\n- **PY_RESUME** - Generator/coroutine resumption\n- **PY_RETURN** - Function return with return value\n- **PY_YIELD** - Generator yield with yielded value\n- **CALL** - Function calls\n- **LINE** - Line-by-line execution\n\nEach event triggers its corresponding USDT probe with relevant context (function name, filename, line number, and event-specific data).\n\n### Zero-Overhead Design\n\npyusdt achieves true zero overhead when not being traced through:\n\n1. **USDT Semaphores**: The libbpf/usdt library uses kernel-managed semaphores that are incremented when tracers attach\n2. **Dynamic Callback Registration**: A background thread polls semaphores every 100ms and only registers `sys.monitoring` callbacks when needed\n3. **Automatic Enable/Disable**: When bpftrace attaches, monitoring activates within ~100ms; when it detaches, monitoring stops immediately\n\nThis means you can leave pyusdt enabled in production with virtually no performance impact until you need to trace.\n\n## References\n\n- [PEP 669 - Low Impact Monitoring for CPython](https://peps.python.org/pep-0669/)\n- [sys.monitoring documentation](https://docs.python.org/3/library/sys.monitoring.html)\n- [libbpf/usdt - Header-only USDT library](https://github.com/libbpf/usdt)\n",
"bugtrack_url": null,
"license": null,
"summary": "Python profiler using USDT probes for low-overhead performance monitoring with bpftrace",
"version": "0.1.1",
"project_urls": {
"Homepage": "https://github.com/natoscott/pyusdt",
"Issues": "https://github.com/natoscott/pyusdt/issues",
"Repository": "https://github.com/natoscott/pyusdt"
},
"split_keywords": [
"profiling",
" tracing",
" usdt",
" bpftrace",
" ebpf",
" monitoring"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "cdf86ca0d3d4f70c42eeac6983a02560cbb596bd9a1320e209daa847aea1090b",
"md5": "2ce8c1b7619dc129b85ea49c53d452e5",
"sha256": "864b2f25cb6ab6cec86a74364312d648f5b32de046648098cb2071a72854aeda"
},
"downloads": -1,
"filename": "pyusdt-0.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"has_sig": false,
"md5_digest": "2ce8c1b7619dc129b85ea49c53d452e5",
"packagetype": "bdist_wheel",
"python_version": "cp312",
"requires_python": ">=3.12",
"size": 30248,
"upload_time": "2025-10-20T06:33:57",
"upload_time_iso_8601": "2025-10-20T06:33:57.509364Z",
"url": "https://files.pythonhosted.org/packages/cd/f8/6ca0d3d4f70c42eeac6983a02560cbb596bd9a1320e209daa847aea1090b/pyusdt-0.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "c8f0ec8c6d04b0e76d821c37c71bebb7c6a22cccd012aaa7193a76bace8d30fa",
"md5": "8ffe450b6713ba21bf10f43416520c71",
"sha256": "b76b529ba79a9dc7adf21bb83d2f1c2998b4923d1244517058ecdd3411c97ca9"
},
"downloads": -1,
"filename": "pyusdt-0.1.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"has_sig": false,
"md5_digest": "8ffe450b6713ba21bf10f43416520c71",
"packagetype": "bdist_wheel",
"python_version": "cp312",
"requires_python": ">=3.12",
"size": 29419,
"upload_time": "2025-10-20T06:33:58",
"upload_time_iso_8601": "2025-10-20T06:33:58.827750Z",
"url": "https://files.pythonhosted.org/packages/c8/f0/ec8c6d04b0e76d821c37c71bebb7c6a22cccd012aaa7193a76bace8d30fa/pyusdt-0.1.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "5c6076d09eea26526ffe9d2af810e3f93c94d8d0c288ac53ac720a7181cd60b7",
"md5": "bb2a61c6dd14611e18fb4daf909c6944",
"sha256": "41fab4768738138f1e17387ab1d85e811a3e769fd0188b258388be3736f4efe5"
},
"downloads": -1,
"filename": "pyusdt-0.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"has_sig": false,
"md5_digest": "bb2a61c6dd14611e18fb4daf909c6944",
"packagetype": "bdist_wheel",
"python_version": "cp313",
"requires_python": ">=3.12",
"size": 30233,
"upload_time": "2025-10-20T06:34:00",
"upload_time_iso_8601": "2025-10-20T06:34:00.002171Z",
"url": "https://files.pythonhosted.org/packages/5c/60/76d09eea26526ffe9d2af810e3f93c94d8d0c288ac53ac720a7181cd60b7/pyusdt-0.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "718e16d7d25c415c8c5c722ce4161d8e460e1ea8d234eb28daf40a6940a4beaa",
"md5": "eae7ed97d02934d92ab66ef90634527b",
"sha256": "e547232ddb1d807f3fc123e9aa7c139c67baaca1fb57247b55e54e950b65afb8"
},
"downloads": -1,
"filename": "pyusdt-0.1.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"has_sig": false,
"md5_digest": "eae7ed97d02934d92ab66ef90634527b",
"packagetype": "bdist_wheel",
"python_version": "cp313",
"requires_python": ">=3.12",
"size": 29371,
"upload_time": "2025-10-20T06:34:01",
"upload_time_iso_8601": "2025-10-20T06:34:01.282042Z",
"url": "https://files.pythonhosted.org/packages/71/8e/16d7d25c415c8c5c722ce4161d8e460e1ea8d234eb28daf40a6940a4beaa/pyusdt-0.1.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "b394b5605a954d5369b8f3cd1f908b35bc8a4b6424807da2a9f388da74695492",
"md5": "dd7dbbc1127b9d78da6cb17da7d7f042",
"sha256": "9a4d2b025898add727bf5e8ceef670e5283964f47b0626994fcac02d1ce8ef0f"
},
"downloads": -1,
"filename": "pyusdt-0.1.1.tar.gz",
"has_sig": false,
"md5_digest": "dd7dbbc1127b9d78da6cb17da7d7f042",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.12",
"size": 24893,
"upload_time": "2025-10-20T06:34:02",
"upload_time_iso_8601": "2025-10-20T06:34:02.231754Z",
"url": "https://files.pythonhosted.org/packages/b3/94/b5605a954d5369b8f3cd1f908b35bc8a4b6424807da2a9f388da74695492/pyusdt-0.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-20 06:34:02",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "natoscott",
"github_project": "pyusdt",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pyusdt"
}