dial9 0.5.6

Low-overhead async runtime telemetry: event recording, Tokio integration, CPU/memory profiling, and a trace viewer CLI
Documentation
#![cfg(feature = "memory-profiling")]
#![cfg(target_os = "linux")]
//! Test that no MemoryProfileOverflowEvent is emitted when ring has sufficient capacity.

mod common;

use common::{CAPTURE_BUFFER_SIZE, capture_processor, decode_all};
use dial9::memory::{Dial9Allocator, MemoryProfiler, MemoryProfilingConfig};
use dial9::{Dial9HandleTokioExt, MemoryBuffer, RecorderPipelineExt, TokioAttachOptions, recorder};
use serde::Deserialize;
use std::time::Duration;

#[global_allocator]
static ALLOC: Dial9Allocator = Dial9Allocator::system();

#[derive(Debug, Deserialize)]
#[serde(tag = "event")]
enum OverflowEvent {
    MemoryProfileOverflowEvent {
        #[allow(dead_code)]
        timestamp_ns: u64,
        #[allow(dead_code)]
        dropped_allocs: u64,
        #[allow(dead_code)]
        dropped_frees: u64,
    },
    #[serde(other)]
    Other,
}

#[test]
fn no_overflow_event_when_ring_has_capacity() {
    let (capture, batches) = capture_processor();

    let recorder = recorder(MemoryBuffer::new(CAPTURE_BUFFER_SIZE).unwrap())
        .with_custom_pipeline(|p| p.pipe(capture))
        .build();
    let mut builder = tokio::runtime::Builder::new_multi_thread();
    builder.enable_all().worker_threads(1);
    let rt = recorder
        .handle()
        .attach_tokio_runtime(builder, TokioAttachOptions::default())
        .expect("attach tokio");

    let handle = recorder.handle().clone();
    let _mem_guard = MemoryProfiler::from_config(
        MemoryProfilingConfig::builder()
            .sample_rate_bytes(512 * 1024)
            .ring_capacity(4096)
            .rng_seed(42)
            .build(),
    )
    .install(handle)
    .expect("install should succeed");

    rt.block_on(async {
        for _ in 0..10 {
            let v: Vec<u8> = vec![0u8; 64];
            std::hint::black_box(&v);
            drop(v);
        }
        tokio::time::sleep(Duration::from_millis(100)).await;
    });

    drop(rt);
    recorder.graceful_shutdown(Duration::from_secs(1));

    let batches = batches.lock().unwrap();
    let events: Vec<OverflowEvent> = decode_all(&batches);

    let overflows: Vec<_> = events
        .iter()
        .filter(|e| matches!(e, OverflowEvent::MemoryProfileOverflowEvent { .. }))
        .collect();

    assert!(
        overflows.is_empty(),
        "expected no MemoryProfileOverflowEvent when ring has capacity, got {}",
        overflows.len()
    );
}