docs: Fix user-guide/bpf-structs

This commit is contained in:
Pragyansh Chaturvedi
2026-01-28 04:12:35 +05:30
parent a31ef3997a
commit 8bfd998863

View File

@ -37,7 +37,7 @@ class Numbers:
short_int: c_int16 # -32768 to 32767 short_int: c_int16 # -32768 to 32767
int_val: c_int32 # -2^31 to 2^31-1 int_val: c_int32 # -2^31 to 2^31-1
long_int: c_int64 # -2^63 to 2^63-1 long_int: c_int64 # -2^63 to 2^63-1
byte: c_uint8 # 0 to 255 byte: c_uint8 # 0 to 255
word: c_uint16 # 0 to 65535 word: c_uint16 # 0 to 65535
dword: c_uint32 # 0 to 2^32-1 dword: c_uint32 # 0 to 2^32-1
@ -115,22 +115,21 @@ class Event:
def capture_event(ctx: c_void_p) -> c_int64: def capture_event(ctx: c_void_p) -> c_int64:
# Create an instance # Create an instance
event = Event() event = Event()
# Set fields # Set fields
event.timestamp = ktime() event.timestamp = ktime()
event.pid = pid() event.pid = pid()
# Note: comm() requires a buffer parameter to fill comm(event.comm) # Fills event.comm with process name
# comm(event.comm) # Fills event.comm with process name
# Use the struct # Use the struct
print(f"Process with PID {event.pid}") print(f"Process with PID {event.pid}")
return c_int64(0) return 0
``` ```
### As Map Values ### As Map Keys and Values
Use structs as values in maps for complex state storage: Use structs as keys and values in maps for complex state storage:
```python ```python
from pythonbpf import bpf, struct, map, section from pythonbpf import bpf, struct, map, section
@ -157,10 +156,10 @@ def stats() -> HashMap:
@section("tracepoint/syscalls/sys_enter_read") @section("tracepoint/syscalls/sys_enter_read")
def track_syscalls(ctx: c_void_p) -> c_int64: def track_syscalls(ctx: c_void_p) -> c_int64:
process_id = pid() process_id = pid()
# Lookup existing stats # Lookup existing stats
s = stats.lookup(process_id) s = stats.lookup(process_id)
if s: if s:
# Update existing stats # Update existing stats
s.syscall_count = s.syscall_count + 1 s.syscall_count = s.syscall_count + 1
@ -168,12 +167,12 @@ def track_syscalls(ctx: c_void_p) -> c_int64:
else: else:
# Create new stats # Create new stats
new_stats = ProcessStats() new_stats = ProcessStats()
new_stats.syscall_count = c_uint64(1) new_stats.syscall_count = 1
new_stats.total_time = c_uint64(0) new_stats.total_time = 0
new_stats.max_latency = c_uint64(0) new_stats.max_latency = 0
stats.update(process_id, new_stats) stats.update(process_id, new_stats)
return c_int64(0) return 0
``` ```
### With Perf Events ### With Perf Events
@ -205,19 +204,16 @@ def trace_fork(ctx: c_void_p) -> c_int64:
event = ProcessEvent() event = ProcessEvent()
event.timestamp = ktime() event.timestamp = ktime()
event.pid = pid() event.pid = pid()
# Note: comm() requires a buffer parameter comm(event.comm) # Fills event.comm with process name
# comm(event.comm) # Fills event.comm with process name
# Send to userspace # Send to userspace
events.output(event) events.output(event)
return c_int64(0) return 0
``` ```
### With Ring Buffers ### With Ring Buffers
Ring buffers provide efficient event delivery:
```python ```python
from pythonbpf import bpf, struct, map, section from pythonbpf import bpf, struct, map, section
from pythonbpf.maps import RingBuffer from pythonbpf.maps import RingBuffer
@ -240,11 +236,10 @@ def trace_open(ctx: c_void_p) -> c_int64:
event = FileEvent() event = FileEvent()
event.timestamp = ktime() event.timestamp = ktime()
event.pid = pid() event.pid = pid()
# event.filename would be populated from ctx
events.output(event) events.output(event)
return c_int64(0) return 0
``` ```
## Field Access and Modification ## Field Access and Modification
@ -267,31 +262,7 @@ Assign values to fields:
event = Event() event = Event()
event.timestamp = ktime() event.timestamp = ktime()
event.pid = pid() event.pid = pid()
# Note: comm() requires a buffer parameter comm(event.comm)
# comm(event.comm) # Fills event.comm with process name
```
### String Fields
String fields have special handling:
```python
@bpf
@struct
class Message:
text: str(64)
@bpf
def example(ctx: c_void_p) -> c_int64:
msg = Message()
# Assign string value
msg.text = "Hello from BPF"
# Use helper to get process name (requires buffer)
# comm(msg.text) # Fills msg.text with process name
return c_int64(0)
``` ```
## StructType Class ## StructType Class
@ -344,10 +315,9 @@ def capture_packets(ctx: c_void_p) -> c_int64:
pkt = PacketEvent() pkt = PacketEvent()
pkt.timestamp = ktime() pkt.timestamp = ktime()
# Parse packet data from ctx... # Parse packet data from ctx...
packets.output(pkt) packets.output(pkt)
# XDP_PASS
return XDP_PASS return XDP_PASS
``` ```
@ -377,121 +347,26 @@ def process_info() -> HashMap:
@section("tracepoint/sched/sched_process_fork") @section("tracepoint/sched/sched_process_fork")
def track_fork(ctx: c_void_p) -> c_int64: def track_fork(ctx: c_void_p) -> c_int64:
process_id = pid() process_id = pid()
info = ProcessLifecycle() info = ProcessLifecycle()
info.pid = process_id info.pid = process_id
info.start_time = ktime() info.start_time = ktime()
# Note: comm() requires a buffer parameter
# comm(info.comm) # Fills info.comm with process name
process_info.update(process_id, info) process_info.update(process_id, info)
return c_int64(0) return 0
@bpf @bpf
@section("tracepoint/sched/sched_process_exit") @section("tracepoint/sched/sched_process_exit")
def track_exit(ctx: c_void_p) -> c_int64: def track_exit(ctx: c_void_p) -> c_int64:
process_id = pid() process_id = pid()
info = process_info.lookup(process_id) info = process_info.lookup(process_id)
if info: if info:
info.exit_time = ktime() info.exit_time = ktime()
process_info.update(process_id, info) process_info.update(process_id, info)
return c_int64(0)
```
### Aggregated Statistics return 0
```python
@bpf
@struct
class FileStats:
read_count: c_uint64
write_count: c_uint64
total_bytes_read: c_uint64
total_bytes_written: c_uint64
last_access: c_uint64
@bpf
@map
def file_stats() -> HashMap:
return HashMap(
key=str(256), # Filename as key
value=FileStats,
max_entries=1024
)
```
## Memory Layout
Structs in BPF follow C struct layout rules:
* Fields are laid out in order
* Padding may be added for alignment
* Size is rounded up to alignment
Example:
```python
@bpf
@struct
class Aligned:
a: c_uint8 # 1 byte
# 3 bytes padding
b: c_uint32 # 4 bytes
c: c_uint64 # 8 bytes
# Total: 16 bytes
```
```{tip}
For optimal memory usage, order fields from largest to smallest to minimize padding.
```
## Best Practices
1. **Use descriptive field names** - Makes code self-documenting
2. **Order fields by size** - Reduces padding and memory usage
3. **Use appropriate sizes** - Don't use `c_uint64` when `c_uint32` suffices
4. **Document complex structs** - Add comments explaining field purposes
5. **Keep structs focused** - Each struct should represent one logical entity
6. **Use fixed-size strings** - Always specify string lengths explicitly
## Common Patterns
### Timestamp + Data Pattern
```python
@bpf
@struct
class TimestampedEvent:
timestamp: c_uint64 # Always first for sorting
# ... other fields
```
### Identification Pattern
```python
@bpf
@struct
class Identifiable:
pid: c_uint32
tid: c_uint32
cpu: c_uint32
# ... additional fields
```
### Stats Aggregation Pattern
```python
@bpf
@struct
class Statistics:
count: c_uint64
sum: c_uint64
min: c_uint64
max: c_uint64
avg: c_uint64 # Computed in userspace
``` ```
## Troubleshooting ## Troubleshooting
@ -522,17 +397,8 @@ If you get type errors:
After capturing struct data, read it in Python: After capturing struct data, read it in Python:
```python ```python
import ctypes
from pylibbpf import BpfMap from pylibbpf import BpfMap
# Define matching Python class
class Event(ctypes.Structure):
_fields_ = [
("timestamp", ctypes.c_uint64),
("pid", ctypes.c_uint32),
("comm", ctypes.c_char * 16),
]
# Read from map # Read from map
map_obj = BpfMap(b, stats) map_obj = BpfMap(b, stats)
for key, value_bytes in map_obj.items(): for key, value_bytes in map_obj.items():