diff --git a/BCC-Examples/hello_perf_output.py b/BCC-Examples/hello_perf_output.py index 9de89b9..2bc195d 100644 --- a/BCC-Examples/hello_perf_output.py +++ b/BCC-Examples/hello_perf_output.py @@ -1,14 +1,14 @@ from pythonbpf import bpf, map, struct, section, bpfglobal, BPF from pythonbpf.helper import ktime, pid, comm from pythonbpf.maps import PerfEventArray -from ctypes import c_void_p, c_int64, c_uint64 +from ctypes import c_void_p, c_int64 @bpf @struct class data_t: - pid: c_uint64 - ts: c_uint64 + pid: c_int64 + ts: c_int64 comm: str(16) # type: ignore [valid-type] diff --git a/BCC-Examples/vfsreadlat.py b/BCC-Examples/vfsreadlat.py new file mode 100644 index 0000000..9dce0ea --- /dev/null +++ b/BCC-Examples/vfsreadlat.py @@ -0,0 +1,127 @@ +from pythonbpf import bpf, map, struct, section, bpfglobal, BPF +from pythonbpf.helper import ktime, pid +from pythonbpf.maps import HashMap, PerfEventArray +from ctypes import c_void_p, c_uint64 +import matplotlib.pyplot as plt +import numpy as np + + +@bpf +@struct +class latency_event: + pid: c_uint64 + delta_us: c_uint64 # Latency in microseconds + + +@bpf +@map +def start() -> HashMap: + return HashMap(key=c_uint64, value=c_uint64, max_entries=10240) + + +@bpf +@map +def events() -> PerfEventArray: + return PerfEventArray(key_size=c_uint64, value_size=c_uint64) + + +@bpf +@section("kprobe/vfs_read") +def do_entry(ctx: c_void_p) -> c_uint64: + p, ts = pid(), ktime() + start.update(p, ts) + return 0 # type: ignore [return-value] + + +@bpf +@section("kretprobe/vfs_read") +def do_return(ctx: c_void_p) -> c_uint64: + p = pid() + tsp = start.lookup(p) + + if tsp: + delta_ns = ktime() - tsp + + # Only track if latency > 1 microsecond + if delta_ns > 1000: + evt = latency_event() + evt.pid, evt.delta_us = p, delta_ns // 1000 + events.output(evt) + + start.delete(p) + + return 0 # type: ignore [return-value] + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +# Load BPF +print("Loading BPF program...") +b = BPF() +b.load() +b.attach_all() + +# Collect latencies +latencies = [] + + +def callback(cpu, event): + latencies.append(event.delta_us) + + +b["events"].open_perf_buffer(callback, struct_name="latency_event") + +print("Tracing vfs_read latency... Hit Ctrl-C to end.") + +try: + while True: + b["events"].poll(1000) + if len(latencies) > 0 and len(latencies) % 1000 == 0: + print(f"Collected {len(latencies)} samples...") + +except KeyboardInterrupt: + print(f"Collected {len(latencies)} samples. Generating histogram...") + +# Create histogram with matplotlib +if latencies: + # Use log scale for better visualization + log_latencies = np.log2(latencies) + + plt.figure(figsize=(12, 6)) + + # Plot 1: Linear histogram + plt.subplot(1, 2, 1) + plt.hist(latencies, bins=50, edgecolor="black", alpha=0.7) + plt.xlabel("Latency (microseconds)") + plt.ylabel("Count") + plt.title("VFS Read Latency Distribution (Linear)") + plt.grid(True, alpha=0.3) + + # Plot 2: Log2 histogram (like BCC) + plt.subplot(1, 2, 2) + plt.hist(log_latencies, bins=50, edgecolor="black", alpha=0.7, color="orange") + plt.xlabel("log2(Latency in µs)") + plt.ylabel("Count") + plt.title("VFS Read Latency Distribution (Log2)") + plt.grid(True, alpha=0.3) + + # Add statistics + print("Statistics:") + print(f" Count: {len(latencies)}") + print(f" Min: {min(latencies)} µs") + print(f" Max: {max(latencies)} µs") + print(f" Mean: {np.mean(latencies):.2f} µs") + print(f" Median: {np.median(latencies):.2f} µs") + print(f" P95: {np.percentile(latencies, 95):.2f} µs") + print(f" P99: {np.percentile(latencies, 99):.2f} µs") + + plt.tight_layout() + plt.savefig("vfs_read_latency.png", dpi=150) + print("Histogram saved to vfs_read_latency.png") + plt.show() +else: + print("No samples collected!")