profile-inspect 0.1.3

Analyze V8 CPU and heap profiles from Node.js/Chrome DevTools
Documentation
use serde::Deserialize;

/// Raw V8 CPU profile as produced by Chrome DevTools / Node.js
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CpuProfile {
    /// Array of profile nodes (call frames with timing data)
    pub nodes: Vec<CpuProfileNode>,

    /// Timestamps of samples in microseconds
    #[serde(default)]
    pub samples: Vec<u32>,

    /// Time deltas between samples in microseconds
    #[serde(default)]
    pub time_deltas: Vec<i64>,

    /// Profile start time in microseconds
    pub start_time: u64,

    /// Profile end time in microseconds
    pub end_time: u64,
}

/// A single node in the CPU profile call tree
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CpuProfileNode {
    /// Unique node identifier
    pub id: u32,

    /// Call frame information
    pub call_frame: CallFrame,

    /// Child node IDs
    #[serde(default)]
    pub children: Vec<u32>,

    /// Hit count (number of samples where this was the leaf)
    #[serde(default)]
    pub hit_count: u32,

    /// Position ticks (line-level timing data)
    #[serde(default)]
    pub position_ticks: Vec<PositionTickInfo>,
}

/// Call frame information for a profile node
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CallFrame {
    /// Function name
    pub function_name: String,

    /// Script ID
    #[serde(default)]
    pub script_id: String,

    /// Script URL (file path or URL)
    #[serde(default)]
    pub url: String,

    /// Line number (0-based)
    #[serde(default)]
    pub line_number: i32,

    /// Column number (0-based)
    #[serde(default)]
    pub column_number: i32,
}

/// Position tick information for line-level profiling
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PositionTickInfo {
    /// Line number
    pub line: u32,

    /// Number of ticks at this line
    pub ticks: u32,
}

impl CpuProfile {
    /// Parse a CPU profile from JSON
    ///
    /// # Errors
    /// Returns an error if the JSON is invalid.
    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
        serde_json::from_str(json)
    }

    /// Get the total duration in microseconds
    pub fn duration_us(&self) -> u64 {
        self.end_time.saturating_sub(self.start_time)
    }

    /// Get the total duration in milliseconds
    #[expect(clippy::cast_precision_loss)]
    pub fn duration_ms(&self) -> f64 {
        self.duration_us() as f64 / 1000.0
    }
}