# How to Ingest Node.js CPU Profile Trace Data with code-graph-rag: A Complete Guide

> Learn to ingest Node.js CPU profile trace data into code-graph-rag. Convert V8 .cpuprofile files to weighted call-graph edges using cgr trace convert and cgr trace ingest.

- Repository: [Vitali Avagyan/code-graph-rag](https://github.com/vitali87/code-graph-rag)
- Tags: how-to-guide
- Published: 2026-09-06

---

**Convert V8 `.cpuprofile` files into weighted call-graph edges using `cgr trace convert` and `cgr trace ingest` commands.**

Node.js emits **V8 CPU profile** files (`.cpuprofile`) that contain sampled call-stack information from JavaScript execution. The `code-graph-rag` repository—maintained at `vitali87/code-graph-rag`—ships a purpose-built converter in [`codebase_rag/trace/cpuprofile.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/trace/cpuprofile.py) that transforms these profiles into the **cgr-trace** interchange format for graph ingestion. This workflow captures dynamic caller-callee relationships with sampling weights, enabling graph-based analysis of actual runtime behavior.

## Generate a V8 CPU Profile

V8's built-in profiler samples the JavaScript execution stack at fixed intervals (default 1ms). To capture a profile from your Node.js application:

```bash
node --cpu-prof --cpu-prof-name=run.cpuprofile app.js

```

The `--cpu-prof` flag enables sampling, and `--cpu-prof-name` specifies the output filename. The resulting `.cpuprofile` file contains a JSON tree of sampled call stacks with timing metadata.

## Convert the Profile to cgr-trace Format

The `cgr` CLI detects the `.cpuprofile` extension and dispatches to `convert_cpuprofile` in [`codebase_rag/trace/cpuprofile.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/trace/cpuprofile.py). Run the conversion:

```bash
cgr trace convert run.cpuprofile \
  --repo-path /path/to/your-repo \
  --workload smoke

```

This command produces `cgr-trace.jsonl`, a line-delimited JSON file with structured call records.

### Key Conversion Logic in [`cpuprofile.py`](https://github.com/vitali87/code-graph-rag/blob/main/cpuprofile.py)

The converter implements three critical transformations according to the source code:

- **`_aggregate_project_edges`** — Collapses the sampled call-tree into weighted caller-callee pairs while filtering through internal V8 frames that lack meaningful source mapping.
- **`_project_frame`** — Resolves generated file URLs to repository-relative paths, with automatic **source-map relocation** for transpiled code (TypeScript, Babel, etc.).
- **`convert_cpuprofile`** — Writes `CallRecord` objects with a `TraceHeader` marked `TRACE_TOOL_NAME_CPUPROFILE`, preserving the sampled count as `dynamic_call_count`.

Frames that resolve outside the repository root—such as `node:` built-ins or `node_modules` dependencies—are automatically discarded, ensuring the graph contains only project-relevant edges.

## Ingest the Trace into the Graph

Merge the converted call records into your repository's knowledge graph:

```bash
cgr trace ingest cgr-trace.jsonl --repo-path /path/to/your-repo

```

The ingest command:

1. Reads each line-delimited JSON record
2. Resolves paths against the provided repository root
3. Creates dynamic edges with `dynamic: true` flag
4. Attaches the workload label (e.g., `smoke`) and sampled weight

## Verify Successful Ingestion

Confirm that dynamic edges were added from the CPU profile:

```bash
cgr graph query 'CALLS' --filter dynamic:true

```

This returns caller-callee pairs observed during sampling, complete with `dynamic_call_count` weights representing relative execution frequency.

## Source-Map Handling for Transpiled Projects

A critical capability of `code-graph-rag`'s Node.js ingestion is transparent source-map support. When the profile references generated `.js` files, the converter:

1. Looks for adjacent `.js.map` files
2. Parses the source map via [`codebase_rag/trace/sourcemap.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/trace/sourcemap.py)
3. Remaps frames to original TypeScript/JavaScript source locations

This ensures accurate graph nodes even for compiled or bundled applications, with the original source positions preserved in the final trace records.

## Core Files and Their Roles

| File | Purpose |
|------|---------|
| [`codebase_rag/trace/cpuprofile.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/trace/cpuprofile.py) | Core converter; parses `.cpuprofile` JSON, aggregates edges, applies source-maps, emits `cgr-trace` records |
| [`codebase_rag/trace/cli.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/trace/cli.py) | CLI dispatcher; detects `.cpuprofile` suffix and routes to `convert_cpuprofile` |
| [`codebase_rag/trace/sourcemap.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/trace/sourcemap.py) | Source-map loader and location remapping utilities |
| [`codebase_rag/constants/trace.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/constants/trace.py) | Trace format constants including `TRACE_TOOL_NAME_CPUPROFILE` |
| [`docs/guide/dynamic-tracing.md`](https://github.com/vitali87/code-graph-rag/blob/main/docs/guide/dynamic-tracing.md) | Complete user documentation for Node.js and other runtimes |

## Summary

- **Generate** Node.js CPU profiles with `node --cpu-prof`
- **Convert** via `cgr trace convert`, which handles sampling aggregation and source-map resolution in [`codebase_rag/trace/cpuprofile.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/trace/cpuprofile.py)
- **Ingest** with `cgr trace ingest` to create weighted dynamic edges
- **Sampled counts** are preserved as `dynamic_call_count`, not exact call frequencies
- **Source-maps** are automatically resolved for transpiled code accuracy
- **Out-of-scope frames** (built-ins, dependencies) are filtered to maintain project focus

## Frequently Asked Questions

### What sampling interval does V8 use for CPU profiles?

V8 samples the JavaScript execution stack every 1 millisecond by default. This produces statistically representative call-stack data without prohibitive overhead, though rare fast functions may be missed entirely. The `code-graph-rag` converter treats sample counts as relative weights rather than absolute call frequencies.

### Can I ingest profiles from TypeScript or bundled applications?

Yes. The converter in [`codebase_rag/trace/cpuprofile.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/trace/cpuprofile.py) automatically detects and applies source maps through [`codebase_rag/trace/sourcemap.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/trace/sourcemap.py). When a `.js.map` file exists adjacent to the generated JavaScript, frames are remapped to original source locations before graph insertion, preserving accurate file paths and line numbers.

### Why are some frames missing from the ingested trace?

Frames are filtered when they resolve outside the repository root—specifically `node:` built-in modules, `node_modules` dependencies, or V8 internal frames without source mappings. This intentional scoping ensures the knowledge graph contains only edges relevant to your project's own code.

### How does `dynamic_call_count` differ from static call analysis?

`dynamic_call_count` reflects **observed execution frequency** from sampling, weighted by sample hits. Static analysis would show all possible call paths regardless of actual runtime usage. The CPU profile ingestion thus surfaces hot paths and frequently executed edges that static analysis cannot identify.