use bcc::perf_event::{Event, HardwareEvent}; use bcc::BccError; use bcc::PerfEvent; use bcc::BPF; use clap::{App, Arg}; use core::sync::atomic::{AtomicBool, Ordering}; use std::collections::HashMap; use std::sync::Arc; use std::{mem, ptr, str, thread, time}; // Summarize cache reference and cache misses // // Based on https://github.com/iovisor/bcc/blob/master/tools/llcstat.py const DEFAULT_SAMPLE_PERIOD: u64 = 100; // Events (Aka every 100 events) const DEFAULT_DURATION: u64 = 10; // Seconds #[repr(C)] struct key_t { cpu: i32, pid: i32, name: [u8; 16], } fn do_main(runnable: Arc) -> Result<(), BccError> { let matches = App::new("cpudist") .arg( Arg::with_name("sample_period") .long("sample_period") .short("c") .help("Sample one in this many number of cache reference / miss events") .takes_value(true), ) .arg( Arg::with_name("duration") .long("duration") .short("d") .help("Duration in seconds to run") .takes_value(true), ) .get_matches(); let sample_period: u64 = matches .value_of("sample_period") .map(|v| v.parse().expect("Invalid sample period")) .unwrap_or(DEFAULT_SAMPLE_PERIOD); let duration: u64 = matches .value_of("duration") .map(|v| v.parse().expect("Invalid duration")) .unwrap_or(DEFAULT_DURATION); let code = include_str!("llcstat.c").to_string(); let mut bpf = BPF::new(&code)?; PerfEvent::new() .handler("on_cache_miss") .event(Event::Hardware(HardwareEvent::CacheMisses)) .sample_period(Some(sample_period)) .attach(&mut bpf)?; PerfEvent::new() .handler("on_cache_ref") .event(Event::Hardware(HardwareEvent::CacheReferences)) .sample_period(Some(sample_period)) .attach(&mut bpf)?; println!("Running for {} seconds", duration); let mut elapsed = 0; while runnable.load(Ordering::SeqCst) { if elapsed == duration { break; } thread::sleep(time::Duration::new(1, 0)); elapsed += 1; } // Count misses let mut miss_table = bpf.table("miss_count")?; let miss_map = to_map(&mut miss_table); let mut ref_table = bpf.table("ref_count")?; let ref_map = to_map(&mut ref_table); let mut total_hit = 0; let mut total_miss = 0; println!( "{:<-8} {:<-16} {:<-4} {:>-12} {:>-12} {:>6}", "PID", "NAME", "CPU", "REFERENCE", "MISS", "HIT%" ); for (key, value) in ref_map.iter() { let miss = miss_map.get(key).unwrap_or(&0); let hit = if value > miss { value - miss } else { 0 }; total_hit += hit; total_miss += miss; println!( "{:<-8} {:<-16} {:<-4} {:>-12} {:>-12} {:>6}%", key.1, // PID key.2, // NAME key.0, // CPU value, miss, hit as f64 / (*value) as f64 * 100.0 ); } println!("Total hit: {}\nTotal miss: {}", total_hit, total_miss); Ok(()) } fn to_map(table: &mut bcc::table::Table) -> HashMap<(i32, i32, String), u64> { let mut map = HashMap::new(); for entry in table.iter() { let key = parse_struct(&entry.key); let value = parse_u64(entry.value); let name = str::from_utf8(&key.name).unwrap_or("").to_string(); map.insert((key.cpu, key.pid, name), value); } map } fn parse_u64(x: Vec) -> u64 { let mut v = [0_u8; 8]; for i in 0..8 { v[i] = *x.get(i).unwrap_or(&0); } unsafe { mem::transmute(v) } } fn parse_struct(x: &[u8]) -> key_t { unsafe { ptr::read_unaligned(x.as_ptr() as *const key_t) } } 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); } }