diff --git a/BCC-Examples/vfsreadlat_rich.py b/BCC-Examples/vfsreadlat_rich.py new file mode 100644 index 0000000..a8660ac --- /dev/null +++ b/BCC-Examples/vfsreadlat_rich.py @@ -0,0 +1,178 @@ +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 + +from rich.console import Console +from rich.live import Live +from rich.table import Table +from rich.panel import Panel +from rich.layout import Layout +import numpy as np +import threading +import time +from collections import Counter + +# ==================== BPF Setup ==================== + + +@bpf +@struct +class latency_event: + pid: c_uint64 + delta_us: c_uint64 + + +@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 + + 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" + + +console = Console() +console.print("[bold green]Loading BPF program...[/]") + +b = BPF() +b.load() +b.attach_all() + +# ==================== Data Collection ==================== + +all_latencies = [] +histogram_buckets = Counter() # type: ignore [var-annotated] + + +def callback(cpu, event): + all_latencies.append(event.delta_us) + # Create log2 bucket + bucket = int(np.floor(np.log2(event.delta_us + 1))) + histogram_buckets[bucket] += 1 + + +b["events"].open_perf_buffer(callback, struct_name="latency_event") + + +def poll_events(): + while True: + b["events"].poll(100) + + +poll_thread = threading.Thread(target=poll_events, daemon=True) +poll_thread.start() + +# ==================== Live Display ==================== + + +def generate_display(): + layout = Layout() + layout.split_column( + Layout(name="header", size=3), + Layout(name="stats", size=8), + Layout(name="histogram", size=20), + ) + + # Header + layout["header"].update( + Panel("[bold cyan]🔥 VFS Read Latency Monitor[/]", style="bold white on blue") + ) + + # Stats + if len(all_latencies) > 0: + lats = np.array(all_latencies) + stats_table = Table(show_header=False, box=None, padding=(0, 2)) + stats_table.add_column(style="bold cyan") + stats_table.add_column(style="bold yellow") + + stats_table.add_row("📊 Total Samples:", f"{len(lats):,}") + stats_table.add_row("⚡ Mean Latency:", f"{np.mean(lats):.2f} µs") + stats_table.add_row("📉 Min Latency:", f"{np.min(lats):.2f} µs") + stats_table.add_row("📈 Max Latency:", f"{np.max(lats):.2f} µs") + stats_table.add_row("🎯 P95 Latency:", f"{np.percentile(lats, 95):.2f} µs") + stats_table.add_row("🔥 P99 Latency:", f"{np.percentile(lats, 99):.2f} µs") + + layout["stats"].update( + Panel(stats_table, title="Statistics", border_style="green") + ) + else: + layout["stats"].update( + Panel("[yellow]Waiting for data...[/]", border_style="yellow") + ) + + # Histogram + if histogram_buckets: + hist_table = Table(title="Latency Distribution", box=None) + hist_table.add_column("Range", style="cyan", no_wrap=True) + hist_table.add_column("Count", justify="right", style="yellow") + hist_table.add_column("Distribution", style="green") + + max_count = max(histogram_buckets.values()) + + for bucket in sorted(histogram_buckets.keys()): + count = histogram_buckets[bucket] + lower = 2**bucket + upper = 2 ** (bucket + 1) + + # Create bar + bar_width = int((count / max_count) * 40) + bar = "█" * bar_width + + hist_table.add_row( + f"{lower:5d}-{upper:5d} µs", + f"{count:6d}", + f"[green]{bar}[/] {count / len(all_latencies) * 100:.1f}%", + ) + + layout["histogram"].update(Panel(hist_table, border_style="green")) + + return layout + + +try: + with Live(generate_display(), refresh_per_second=2, console=console) as live: + while True: + time.sleep(0.5) + live.update(generate_display()) +except KeyboardInterrupt: + console.print("\n[bold red]Stopping...[/]") + + if all_latencies: + console.print(f"\n[bold green]✅ Collected {len(all_latencies):,} samples[/]")