tauri-plugin-profiling 0.1.0

Tauri plugin for CPU profiling with flamegraph generation
Documentation
//! Benchmarks for the profiling plugin
//!
//! Run with: cargo bench
//!
//! Measures:
//! - Profiler startup time
//! - Profiler stop + flamegraph generation time
//! - Profiling overhead on CPU-bound work

#![allow(clippy::expect_used)]

use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
use tempfile::TempDir;

#[cfg(unix)]
use pprof::ProfilerGuardBuilder;

/// CPU-bound workload for testing profiler overhead
fn cpu_workload(iterations: u64) -> u64 {
    let mut sum: u64 = 0;
    for i in 0..iterations {
        sum = sum.wrapping_add(i.wrapping_mul(i));
        // Add some branching to make it more realistic
        if sum.is_multiple_of(1000) {
            sum = sum.wrapping_add(1);
        }
    }
    sum
}

/// Recursive fibonacci - creates deep call stacks for profiling
fn fib(n: u64) -> u64 {
    if n <= 1 { n } else { fib(n - 1) + fib(n - 2) }
}

#[cfg(unix)]
fn bench_profiler_start(c: &mut Criterion) {
    c.bench_function("profiler_start", |b| {
        b.iter(|| {
            let guard = ProfilerGuardBuilder::default()
                .frequency(100)
                .blocklist(&["libc", "libgcc", "pthread", "vdso"])
                .build()
                .expect("Failed to start profiler");
            black_box(guard)
        });
    });
}

#[cfg(unix)]
fn bench_profiler_stop_and_flamegraph(c: &mut Criterion) {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");

    c.bench_function("profiler_stop_flamegraph", |b| {
        b.iter_custom(|iters| {
            let mut total = std::time::Duration::ZERO;

            for i in 0..iters {
                // Start profiler and do minimal work
                let guard = ProfilerGuardBuilder::default()
                    .frequency(100)
                    .blocklist(&["libc", "libgcc", "pthread", "vdso"])
                    .build()
                    .expect("Failed to start profiler");

                // Do a tiny bit of work so there's something to report
                black_box(cpu_workload(1000));

                // Time only the stop + flamegraph generation
                let start = std::time::Instant::now();
                let report = guard.report().build().expect("Failed to build report");

                let output_path = temp_dir.path().join(format!("bench_{}.svg", i));
                let file = std::fs::File::create(&output_path).expect("Failed to create file");
                let mut writer = std::io::BufWriter::new(file);
                report
                    .flamegraph(&mut writer)
                    .expect("Failed to write flamegraph");

                total += start.elapsed();

                // Cleanup
                let _ = std::fs::remove_file(&output_path);
            }

            total
        });
    });
}

#[cfg(unix)]
fn bench_profiling_overhead(c: &mut Criterion) {
    let mut group = c.benchmark_group("profiling_overhead");

    // Test with different workload sizes
    for size in [10_000u64, 100_000, 1_000_000].iter() {
        // Baseline: work without profiling
        group.bench_with_input(
            BenchmarkId::new("without_profiler", size),
            size,
            |b, &size| {
                b.iter(|| black_box(cpu_workload(size)));
            },
        );

        // With profiling enabled
        group.bench_with_input(BenchmarkId::new("with_profiler", size), size, |b, &size| {
            b.iter(|| {
                let guard = ProfilerGuardBuilder::default()
                    .frequency(100)
                    .blocklist(&["libc", "libgcc", "pthread", "vdso"])
                    .build()
                    .expect("Failed to start profiler");

                let result = black_box(cpu_workload(size));

                // Don't generate flamegraph, just stop
                drop(guard);

                result
            });
        });
    }

    group.finish();
}

#[cfg(unix)]
fn bench_flamegraph_sizes(c: &mut Criterion) {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let mut group = c.benchmark_group("flamegraph_generation");

    // Different call stack depths via fibonacci
    for fib_n in [20u64, 25, 30].iter() {
        group.bench_with_input(BenchmarkId::new("fib_depth", fib_n), fib_n, |b, &fib_n| {
            b.iter_custom(|iters| {
                let mut total = std::time::Duration::ZERO;

                for i in 0..iters {
                    let guard = ProfilerGuardBuilder::default()
                        .frequency(1000) // Higher frequency to capture more
                        .blocklist(&["libc", "libgcc", "pthread", "vdso"])
                        .build()
                        .expect("Failed to start profiler");

                    // Generate call stacks via recursive fib
                    black_box(fib(fib_n));

                    let start = std::time::Instant::now();
                    let report = guard.report().build().expect("Failed to build report");

                    let output_path = temp_dir.path().join(format!("fib_{}_{}.svg", fib_n, i));
                    let file = std::fs::File::create(&output_path).expect("Failed to create file");
                    let mut writer = std::io::BufWriter::new(file);
                    report
                        .flamegraph(&mut writer)
                        .expect("Failed to write flamegraph");

                    total += start.elapsed();
                    let _ = std::fs::remove_file(&output_path);
                }

                total
            });
        });
    }

    group.finish();
}

#[cfg(unix)]
criterion_group!(
    benches,
    bench_profiler_start,
    bench_profiler_stop_and_flamegraph,
    bench_profiling_overhead,
    bench_flamegraph_sizes,
);

#[cfg(unix)]
criterion_main!(benches);

// Stub for non-unix platforms
#[cfg(not(unix))]
fn main() {
    eprintln!("Benchmarks only supported on Unix platforms (macOS, Linux)");
    std::process::exit(1);
}