hotpath 0.15.0

Simple async Rust profiler with memory and data-flow insights - quickly find and debug performance bottlenecks.
Documentation
use clap::Parser;
use eyre::Result;
use hotpath::json::JsonReport;
use std::env;
use std::fs;

use crate::cmd::shared::{
    build_functions_table, build_threads_table, compare_reports, format_threads_globals,
    JsonReportDiff,
};

fn use_colors() -> bool {
    env::var("NO_COLOR").is_err()
}

fn print_table(table: &prettytable::Table) {
    if use_colors() {
        let _ = table.print_tty(false);
    } else {
        table.printstd();
    }
}

#[derive(Debug, Parser)]
pub struct CompareArgs {
    #[arg(long, help = "Path to the before JSON report")]
    before_json_path: String,

    #[arg(long, help = "Path to the after JSON report")]
    after_json_path: String,
}

impl CompareArgs {
    pub fn run(&self) -> Result<()> {
        let before_raw = fs::read_to_string(&self.before_json_path)
            .map_err(|e| eyre::eyre!("Failed to read before JSON: {}", e))?;
        let after_raw = fs::read_to_string(&self.after_json_path)
            .map_err(|e| eyre::eyre!("Failed to read after JSON: {}", e))?;

        let before: JsonReport = serde_json::from_str(&before_raw)
            .map_err(|e| eyre::eyre!("Failed to parse before JSON: {}", e))?;
        let after: JsonReport = serde_json::from_str(&after_raw)
            .map_err(|e| eyre::eyre!("Failed to parse after JSON: {}", e))?;

        let diff = compare_reports(&before, &after).map_err(|e| eyre::eyre!("{}", e))?;
        print_diff(&diff);

        Ok(())
    }
}

fn print_diff(diff: &JsonReportDiff) {
    if diff.functions_timing.is_none() && diff.functions_alloc.is_none() && diff.threads.is_none() {
        println!("No comparable sections found.");
        return;
    }

    if diff.before_label.is_some() || diff.after_label.is_some() {
        let before = diff.before_label.as_deref().unwrap_or("(unlabeled)");
        let after = diff.after_label.as_deref().unwrap_or("(unlabeled)");
        println!("Comparing: {}{}", before, after);
    }

    println!("Total Elapsed: {}", diff.total_elapsed_diff);
    if let Some(cpu_baseline) = &diff.cpu_baseline_diff {
        println!("CPU Baseline: {}", cpu_baseline);
    }
    println!();

    let colors = use_colors();

    if let Some(comparison) = &diff.functions_timing {
        println!("{} - {}", comparison.profiling_mode, comparison.description);
        if comparison.function_diffs.is_empty() {
            println!("No functions to compare.");
        } else {
            print_table(&build_functions_table(comparison, None, colors));
        }
        println!();
    }

    if let Some(comparison) = &diff.functions_alloc {
        println!("{} - {}", comparison.profiling_mode, comparison.description);
        if comparison.function_diffs.is_empty() {
            println!("No functions to compare.");
        } else {
            print_table(&build_functions_table(comparison, None, colors));
        }
        println!();
    }

    if let Some(threads) = &diff.threads {
        println!("Threads");
        if threads.thread_diffs.is_empty() {
            println!("No threads to compare.");
        } else {
            let globals = format_threads_globals(threads, None);
            if !globals.is_empty() {
                println!("{}", globals.join(" | "));
            }
            print_table(&build_threads_table(threads, None, colors));
        }
        println!();
    }
}