denoland/deno · error

Failed to parse profile: {}

Error message

Failed to parse profile: {}

What it means

The flamegraph path of the CPU profiler (runtime/cpu_profiler/flamegraph.rs) deserializes the same profile JSON into `CpuProfile` before building the node/parent maps for the SVG. On serde failure it wraps the error as `io::ErrorKind::InvalidData` with this message. Like the markdown report, the flamegraph is generated after the raw `.cpuprofile` was already flushed, so only the `.svg` artifact is lost; the caller in mod.rs logs and continues.

Source

Thrown at runtime/cpu_profiler/flamegraph.rs:22

use std::fs::File;
use std::io::BufWriter;
use std::io::Write;

use deno_core::serde_json;

use super::cpuprof::CpuProfile;
use super::cpuprof::ProfileNode;

const FLAMEGRAPH_JS: &str = include_str!("flamegraph.js");

pub(crate) fn generate_flamegraph_svg(
  profile: &serde_json::Value,
  filepath: &std::path::Path,
) -> std::io::Result<()> {
  let profile: CpuProfile = match serde_json::from_value(profile.clone()) {
    Ok(p) => p,
    Err(err) => {
      return Err(std::io::Error::new(
        std::io::ErrorKind::InvalidData,
        format!("Failed to parse profile: {}", err),
      ));
    }
  };

  let node_map: HashMap<i32, &ProfileNode> =
    profile.nodes.iter().map(|n| (n.id, n)).collect();

  // Build parent map
  let mut parent_map: HashMap<i32, i32> = HashMap::new();
  for node in &profile.nodes {
    for &child_id in &node.children {
      parent_map.insert(child_id, node.id);
    }
  }

  // Build folded stacks from samples

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Read the serde detail in the message to find the offending field, then fix the `CpuProfile`/`ProfileNode` structs in runtime/cpu_profiler/cpuprof.rs (both report generators share them, so one fix repairs markdown and flamegraph).
  2. Add `#[serde(default)]` for fields that may be absent (empty samples/time deltas on tiny profiles).
  3. Use the raw `.cpuprofile` (still written successfully) in speedscope or Chrome DevTools as an immediate workaround.

Example fix

// before (runtime/cpu_profiler/cpuprof.rs)
#[derive(Deserialize)]
pub(crate) struct ProfileNode {
  pub id: i32,
  pub call_frame: CallFrame,
  pub children: Vec<i32>,
}

// after — children may be absent for leaf nodes in some V8 versions
#[derive(Deserialize)]
pub(crate) struct ProfileNode {
  pub id: i32,
  pub call_frame: CallFrame,
  #[serde(default)]
  pub children: Vec<i32>,
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Same pre-check as the markdown path — validate the JSON shape first:
fn looks_like_cpuprofile(v: &serde_json::Value) -> bool {
  v.get("nodes").map(|n| n.is_array()).unwrap_or(false)
    && v.get("samples").map(|n| n.is_array()).unwrap_or(false)
    && v.get("timeDeltas").map(|n| n.is_array()).unwrap_or(false)
}

Try / catch

// Best-effort flamegraph, mirroring runtime/cpu_profiler/mod.rs:
match flamegraph::generate_flamegraph_svg(&profile, &svg_path) {
  Ok(()) => {}
  Err(err) if err.kind() == std::io::ErrorKind::InvalidData => {
    log::error!("flamegraph skipped: profile JSON rejected: {err}");
  }
  Err(err) => log::error!("flamegraph write failed: {err}"),
}

Prevention

When it happens

Trigger: Enabling flamegraph generation on a profile whose JSON does not match `CpuProfile` — same root causes as the markdown variant: schema drift after a V8 upgrade, missing `nodes`/`samples`/`timeDeltas` keys, or value type mismatches (e.g. a string where an i32 node id is expected).

Common situations: V8 profile-format changes between versions; profiles captured under unusual conditions (extremely short runs, no samples); post-processing or merging `.cpuprofile` files with external tools that alter the JSON shape.

Understand the failure class

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/d3c615b0aebfa16b. Report an issue: GitHub.