This repository has been archived by the owner on Jul 11, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 134
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
redbpf-tools is going to include a collection of BPF tools written using redbpf. It will serve as an example of how to write and structure BPF programs. The first tool included is redbpf-iotop - a iotop clone that outputs something like: PID COMM D MAJ MIN DISK I/O Kbytes AVGms 3302 node R 8 0 sda 81 1144 0.39 6309 bash R 8 0 sda 42 468 0.40 13391 df R 8 0 sda 2 76 0.48 507 vpnkit-bridge R 8 0 sda 3 40 0.53 13390 sync R 8 0 sda 1 24 0.46 13391 bash R 8 0 sda 1 16 0.92 ...
- Loading branch information
1 parent
1f86fb3
commit 59346be
Showing
10 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
name = "redbpf-tools" | ||
version = "0.1.0" | ||
authors = ["Alessandro Decina <alessandro.d@gmail.com>"] | ||
edition = "2018" | ||
|
||
[build-dependencies] | ||
cargo-bpf = { git = "https://github.com/redsift/redbpf" } | ||
|
||
[dependencies] | ||
probes = {path = "./probes" } | ||
redbpf = { version = "^0.9.11", features = ["build", "load"], path = "../redbpf" } | ||
tokio = { version = "0.2.4", features = ["rt-core", "io-driver", "macros", "signal", "time"] } | ||
futures = "0.3" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
use std::env; | ||
use std::path::{Path, PathBuf}; | ||
|
||
use cargo_bpf_lib as cargo_bpf; | ||
|
||
fn main() { | ||
let cargo = PathBuf::from(env::var("CARGO").unwrap()); | ||
let probes = Path::new("probes"); | ||
cargo_bpf::build( | ||
&cargo, | ||
&probes, | ||
&probes.join("target/release/bpf-programs"), | ||
Vec::new(), | ||
) | ||
.expect("couldn't compile probes"); | ||
|
||
cargo_bpf::probe_files(&probes) | ||
.expect("couldn't list probe files") | ||
.iter() | ||
.for_each(|file| { | ||
println!("cargo:rerun-if-changed={}", file); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
[package] | ||
name = "probes" | ||
version = "0.1.0" | ||
edition = '2018' | ||
|
||
[build-dependencies] | ||
cargo-bpf = { git = "https://github.com/redsift/redbpf" } | ||
|
||
[dependencies] | ||
cty = "0.2" | ||
redbpf-macros = { git = "https://github.com/redsift/redbpf" } | ||
redbpf-probes = { git = "https://github.com/redsift/redbpf" } | ||
|
||
[features] | ||
default = [] | ||
probes = [] | ||
|
||
[lib] | ||
path = "src/lib.rs" | ||
|
||
[[bin]] | ||
name = "iotop" | ||
path = "src/iotop/main.rs" | ||
required-features = ["probes"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
|
||
use std::env; | ||
use std::fs::File; | ||
use std::io::{self, Write}; | ||
use std::path::PathBuf; | ||
|
||
use cargo_bpf_lib::bindgen as bpf_bindgen; | ||
|
||
fn create_module(path: PathBuf, name: &str, bindings: &str) -> io::Result<()> { | ||
let mut file = File::create(path)?; | ||
writeln!( | ||
&mut file, | ||
r" | ||
mod {name} {{ | ||
#![allow(non_camel_case_types)] | ||
#![allow(non_upper_case_globals)] | ||
#![allow(non_snake_case)] | ||
#![allow(unused_unsafe)] | ||
#![allow(clippy::all)] | ||
{bindings} | ||
}} | ||
pub use {name}::*; | ||
", | ||
name = name, | ||
bindings = bindings | ||
) | ||
} | ||
|
||
fn main() { | ||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); | ||
|
||
let mut builder = bpf_bindgen::builder().header("include/bindings.h"); | ||
let types = ["request", "req_opf"]; | ||
|
||
for ty in types.iter() { | ||
builder = builder.whitelist_type(ty); | ||
} | ||
|
||
let mut bindings = builder | ||
.generate() | ||
.expect("failed to generate bindings") | ||
.to_string(); | ||
let accessors = bpf_bindgen::generate_read_accessors(&bindings, &["request", "gendisk"]); | ||
bindings.push_str("use redbpf_probes::helpers::bpf_probe_read;"); | ||
bindings.push_str(&accessors); | ||
create_module(out_dir.join("gen_bindings.rs"), "gen_bindings", &bindings).unwrap(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#define KBUILD_MODNAME "cargo_bpf_bindings" | ||
#include <linux/kconfig.h> | ||
#include <linux/blkdev.h> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include!(concat!(env!("OUT_DIR"), "/gen_bindings.rs")); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
#![no_std] | ||
#![no_main] | ||
use probes::bindings::*; | ||
use probes::iotop::{Counter, CounterKey, Process}; | ||
use redbpf_probes::kprobe::prelude::*; | ||
|
||
const REQ_OP_WRITE: u32 = 1; | ||
|
||
program!(0xFFFFFFFE, "GPL"); | ||
|
||
#[map("start")] | ||
static mut start: HashMap<*const request, u64> = HashMap::with_max_entries(10240); | ||
|
||
#[map("processes")] | ||
static mut processes: HashMap<*const request, Process> = HashMap::with_max_entries(10240); | ||
|
||
#[map("counts")] | ||
static mut counts: HashMap<CounterKey, Counter> = HashMap::with_max_entries(10240); | ||
|
||
#[kprobe] | ||
fn blk_account_io_start(regs: Registers) { | ||
let comm = bpf_get_current_comm(); | ||
let pid = bpf_get_current_pid_tgid() >> 32; | ||
let req = regs.parm1() as *const request; | ||
unsafe { processes.set(&req, &Process { pid, comm }) } | ||
} | ||
|
||
fn start_request(regs: Registers) { | ||
let ts = bpf_ktime_get_ns(); | ||
let req = regs.parm1() as *const request; | ||
unsafe { start.set(&req, &ts) } | ||
} | ||
|
||
#[kprobe] | ||
fn blk_start_request(regs: Registers) { | ||
start_request(regs) | ||
} | ||
|
||
#[kprobe] | ||
fn blk_mq_start_request(regs: Registers) { | ||
start_request(regs) | ||
} | ||
|
||
#[kprobe] | ||
fn blk_account_io_completion(regs: Registers) { | ||
let _ = do_complete(regs); | ||
} | ||
|
||
#[inline] | ||
fn do_complete(regs: Registers) -> Option<()> { | ||
let req = regs.parm1() as *const request; | ||
|
||
let start_ts = unsafe { start.get(&req)? }; | ||
let delta_us = (bpf_ktime_get_ns() - start_ts) / 1000u64; | ||
|
||
let request = unsafe { &*req }; | ||
let rq_disk = unsafe { &*request.rq_disk()? }; | ||
let major = rq_disk.major()?; | ||
let minor = rq_disk.first_minor()?; | ||
let write = (request.cmd_flags()? & REQ_OP_WRITE != 0) as u64; | ||
|
||
let unknown_process = Process { | ||
pid: 0, | ||
comm: [0; 16], | ||
}; | ||
let process = match unsafe { processes.get(&req) } { | ||
Some(p) => p, | ||
None => &unknown_process, | ||
}; | ||
let key = CounterKey { | ||
process: process.clone(), | ||
major, | ||
minor, | ||
write, | ||
}; | ||
|
||
let mut counter = unsafe { | ||
match counts.get_mut(&key) { | ||
Some(c) => c, | ||
None => { | ||
let zero = Counter { | ||
bytes: 0, | ||
us: 0, | ||
io: 0, | ||
}; | ||
counts.set(&key, &zero); | ||
counts.get_mut(&key)? | ||
} | ||
} | ||
}; | ||
|
||
counter.bytes += request.__data_len()? as u64; | ||
counter.us += delta_us; | ||
counter.io += 1; | ||
|
||
unsafe { | ||
start.delete(&req); | ||
processes.delete(&req); | ||
} | ||
|
||
None | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
use cty::*; | ||
|
||
#[derive(Clone, Debug)] | ||
#[repr(C)] | ||
pub struct Process { | ||
pub pid: u64, | ||
pub comm: [c_char; 16] | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
#[repr(C)] | ||
pub struct CounterKey { | ||
pub process: Process, | ||
pub major: i32, | ||
pub minor: i32, | ||
pub write: u64, | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
#[repr(C)] | ||
pub struct Counter { | ||
pub bytes: u64, | ||
pub us: u64, | ||
pub io: u64 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#![no_std] | ||
pub mod bindings; | ||
pub mod iotop; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Copyright 2019-2020 Authors of Red Sift | ||
// | ||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or | ||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or | ||
// http://opensource.org/licenses/MIT>, at your option. This file may not be | ||
// copied, modified, or distributed except according to those terms. | ||
use redbpf::{load::Loader, BPFHashMap}; | ||
use std::collections::HashMap; | ||
use std::ffi::CStr; | ||
use std::fs::File; | ||
use std::io::{self, BufRead, BufReader}; | ||
use std::os::raw::c_char; | ||
use std::path::PathBuf; | ||
use std::time::Duration; | ||
use tokio; | ||
use tokio::runtime::Runtime; | ||
use tokio::signal; | ||
use tokio::time::delay_for; | ||
|
||
use probes::iotop::{Counter, CounterKey}; | ||
|
||
fn main() { | ||
let mut runtime = Runtime::new().unwrap(); | ||
let _ = runtime.block_on(async { | ||
let loader = Loader::new() | ||
.load_file( | ||
&PathBuf::from(env!("CARGO_MANIFEST_DIR")) | ||
.join("probes/target/release/bpf-programs/biotop/biotop.elf"), | ||
) | ||
.await | ||
.expect("error loading probe"); | ||
tokio::spawn(async move { | ||
let counts = loader | ||
.module | ||
.maps | ||
.iter() | ||
.find(|m| m.name == "counts") | ||
.unwrap(); | ||
let counts = BPFHashMap::<CounterKey, Counter>::new(counts).unwrap(); | ||
let disks = parse_diskstats().unwrap(); | ||
|
||
loop { | ||
delay_for(Duration::from_millis(1000)).await; | ||
|
||
println!( | ||
"{:6} {:16} {:1} {:3} {:3} {:8} {:>5} {:>7} {:>6}", | ||
"PID", "COMM", "D", "MAJ", "MIN", "DISK", "I/O", "Kbytes", "AVGms" | ||
); | ||
|
||
let mut items: Vec<(CounterKey, Counter)> = counts.iter().collect(); | ||
items.sort_unstable_by(|(_, av), (_, bv)| av.bytes.cmp(&bv.bytes)); | ||
|
||
for (k, v) in items.iter().rev() { | ||
let comm = unsafe { CStr::from_ptr(k.process.comm.as_ptr() as *const c_char) } | ||
.to_string_lossy() | ||
.into_owned(); | ||
|
||
let unknown = String::from("?"); | ||
let disk_name = disks.get(&(k.major, k.minor)).unwrap_or(&unknown); | ||
let avg_ms = v.us as f64 / 1000f64 / v.io as f64; | ||
|
||
println!( | ||
"{:<6} {:16} {:1} {:3} {:3} {:8} {:5} {:7} {:6.2}", | ||
k.process.pid, | ||
comm, | ||
if k.write != 0 { "W" } else { "R" }, | ||
k.major, | ||
k.minor, | ||
disk_name, | ||
v.io, | ||
v.bytes / 1024, | ||
avg_ms | ||
); | ||
} | ||
|
||
println!(""); | ||
} | ||
}); | ||
|
||
signal::ctrl_c().await | ||
}); | ||
} | ||
|
||
fn parse_diskstats() -> io::Result<HashMap<(i32, i32), String>> { | ||
let file = File::open("/proc/diskstats")?; | ||
let reader = BufReader::new(file); | ||
let mut disks = HashMap::new(); | ||
for line in reader.lines() { | ||
let line = line.unwrap(); | ||
let parts: Vec<_> = line.split_ascii_whitespace().collect(); | ||
disks.insert( | ||
(parts[0].parse().unwrap(), parts[1].parse().unwrap()), | ||
parts[2].to_string(), | ||
); | ||
} | ||
Ok(disks) | ||
} |