mirror of
https://github.com/varun-r-mallya/Python-BPF.git
synced 2025-12-31 21:06:25 +00:00
179 lines
4.6 KiB
Python
179 lines
4.6 KiB
Python
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[/]")
|