Crate crabgrind

source ·
Expand description

crabgrind allows Rust programs running under Valgrind to interact with the tools and virtualized environment.

Valgrind’s “client request interface” is primarily accessible through a set of C macros in Valgrind’s header files. However, these macros cannot be utilized in languages that lack support for C-preprocessor, such as Rust. To address this, crabgrind wraps “client request interface” macros with C functions and expose this API to Rust programs.

This library is essentially a wrapper. It only adds type conversions and some structure, while all the real things happens inside Valgrind.

Valgrind 3 API coverage

Quickstart

crabgrind does not link against Valgrind but instead reads its header files, which must be accessible during build.

If you have installed Vallgrind using OS-specific package manager, the paths to the headers are likely to be resolved automatically by cc.

In case of manual installation or any missing file error, you can set the path to the Valgrind headers location through the DEP_VALGRIND environment variable. For example:

add dependency Cargo.toml

[dependencies]
crabgrind = "0.1"

use some of the Valgrind’s API

use crabgrind as cg;

fn main() {
    if matches!(cg::run_mode(), cg::RunMode::Native) {
        println!("run me under Valgrind");
    } else {
        cg::println!("Hey, Valgrind!");
    }
}

and run under Valgrind,

using cargo-valgrind:

cargo valgrind run

manually:

cargo build

valgrind ./target/debug/appname

Examples

Valgrind provides VALGRIND_PRINTF_BACKTRACE macro to print the message with the stack-trace attached, crabgrind::print_stacktrace is it’s crabbed wrapper.

use crabgrind as cg;

#[inline(never)]
fn print_trace(){
    let mode = cg::run_mode();
    cg::print_stacktrace!("current mode: {mode:?}");
}

print_trace();
Exclude expensive initialization code from the measurements

One way to do this would be to turn off stats collection at stratup with the --collect-atstart=no callgrind command-line attribute, and enable/disable it from the code with callgrind::toggle_collect

use crabgrind as cg;

// ... some expensive initialization

cg::callgrind::toggle_collect();
// code of interest
cg::callgrind::toggle_collect();

// ... some deinitialization
Run a closure on the real CPU while running under Valgrind

We can run on the real CPU instead of the virtual one using valgrind::non_simd_call, refer to valgrind.h for details on limitations and various ways to crash.

use crabgrind as cg;

let mut state = 0;
cg::valgrind::non_simd_call(|tid| {
    // uncomment following line to see "the 'impossible' happened"
    // println!("tid: {tid}");
    state = tid;
});

println!("tid: {state}");
Save current memory usage snapshot to a file

We’ll use Massif tool and the monitor command interface to run the corresponding Massif command.

use crabgrind as cg;

let heap = String::from("alloca");

if cg::monitor_command("snapshot mem.snapshot").is_ok(){
    println!("snapshot is saved to \"mem.snapshot\"");
}
Dump Callgrind counters on a function basis
use crabgrind as cg;

fn factorial1(num: u128) -> u128 {
    match num {
        0 => 1,
        1 => 1,
        _ => factorial1(num - 1) * num,
    }
}

fn factorial2(num: u128) -> u128 {
    (1..=num).product()
}

cg::callgrind::zero_stats();

let a = factorial1(20);
cg::callgrind::dump_stats("factorial1");

let b = factorial2(20);
cg::callgrind::dump_stats("factorial2");

assert_eq!(a,b);
cg::callgrind::dump_stats(None);

Overhead

from Valgrind docs

The code added to your binary has negligible performance impact: on x86, amd64, ppc32, ppc64 and ARM, the overhead is 6 simple integer instructions and is probably undetectable except in tight loops.

… the code does nothing when not run on Valgrind, so you are not forced to run your program under Valgrind just because you use the macros in this file.

Although your loops should be very tight (like a well-executed dance move) to notice any impact, keep in mind that:

  • Wrapping each macros in a function implies function call overhead regardless of the run mode. This can potentially impact the performance of your Rust program.
  • Functions that return std::result::Result involve branching, which can also have an impact on performance.
  • Functions that take strings as parameters internally convert them to std::ffi::CString, which can introduce additional overhead.

Modules

Macros

  • Prints to the Valgrind’s log.
  • Prints to the Valgrind’s log, with the current stacktrace attached.
  • Prints to the Valgrind’s log, with a newline.

Enums

Functions