Skip to content
This repository has been archived by the owner on Jul 11, 2023. It is now read-only.

Commit

Permalink
Add redbpf-tools
Browse files Browse the repository at this point in the history
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
alessandrod committed Feb 27, 2020
1 parent 1f86fb3 commit 59346be
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 0 deletions.
14 changes: 14 additions & 0 deletions redbpf-tools/Cargo.toml
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"
23 changes: 23 additions & 0 deletions redbpf-tools/build.rs
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);
});
}
24 changes: 24 additions & 0 deletions redbpf-tools/probes/Cargo.toml
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"]
47 changes: 47 additions & 0 deletions redbpf-tools/probes/build.rs
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();
}
3 changes: 3 additions & 0 deletions redbpf-tools/probes/include/bindings.h
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>
1 change: 1 addition & 0 deletions redbpf-tools/probes/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include!(concat!(env!("OUT_DIR"), "/gen_bindings.rs"));
102 changes: 102 additions & 0 deletions redbpf-tools/probes/src/iotop/main.rs
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
}
25 changes: 25 additions & 0 deletions redbpf-tools/probes/src/iotop/mod.rs
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
}
3 changes: 3 additions & 0 deletions redbpf-tools/probes/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#![no_std]
pub mod bindings;
pub mod iotop;
97 changes: 97 additions & 0 deletions redbpf-tools/src/bin/redbpf-iotop.rs
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)
}

0 comments on commit 59346be

Please sign in to comment.