dial9 0.5.0

Low-overhead async runtime telemetry: event recording, Tokio integration, CPU/memory profiling, and a trace viewer CLI
Documentation
//! Example: emitting custom events into a dial9 trace.
//!
//! Shows two patterns:
//! 1. **Simple** — `#[derive(TraceEvent)]` struct passed directly to `record_event`
//! 2. **Advanced** — manual `Encodable` impl with string interning for repeated values
//!
//! Run with:
//! ```sh
//! cargo run --example custom_events --features analysis
//! ```

use dial9::core::{Encodable, ThreadLocalEncoder, clock_monotonic_ns};
use dial9::format::{InternedString, TraceEvent};
use dial9::{Dial9HandleTokioExt, DiskBuffer, TokioAttachOptions, recorder};
use std::time::Duration;

// ── Simple: derive-only, no interning ───────────────────────────────────────

/// A custom event with primitive and optional fields. The blanket `Encodable` impl
/// handles encoding automatically — just pass it to `record_event`.
/// Optional fields use 1 byte on the wire when absent (None).
#[derive(TraceEvent)]
struct RequestCompleted {
    #[traceevent(timestamp)]
    timestamp_ns: u64,
    status_code: u32,
    /// The annotations format this as a duration and chart observed latencies
    /// directly, without asking for a metric interpretation.
    #[traceevent(unit = "us", kind = "gauge")]
    latency_us: u64,
    /// Only present for failed requests.
    error_message: Option<String>,
}

// ── Advanced: manual Encodable with string interning ────────────────────────

/// Application-level event with a string field we want to intern.
struct HttpRequest {
    timestamp_ns: u64,
    method: String,
    status: u32,
}

/// Wire-format struct with the interned string handle.
#[derive(TraceEvent)]
struct HttpRequestWire {
    #[traceevent(timestamp)]
    timestamp_ns: u64,
    method: InternedString,
    status: u32,
}

impl Encodable for HttpRequest {
    fn encode(&self, enc: &mut ThreadLocalEncoder<'_>) {
        let method = enc.intern_string(&self.method);
        enc.encode(&HttpRequestWire {
            timestamp_ns: self.timestamp_ns,
            method,
            status: self.status,
        });
    }
}

fn main() -> std::io::Result<()> {
    let dir = tempfile::tempdir()?;
    let trace_path = dir.path().join("trace.bin");

    let writer = DiskBuffer::single_file(&trace_path)?;
    let recorder = recorder(writer).build();

    let mut builder = tokio::runtime::Builder::new_multi_thread();
    builder.enable_all().worker_threads(2);

    let rt = recorder
        .handle()
        .attach_tokio_runtime(builder, TokioAttachOptions::default())
        .expect("build tokio runtime");

    let handle = recorder.handle();

    rt.block_on(async {
        // Simple: derive-only events
        for i in 0..10 {
            let error_message = if i % 4 == 3 {
                Some(format!("timeout after {}ms", 100 + i))
            } else {
                None
            };
            handle.record_event(RequestCompleted {
                timestamp_ns: clock_monotonic_ns(),
                status_code: if error_message.is_some() { 500 } else { 200 },
                latency_us: 100 + i,
                error_message,
            });
        }

        // Advanced: manual Encodable with interning
        // "GET" is interned once and reused across all events in the batch.
        for _ in 0..10 {
            handle.record_event(HttpRequest {
                timestamp_ns: clock_monotonic_ns(),
                method: "GET".into(),
                status: 200,
            });
        }

        tokio::time::sleep(Duration::from_millis(100)).await;
    });

    drop(rt);
    drop(recorder);

    // Verify: decode the trace and count our custom events
    let sealed = dir.path().join("trace.0.bin");
    let data = std::fs::read(&sealed)?;
    let mut decoder =
        dial9::format::Decoder::new(&data).ok_or_else(|| std::io::Error::other("invalid trace"))?;

    let mut request_completed = 0u32;
    let mut http_request = 0u32;
    decoder
        .for_each_event(|ev| match ev.name {
            "RequestCompleted" => request_completed += 1,
            "HttpRequestWire" => http_request += 1,
            _ => {}
        })
        .map_err(|e| std::io::Error::other(e.to_string()))?;

    println!("RequestCompleted events: {request_completed}");
    println!("HttpRequestWire events:  {http_request}");
    assert_eq!(request_completed, 10);
    assert_eq!(http_request, 10);
    println!("✓ All custom events recorded successfully");

    Ok(())
}