mirror of
https://github.com/varun-r-mallya/Python-BPF.git
synced 2025-12-31 21:06:25 +00:00
Compare commits
11 Commits
v0.1.5
...
0a1557e318
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a1557e318 | |||
| c56928bc8a | |||
| dd3fc74d09 | |||
| 4a79f9b9b2 | |||
| b676a5ebb4 | |||
| d7329ad3d7 | |||
| 903654daff | |||
| 263402d137 | |||
| 37d1e1b143 | |||
| edc33733d9 | |||
| 18d62d605a |
33
BCC-Examples/hello_fields.py
Normal file
33
BCC-Examples/hello_fields.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from pythonbpf import bpf, section, bpfglobal, BPF, trace_fields
|
||||||
|
from ctypes import c_void_p, c_int64
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@section("tracepoint/syscalls/sys_enter_clone")
|
||||||
|
def hello_world(ctx: c_void_p) -> c_int64:
|
||||||
|
print("Hello, World!")
|
||||||
|
return 0 # type: ignore [return-value]
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@bpfglobal
|
||||||
|
def LICENSE() -> str:
|
||||||
|
return "GPL"
|
||||||
|
|
||||||
|
|
||||||
|
# compile
|
||||||
|
b = BPF()
|
||||||
|
b.load_and_attach()
|
||||||
|
|
||||||
|
# header
|
||||||
|
print(f"{'TIME(s)':<18} {'COMM':<16} {'PID':<6} {'MESSAGE'}")
|
||||||
|
|
||||||
|
# format output
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
(task, pid, cpu, flags, ts, msg) = trace_fields()
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
exit()
|
||||||
|
print(f"{ts:<18} {task:<16} {pid:<6} {msg}")
|
||||||
21
BCC-Examples/hello_world.py
Normal file
21
BCC-Examples/hello_world.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from pythonbpf import bpf, section, bpfglobal, BPF, trace_pipe
|
||||||
|
from ctypes import c_void_p, c_int64
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@section("tracepoint/syscalls/sys_enter_clone")
|
||||||
|
def hello_world(ctx: c_void_p) -> c_int64:
|
||||||
|
print("Hello, World!")
|
||||||
|
return 0 # type: ignore [return-value]
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@bpfglobal
|
||||||
|
def LICENSE() -> str:
|
||||||
|
return "GPL"
|
||||||
|
|
||||||
|
|
||||||
|
b = BPF()
|
||||||
|
b.load_and_attach()
|
||||||
|
|
||||||
|
trace_pipe()
|
||||||
57
BCC-Examples/sync_count.py
Normal file
57
BCC-Examples/sync_count.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
from pythonbpf import bpf, map, section, bpfglobal, BPF, trace_fields
|
||||||
|
from pythonbpf.helper import ktime
|
||||||
|
from pythonbpf.maps import HashMap
|
||||||
|
|
||||||
|
from ctypes import c_void_p, c_int64
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@map
|
||||||
|
def last() -> HashMap:
|
||||||
|
return HashMap(key=c_int64, value=c_int64, max_entries=2)
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@section("tracepoint/syscalls/sys_enter_sync")
|
||||||
|
def do_trace(ctx: c_void_p) -> c_int64:
|
||||||
|
ts_key, cnt_key = 0, 1
|
||||||
|
tsp, cntp = last.lookup(ts_key), last.lookup(cnt_key)
|
||||||
|
if not cntp:
|
||||||
|
last.update(cnt_key, 0)
|
||||||
|
cntp = last.lookup(cnt_key)
|
||||||
|
if tsp:
|
||||||
|
delta = ktime() - tsp
|
||||||
|
if delta < 1000000000:
|
||||||
|
time_ms = delta // 1000000
|
||||||
|
print(f"{time_ms} {cntp}")
|
||||||
|
last.delete(ts_key)
|
||||||
|
else:
|
||||||
|
last.update(ts_key, ktime())
|
||||||
|
last.update(cnt_key, cntp + 1)
|
||||||
|
return 0 # type: ignore [return-value]
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@bpfglobal
|
||||||
|
def LICENSE() -> str:
|
||||||
|
return "GPL"
|
||||||
|
|
||||||
|
|
||||||
|
# compile
|
||||||
|
b = BPF()
|
||||||
|
b.load_and_attach()
|
||||||
|
|
||||||
|
print("Tracing for quick sync's... Ctrl-C to end")
|
||||||
|
|
||||||
|
# format output
|
||||||
|
start = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
task, pid, cpu, flags, ts, msg = trace_fields()
|
||||||
|
if start == 0:
|
||||||
|
start = ts
|
||||||
|
ts -= start
|
||||||
|
ms, cnt = msg.split()
|
||||||
|
print(f"At time {ts} s: Multiple syncs detected, last {ms} ms ago. Count {cnt}")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
exit()
|
||||||
52
BCC-Examples/sync_timing.py
Normal file
52
BCC-Examples/sync_timing.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from pythonbpf import bpf, map, section, bpfglobal, BPF, trace_fields
|
||||||
|
from pythonbpf.helper import ktime
|
||||||
|
from pythonbpf.maps import HashMap
|
||||||
|
|
||||||
|
from ctypes import c_void_p, c_int64
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@map
|
||||||
|
def last() -> HashMap:
|
||||||
|
return HashMap(key=c_int64, value=c_int64, max_entries=1)
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@section("tracepoint/syscalls/sys_enter_sync")
|
||||||
|
def do_trace(ctx: c_void_p) -> c_int64:
|
||||||
|
key = 0
|
||||||
|
tsp = last.lookup(key)
|
||||||
|
if tsp:
|
||||||
|
delta = ktime() - tsp
|
||||||
|
if delta < 1000000000:
|
||||||
|
time_ms = delta // 1000000
|
||||||
|
print(f"{time_ms}")
|
||||||
|
last.delete(key)
|
||||||
|
else:
|
||||||
|
last.update(key, ktime())
|
||||||
|
return 0 # type: ignore [return-value]
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@bpfglobal
|
||||||
|
def LICENSE() -> str:
|
||||||
|
return "GPL"
|
||||||
|
|
||||||
|
|
||||||
|
# compile
|
||||||
|
b = BPF()
|
||||||
|
b.load_and_attach()
|
||||||
|
|
||||||
|
print("Tracing for quick sync's... Ctrl-C to end")
|
||||||
|
|
||||||
|
# format output
|
||||||
|
start = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
task, pid, cpu, flags, ts, ms = trace_fields()
|
||||||
|
if start == 0:
|
||||||
|
start = ts
|
||||||
|
ts -= start
|
||||||
|
print(f"At time {ts} s: Multiple syncs detected, last {ms} ms ago")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
exit()
|
||||||
20
BCC-Examples/sys_sync.py
Normal file
20
BCC-Examples/sys_sync.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from pythonbpf import bpf, section, bpfglobal, BPF, trace_pipe
|
||||||
|
from ctypes import c_void_p, c_int64
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@section("tracepoint/syscalls/sys_enter_sync")
|
||||||
|
def hello_world(ctx: c_void_p) -> c_int64:
|
||||||
|
print("sys_sync() called")
|
||||||
|
return c_int64(0)
|
||||||
|
|
||||||
|
|
||||||
|
@bpf
|
||||||
|
@bpfglobal
|
||||||
|
def LICENSE() -> str:
|
||||||
|
return "GPL"
|
||||||
|
|
||||||
|
|
||||||
|
BPF().load_and_attach()
|
||||||
|
print("Tracing sys_sync()... Ctrl-C to end.")
|
||||||
|
trace_pipe()
|
||||||
@ -1,5 +1,6 @@
|
|||||||
from .decorators import bpf, map, section, bpfglobal, struct
|
from .decorators import bpf, map, section, bpfglobal, struct
|
||||||
from .codegen import compile_to_ir, compile, BPF
|
from .codegen import compile_to_ir, compile, BPF
|
||||||
|
from .utils import trace_pipe, trace_fields
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"bpf",
|
"bpf",
|
||||||
@ -10,4 +11,6 @@ __all__ = [
|
|||||||
"compile_to_ir",
|
"compile_to_ir",
|
||||||
"compile",
|
"compile",
|
||||||
"BPF",
|
"BPF",
|
||||||
|
"trace_pipe",
|
||||||
|
"trace_fields",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -22,44 +22,60 @@ class LocalSymbol:
|
|||||||
yield self.metadata
|
yield self.metadata
|
||||||
|
|
||||||
|
|
||||||
|
def create_targets_and_rvals(stmt):
|
||||||
|
"""Create lists of targets and right-hand values from an assignment statement."""
|
||||||
|
if isinstance(stmt.targets[0], ast.Tuple):
|
||||||
|
if not isinstance(stmt.value, ast.Tuple):
|
||||||
|
logger.warning("Mismatched multi-target assignment, skipping allocation")
|
||||||
|
return
|
||||||
|
targets, rvals = stmt.targets[0].elts, stmt.value.elts
|
||||||
|
if len(targets) != len(rvals):
|
||||||
|
logger.warning("length of LHS != length of RHS, skipping allocation")
|
||||||
|
return
|
||||||
|
return targets, rvals
|
||||||
|
return stmt.targets, [stmt.value]
|
||||||
|
|
||||||
|
|
||||||
def handle_assign_allocation(builder, stmt, local_sym_tab, structs_sym_tab):
|
def handle_assign_allocation(builder, stmt, local_sym_tab, structs_sym_tab):
|
||||||
"""Handle memory allocation for assignment statements."""
|
"""Handle memory allocation for assignment statements."""
|
||||||
|
|
||||||
# Validate assignment
|
logger.info(f"Handling assignment for allocation: {ast.dump(stmt)}")
|
||||||
if len(stmt.targets) != 1:
|
|
||||||
logger.warning("Multi-target assignment not supported, skipping allocation")
|
|
||||||
return
|
|
||||||
|
|
||||||
target = stmt.targets[0]
|
# NOTE: Support multi-target assignments (e.g.: a, b = 1, 2)
|
||||||
|
targets, rvals = create_targets_and_rvals(stmt)
|
||||||
|
|
||||||
# Skip non-name targets (e.g., struct field assignments)
|
for target, rval in zip(targets, rvals):
|
||||||
if isinstance(target, ast.Attribute):
|
# Skip non-name targets (e.g., struct field assignments)
|
||||||
logger.debug(f"Struct field assignment to {target.attr}, no allocation needed")
|
if isinstance(target, ast.Attribute):
|
||||||
return
|
logger.debug(
|
||||||
|
f"Struct field assignment to {target.attr}, no allocation needed"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
if not isinstance(target, ast.Name):
|
if not isinstance(target, ast.Name):
|
||||||
logger.warning(f"Unsupported assignment target type: {type(target).__name__}")
|
logger.warning(
|
||||||
return
|
f"Unsupported assignment target type: {type(target).__name__}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
var_name = target.id
|
var_name = target.id
|
||||||
rval = stmt.value
|
|
||||||
|
|
||||||
# Skip if already allocated
|
# Skip if already allocated
|
||||||
if var_name in local_sym_tab:
|
if var_name in local_sym_tab:
|
||||||
logger.debug(f"Variable {var_name} already allocated, skipping")
|
logger.debug(f"Variable {var_name} already allocated, skipping")
|
||||||
return
|
continue
|
||||||
|
|
||||||
# Determine type and allocate based on rval
|
# Determine type and allocate based on rval
|
||||||
if isinstance(rval, ast.Call):
|
if isinstance(rval, ast.Call):
|
||||||
_allocate_for_call(builder, var_name, rval, local_sym_tab, structs_sym_tab)
|
_allocate_for_call(builder, var_name, rval, local_sym_tab, structs_sym_tab)
|
||||||
elif isinstance(rval, ast.Constant):
|
elif isinstance(rval, ast.Constant):
|
||||||
_allocate_for_constant(builder, var_name, rval, local_sym_tab)
|
_allocate_for_constant(builder, var_name, rval, local_sym_tab)
|
||||||
elif isinstance(rval, ast.BinOp):
|
elif isinstance(rval, ast.BinOp):
|
||||||
_allocate_for_binop(builder, var_name, local_sym_tab)
|
_allocate_for_binop(builder, var_name, local_sym_tab)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Unsupported assignment value type for {var_name}: {type(rval).__name__}"
|
f"Unsupported assignment value type for {var_name}: {type(rval).__name__}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _allocate_for_call(builder, var_name, rval, local_sym_tab, structs_sym_tab):
|
def _allocate_for_call(builder, var_name, rval, local_sym_tab, structs_sym_tab):
|
||||||
|
|||||||
@ -12,7 +12,11 @@ from pythonbpf.assign_pass import (
|
|||||||
handle_variable_assignment,
|
handle_variable_assignment,
|
||||||
handle_struct_field_assignment,
|
handle_struct_field_assignment,
|
||||||
)
|
)
|
||||||
from pythonbpf.allocation_pass import handle_assign_allocation, allocate_temp_pool
|
from pythonbpf.allocation_pass import (
|
||||||
|
handle_assign_allocation,
|
||||||
|
allocate_temp_pool,
|
||||||
|
create_targets_and_rvals,
|
||||||
|
)
|
||||||
|
|
||||||
from .return_utils import handle_none_return, handle_xdp_return, is_xdp_name
|
from .return_utils import handle_none_return, handle_xdp_return, is_xdp_name
|
||||||
from .function_metadata import get_probe_string, is_global_function, infer_return_type
|
from .function_metadata import get_probe_string, is_global_function, infer_return_type
|
||||||
@ -140,48 +144,43 @@ def handle_assign(
|
|||||||
):
|
):
|
||||||
"""Handle assignment statements in the function body."""
|
"""Handle assignment statements in the function body."""
|
||||||
|
|
||||||
# TODO: Support this later
|
# NOTE: Support multi-target assignments (e.g.: a, b = 1, 2)
|
||||||
# GH #37
|
targets, rvals = create_targets_and_rvals(stmt)
|
||||||
if len(stmt.targets) != 1:
|
|
||||||
logger.error("Multi-target assignment is not supported for now")
|
|
||||||
return
|
|
||||||
|
|
||||||
target = stmt.targets[0]
|
for target, rval in zip(targets, rvals):
|
||||||
rval = stmt.value
|
if isinstance(target, ast.Name):
|
||||||
|
# NOTE: Simple variable assignment case: x = 5
|
||||||
|
var_name = target.id
|
||||||
|
result = handle_variable_assignment(
|
||||||
|
func,
|
||||||
|
module,
|
||||||
|
builder,
|
||||||
|
var_name,
|
||||||
|
rval,
|
||||||
|
local_sym_tab,
|
||||||
|
map_sym_tab,
|
||||||
|
structs_sym_tab,
|
||||||
|
)
|
||||||
|
if not result:
|
||||||
|
logger.error(f"Failed to handle assignment to {var_name}")
|
||||||
|
continue
|
||||||
|
|
||||||
if isinstance(target, ast.Name):
|
if isinstance(target, ast.Attribute):
|
||||||
# NOTE: Simple variable assignment case: x = 5
|
# NOTE: Struct field assignment case: pkt.field = value
|
||||||
var_name = target.id
|
handle_struct_field_assignment(
|
||||||
result = handle_variable_assignment(
|
func,
|
||||||
func,
|
module,
|
||||||
module,
|
builder,
|
||||||
builder,
|
target,
|
||||||
var_name,
|
rval,
|
||||||
rval,
|
local_sym_tab,
|
||||||
local_sym_tab,
|
map_sym_tab,
|
||||||
map_sym_tab,
|
structs_sym_tab,
|
||||||
structs_sym_tab,
|
)
|
||||||
)
|
continue
|
||||||
if not result:
|
|
||||||
logger.error(f"Failed to handle assignment to {var_name}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(target, ast.Attribute):
|
# Unsupported target type
|
||||||
# NOTE: Struct field assignment case: pkt.field = value
|
logger.error(f"Unsupported assignment target: {ast.dump(target)}")
|
||||||
handle_struct_field_assignment(
|
|
||||||
func,
|
|
||||||
module,
|
|
||||||
builder,
|
|
||||||
target,
|
|
||||||
rval,
|
|
||||||
local_sym_tab,
|
|
||||||
map_sym_tab,
|
|
||||||
structs_sym_tab,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Unsupported target type
|
|
||||||
logger.error(f"Unsupported assignment target: {ast.dump(target)}")
|
|
||||||
|
|
||||||
|
|
||||||
def handle_cond(
|
def handle_cond(
|
||||||
|
|||||||
56
pythonbpf/utils.py
Normal file
56
pythonbpf/utils.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def trace_pipe():
|
||||||
|
"""Util to read from the trace pipe."""
|
||||||
|
try:
|
||||||
|
subprocess.run(["cat", "/sys/kernel/tracing/trace_pipe"])
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Tracing stopped.")
|
||||||
|
|
||||||
|
|
||||||
|
def trace_fields():
|
||||||
|
"""Parse one line from trace_pipe into fields."""
|
||||||
|
with open("/sys/kernel/tracing/trace_pipe", "rb", buffering=0) as f:
|
||||||
|
while True:
|
||||||
|
line = f.readline().rstrip()
|
||||||
|
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip lost event lines
|
||||||
|
if line.startswith(b"CPU:"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Parse BCC-style: first 16 bytes = task
|
||||||
|
task = line[:16].lstrip().decode("utf-8")
|
||||||
|
line = line[17:] # Skip past task field and space
|
||||||
|
|
||||||
|
# Find the colon that ends "pid cpu flags timestamp"
|
||||||
|
ts_end = line.find(b":")
|
||||||
|
if ts_end == -1:
|
||||||
|
raise ValueError("Cannot parse trace line")
|
||||||
|
|
||||||
|
# Split "pid [cpu] flags timestamp"
|
||||||
|
try:
|
||||||
|
parts = line[:ts_end].split()
|
||||||
|
if len(parts) < 4:
|
||||||
|
raise ValueError("Not enough fields")
|
||||||
|
|
||||||
|
pid = int(parts[0])
|
||||||
|
cpu = parts[1][1:-1] # Remove brackets from [cpu]
|
||||||
|
cpu = int(cpu)
|
||||||
|
flags = parts[2]
|
||||||
|
ts = float(parts[3])
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
raise ValueError("Cannot parse trace line")
|
||||||
|
|
||||||
|
# Get message: skip ": symbol:" part
|
||||||
|
line = line[ts_end + 1 :] # Skip first ":"
|
||||||
|
sym_end = line.find(b":")
|
||||||
|
if sym_end != -1:
|
||||||
|
msg = line[sym_end + 2 :].decode("utf-8") # Skip ": " after symbol
|
||||||
|
else:
|
||||||
|
msg = line.lstrip().decode("utf-8")
|
||||||
|
|
||||||
|
return (task, pid, cpu, flags, ts, msg)
|
||||||
Reference in New Issue
Block a user