use bcc::perf_event::PerfMapBuilder; use bcc::BccError; use bcc::{Kprobe, BPF}; use clap::{App, Arg}; use core::sync::atomic::{AtomicBool, Ordering}; use std::ptr; use std::sync::Arc; // A simple tool for tracing block device I/O and print details including issuing PID. // // Based on: https://github.com/iovisor/bcc/blob/master/tools/biosnoop.py #[repr(C)] struct data_t { pid: u32, rwflag: u64, delta: u64, qdelta: u64, sector: u64, len: u64, ts: u64, disk_name: [u8; 32], name: [u8; 16], } fn do_main(runnable: Arc) -> Result<(), BccError> { let matches = App::new("biosnoop") .about("Trace block I/O") .arg( Arg::with_name("queue") .short("Q") .long("queue") .help("include OS queued time"), ) .arg( Arg::with_name("duration") .long("duration") .value_name("Seconds") .help("The total duration to run this tool") .takes_value(true), ) .get_matches(); let duration: Option = matches .value_of("duration") .map(|v| std::time::Duration::new(v.parse().expect("Invalid argument for duration"), 0)); let code = include_str!("biosnoop.c"); let code = if matches.is_present("queue") { code.replace("##QUEUE##", "1") } else { code.replace("##QUEUE##", "0") }; // compile the above BPF code! let mut bpf = BPF::new(&code)?; // attach kprobes Kprobe::new() .handler("trace_pid_start") .function("blk_account_io_start") .attach(&mut bpf)?; Kprobe::new() .handler("trace_req_start") .function("blk_mq_start_request") .attach(&mut bpf)?; Kprobe::new() .handler("trace_req_completion") .function("blk_account_io_completion") .attach(&mut bpf)?; if let Ok(funcs) = bpf.get_kprobe_functions("blk_start_request") { if funcs.len() > 0 { Kprobe::new() .handler("trace_req_start") .function("blk_start_request") .attach(&mut bpf)?; } } // the "events" table is where the "open file" events get sent let table = bpf.table("events")?; let mut perf_map = PerfMapBuilder::new(table, perf_data_callback).build()?; // print a header println!( "{:<-11} {:<-14} {:<-6} {:<-7} {:<-1} {:<-10} {:>-7}", "TIME(s)", "COMM", "PID", "DISK", "T", "SECTOR", "BYTES" ); let start = std::time::Instant::now(); // this `.poll()` loop is what makes our callback get called while runnable.load(Ordering::SeqCst) { perf_map.poll(200); if let Some(d) = duration { if std::time::Instant::now() - start >= d { break; } } } Ok(()) } static mut START_TS: u64 = 0; fn perf_data_callback() -> Box { Box::new(|x| unsafe { let data = parse_struct(x); if START_TS == 0 { START_TS = data.ts; } let rwflag = if data.rwflag == 1 { "W" } else { "R" }; let delta = (data.ts - START_TS) as f64; println!( "{:<-11} {:<-14} {:<-6} {:<-7} {:<-1} {:<-10} {:>-7}", delta / 1000000 as f64, get_string(&data.name), data.pid, get_string(&data.disk_name), rwflag, data.sector, data.len ); }) } fn parse_struct(x: &[u8]) -> data_t { unsafe { ptr::read_unaligned(x.as_ptr() as *const data_t) } } fn get_string(x: &[u8]) -> String { match x.iter().position(|&r| r == 0) { Some(zero_pos) => String::from_utf8_lossy(&x[0..zero_pos]).to_string(), None => String::from_utf8_lossy(x).to_string(), } } fn main() { let runnable = Arc::new(AtomicBool::new(true)); let r = runnable.clone(); ctrlc::set_handler(move || { r.store(false, Ordering::SeqCst); }) .expect("Failed to set handler for SIGINT / SIGTERM"); if let Err(x) = do_main(runnable) { eprintln!("Error: {}", x); std::process::exit(1); } }