Rust USDT探针实现库usdt-impl的使用,高性能动态追踪与系统级调试工具

Rust USDT探针实现库usdt-impl的使用,高性能动态追踪与系统级调试工具

概述

usdt crate 允许在 Rust 代码中使用静态定义的 DTrace 探针。用户可以用 D 语言或直接在 Rust 代码中编写提供者(provider)定义,然后将提供者的探针编译成触发探针的 Rust 代码。这些探针可以通过 dtrace 命令行工具查看。

使用方式

有三种将 D 探针定义转换为 Rust 代码的机制:

  1. 使用 build.rs 脚本
  2. 使用函数式 procedural macro usdt::dtrace_provider
  3. 使用 attribute macro usdt::provider

三种方式生成的代码相同,但第三种方式比前两种更灵活,支持任何实现了 serde::Serialize 类型的探针参数。

示例代码

1. 使用 build.rs 脚本的示例

首先创建一个 D 脚本 test.d

provider my_provider {
    probe start_work(uint8_t);
    probe stop_work(char*, uint8_t);
};

然后使用 build 脚本:

use usdt::Builder;

fn main() {
    Builder::new("test.d").build().unwrap();
}

这会在 OUT_DIR 目录生成包含探针宏的 Rust 文件 test.rs

使用探针的 Rust 代码示例:

// 使用 usdt crate 的示例,通过 build 脚本生成探针
use std::thread::sleep;
use std::time::Duration;
use usdt::register_probes;

// 包含 build 脚本生成的 Rust 实现
include!(concat!(env!("OUT_DIR"), "/test.rs"));

fn main() {
    let duration = Duration::from_secs(1);
    let mut counter: u8 = 0;

    // 必须调用此函数才能在 DTrace 中注册探针
    register_probes().unwrap();

    loop {
        // 调用接受 u8 的 "start_work" 探针
        my_provider::start_work!(|| (counter));

        // 模拟工作
        sleep(duration);

        // 调用接受 &str 和 u8 的 "stop_work" 探针
        my_provider::stop_work!(|| ("the probe has fired", counter));

        counter = counter.wrapping_add(1);
    }
}

2. 使用 procedural macro 的示例

dtrace_provider!("test.d");

3. 使用 attribute macro 的示例

#[derive(serde::Serialize)]
pub struct Arg {
    val: u8,
    data: Vec<String>,
}

#[usdt::provider]
mod my_provider {
    use crate::Arg;
    fn my_probe(_: &Arg) {}
}

在 DTrace 脚本中可以这样查看数据:

dtrace -n 'my_probe* { printf("%s", json(copyinstr(arg0), "ok.val")); }'

完整示例

下面是一个更完整的使用 attribute macro 的示例:

// 引入必要的依赖
use serde::Serialize;
use usdt::register_probes;

// 定义可序列化的探针参数结构体
#[derive(Serialize)]
struct WorkStatus {
    id: u64,
    progress: f32,
    message: String,
}

// 定义 USDT 提供者模块
#[usdt::provider]
mod work_provider {
    use crate::WorkStatus;
    
    // 定义工作开始探针
    fn work_started(status: &WorkStatus) {}
    
    // 定义工作完成探针
    fn work_completed(status: &WorkStatus, result: i32) {}
}

fn main() {
    // 注册探针(必须调用)
    register_probes().unwrap();
    
    let mut id = 0;
    loop {
        id += 1;
        
        // 创建工作状态
        let status = WorkStatus {
            id,
            progress: 0.0,
            message: "Starting work".to_string(),
        };
        
        // 触发工作开始探针
        work_provider::work_started!(|| (status));
        
        // 模拟工作
        let result = do_work(id);
        
        // 更新工作状态
        let status = WorkStatus {
            id,
            progress: 1.0,
            message: format!("Work completed with result {}", result),
        };
        
        // 触发工作完成探针
        work_provider::work_completed!(|| (status, result));
    }
}

// 模拟工作函数
fn do_work(id: u64) -> i32 {
    // 简单模拟工作逻辑
    (id % 10) as i32
}

注意事项

  1. 必须调用 usdt::register_probes() 才能注册探针
  2. 探针宏使用闭包参数,只有探针启用时才会评估参数
  3. 需要 Rust 1.59 或更高版本(或启用 nightly 的 asm 特性)
  4. 在 macOS 上需要 Rust 1.66 或更高版本(或启用 asm_sym 特性)

这个库为 Rust 提供了强大的动态追踪能力,可以用于性能分析、调试和系统监控等场景。


1 回复

Rust USDT探针实现库usdt-impl使用指南

完整示例Demo

首先展示内容中提供的基础示例:

// 定义探针示例
use usdt::register_probes;

register_probes! {
    my_provider {
        probe1 => (arg1: u8, arg2: *const u8);
        probe2 => (arg1: i32, arg2: i64);
    }
}

// 触发探针示例
fn some_function() {
    let value = 42u8;
    let ptr = &value as *const u8;
    
    my_provider_probe1!(|| (value, ptr));
    
    let x = 10i32;
    let y = 20i64;
    my_provider_probe2!(|| (x, y));
}

下面是一个完整的项目示例,展示如何在实际项目中使用usdt-impl:

项目结构

src/
├── main.rs
├── probes.rs     # 探针定义文件
Cargo.toml

probes.rs - 探针定义文件

use usdt::register_probes;

// 定义应用的所有探针
register_probes! {
    app_tracing {
        // HTTP请求探针
        http_request => (method: *const u8, path: *const u8, status: u16);
        
        // 数据库查询探针
        db_query => (query: *const u8, duration_ms: u64);
        
        // 缓存探针
        cache_event => (key: *const u8, hit: bool);
    }
}

main.rs - 主程序文件

mod probes;

use std::ffi::CString;
use std::thread;
use std::time::{Duration, Instant};

fn mock_http_request() {
    // 模拟HTTP请求处理
    let method = CString::new("GET").unwrap();
    let path = CString::new("/api/users").unwrap();
    
    // 触发HTTP探针
    probes::app_tracing_http_request!(|| (
        method.as_ptr(),
        path.as_ptr(),
        200u16
    ));
    
    thread::sleep(Duration::from_millis(50));
}

fn mock_db_query() {
    // 模拟数据库查询
    let query = CString::new("SELECT * FROM users WHERE id = 42").unwrap();
    let start = Instant::now();
    
    thread::sleep(Duration::from_millis(20));
    
    // 触发数据库探针
    probes::app_tracing_db_query!(|| (
        query.as_ptr(),
        start.elapsed().as_millis() as u64
    ));
}

fn mock_cache_access() {
    // 模拟缓存访问
    let key = CString::new("user:42:profile").unwrap();
    let hit = rand::random();
    
    // 触发缓存探针
    probes::app_tracing_cache_event!(|| (
        key.as_ptr(),
        hit
    ));
}

fn main() {
    println!("USDT探针示例程序启动");
    
    // 模拟应用运行
    for _ in 0..10 {
        mock_http_request();
        mock_db_query();
        mock_cache_access();
    }
    
    println!("程序运行结束");
}

Cargo.toml

[package]
name = "usdt-demo"
version = "0.1.0"
edition = "2021"

[dependencies]
usdt = "0.3"
libc = "0.2"
rand = "0.8"

构建和运行

# 构建
cargo build --features="usdt"

# 使用bpftrace监控探针
sudo bpftrace -e 'usdt::app_tracing:http_request { printf("HTTP请求: %s %s -> %d\n", str(arg0), str(arg1), arg2); }'

# 在另一个终端运行程序
./target/debug/usdt-demo

DTrace脚本示例 (trace.d)

app_tracing*:::http_request
{
    printf("HTTP请求: %s %s -> %d\n", copyinstr(arg0), copyinstr(arg1), arg2);
}

app_tracing*:::db_query
{
    printf("数据库查询: %s 耗时: %dms\n", copyinstr(arg0), arg1);
}

app_tracing*:::cache_event
{
    printf("缓存事件: 键=%s %s\n", copyinstr(arg0), arg1 ? "命中" : "未命中");
}

运行DTrace监控:

sudo dtrace -s trace.d -c ./target/debug/usdt-demo

这个完整示例展示了:

  1. 如何组织项目结构
  2. 如何定义不同类型的探针
  3. 如何在业务代码中触发探针
  4. 如何使用DTrace和bpftrace进行监控
  5. 如何处理字符串参数(使用CString)
  6. 如何测量耗时等常见场景
回到顶部