CPU profiles in Chrome DevTools can reveal a lot about how your application runs, but making sense of the underlying data structures isn’t always straightforward. Even experienced developers often find the structure of these JSON files less than intuitive. In this article, we break down the main components that make up CPU profile data and examine how they’re represented in DevTools. With a clearer grasp of these building blocks, you will be better equipped to investigate performance and spot optimization opportunities.

## Data Structure

CPU profiles are JSON files that contain structured data representing the execution timeline and call hierarchy of your Node.js application. The profile consists of nodes representing function calls, timing information, and sampling data that can be visualized in DevTools.

```
type CpuProfile = {
  // List of nodes in the CPU profile
  nodes: Node[];
  // Start time of the profile in microseconds (μs)
  startTime: number;
  // End time of the profile in microseconds (μs)
  endTime: number;
  // List of node IDs indicating which nodes were active during the profile. e.g. [2,4,5]
  samples: number[];
  // List of time deltas between samples in microseconds (μs)
  timeDeltas: number[];
};
```

The example below shows a minimal CPU profile.

**Profile:** [minimal-cpu-profile.cpuprofile](https://github.com/push-based/cpu-prof/blob/main/packages/cpu-prof/docs/examples/minimal-cpu-profile.cpuprofile)

**Profile content:**

```
{
  "nodes": [
    {
      "id": 1,
      "callFrame": {
        "functionName": "(root)",
        "scriptId": "0",
        "url": "",
        "lineNumber": -1,
        "columnNumber": -1
      },
      "children": [2]
    },
    {
      "id": 2,
      "callFrame": {
        "functionName": "runMainESM",
        "scriptId": "1",
        "url": "node:internal/modules/run_main",
        "lineNumber": 92,
        "columnNumber": 19
      },
      "children": [3]
    },
    {
      "id": 3,
      "callFrame": {
        "functionName": "main-work",
        "scriptId": "2",
        "url": "file:///index.mjs",
        "lineNumber": 10,
        "columnNumber": 0
      },
      "children": [4, 5]
    },
    {
      "id": 4,
      "callFrame": {
        "functionName": "child-work-1",
        "scriptId": "2",
        "url": "file:///index.mjs",
        "lineNumber": 11,
        "columnNumber": 2
      }
    },
    {
      "id": 5,
      "callFrame": {
        "functionName": "child-work-2",
        "scriptId": "2",
        "url": "file:///index.mjs",
        "lineNumber": 12,
        "columnNumber": 2
      }
    }
  ],
  "startTime": 1,
  "endTime": 400,
  "samples": [2, 4, 5],
  "timeDeltas": [0, 100, 100]
}
```

**DevTools Performance Tab:**

## Dimensions and Timing Data

CPU profiles represent execution data across two primary dimensions: time (horizontal axis) and call-tree depth (vertical axis). Understanding these dimensions is crucial for interpreting flame charts and performance data.

### **Dimensions**

*   `startTime` - the microsecond timestamp when profiling began
    
*   `endTime` - equals `startTime + Σ timeDeltas`, marking the profile's visible end
    

### **Timing Data**

*   `timeDeltas` - an array of intervals (μs) between each sample tick. Time deltas overflow the visible end of the measure.
    
*   `samples` - an array of node IDs indicating which nodes were active during the profile.
    

**Time (horizontal axis)** represents the execution timeline with `startTime` marking when profiling began, `endTime` marking the profile's visible end, and `timeDeltas` providing intervals between samples.

**Call-tree depth (vertical axis)** shows the function call hierarchy where the root node is at depth 0, and each level represents nested function calls. The `samples` array contains the "leaf frames" - the deepest executing functions at each time interval, with their parent chain automatically reconstructed for visualization.

The example below shows a profile with one node (ID 2) centered in the middle of the chart. Its position in the timeline is determined by the `timeDeltas`, while `endTime` marks the end of the profile.

**Profile:** [minimal-cpu-profile-timing-data.cpuprofile](https://github.com/push-based/cpu-prof/blob/main/packages/cpu-prof/docs/examples/minimal-cpu-profile-timing-data.cpuprofile)

```
{
  "nodes": [
    {
      "id": 1,
      "callFrame": {
        "functionName": "(root)",
        "scriptId": "0",
        "url": "",
        "lineNumber": -1,
        "columnNumber": -1
      },
      "children": [2]
    },
    {
      "id": 2,
      "callFrame": {
        "functionName": "runMainESM",
        "scriptId": "1",
        "url": "node:internal/modules/run_main",
        "lineNumber": 92,
        "columnNumber": 19
      },
      "children": []
    }
  ],
  "startTime": 1,
  "endTime": 400,
  "samples": [1, 2, 2, 1],
  "timeDeltas": [0, 100, 100, 100]
}
```

**DevTools Performance Tab:**

## Time deltas

**Profile:** [minimal-cpu-profile-timing-data-time-deltas.cpuprofile](https://github.com/push-based/cpu-prof/blob/main/packages/cpu-prof/docs/examples/minimal-cpu-profile-timing-data-time-deltas.cpuprofile)

```
{
  "nodes": [
    {
      "id": 1,
      "callFrame": {
        "functionName": "(root)",
        "scriptId": "0",
        "url": "",
        "lineNumber": -1,
        "columnNumber": -1
      },
      "children": [2, 3]
    },
    {
      "id": 2,
      "callFrame": {
        "functionName": "work-1",
        "scriptId": "1",
        "url": "file:///a.js",
        "lineNumber": 92,
        "columnNumber": 19
      }
    },
    {
      "id": 3,
      "callFrame": {
        "functionName": "work-2",
        "scriptId": "2",
        "url": "file:///b.js",
        "lineNumber": 92,
        "columnNumber": 19
      }
    }
  ],
  "startTime": 4,
  "endTime": 27,
  "samples": [1, 2, 1, 3, 1, 2, 1, 3],
  "timeDeltas": [0, 1, 3, 2, 5, 1, 4, 2]
}
```

**DevTools Performance Tab:**

## Samples

Samples are the list of "visible" nodes when viewed from the bottom of the chart. Listing a node at a given `timeDelta` (its position in the `samples` array) also includes all of its parent nodes. In other words, the `samples` array represents the leaf frames in the chart.

**Profile:** [minimal-cpu-profile-timing-data-samples.cpuprofile](https://github.com/push-based/cpu-prof/blob/main/packages/cpu-prof/docs/examples/minimal-cpu-profile-timing-data-samples.cpuprofile)

```
{
  "nodes": [
    {
      "id": 1,
      "callFrame": {
        "functionName": "(root)",
        "scriptId": "0",
        "url": "",
        "lineNumber": -1,
        "columnNumber": -1
      },
      "children": [2, 3]
    },
    {
      "id": 2,
      "callFrame": {
        "functionName": "work-1",
        "scriptId": "1",
        "url": "file:///a.js",
        "lineNumber": 92,
        "columnNumber": 19
      },
      "children": [4]
    },
    {
      "id": 4,
      "callFrame": {
        "functionName": "helper",
        "scriptId": "1",
        "url": "file:///a.js",
        "lineNumber": 98,
        "columnNumber": 5
      },
      "children": [5]
    },
    {
      "id": 5,
      "callFrame": {
        "functionName": "compute",
        "scriptId": "1",
        "url": "file:///a.js",
        "lineNumber": 99,
        "columnNumber": 5
      }
    },
    {
      "id": 3,
      "callFrame": {
        "functionName": "work-2",
        "scriptId": "2",
        "url": "file:///b.js",
        "lineNumber": 92,
        "columnNumber": 19
      },
      "children": [6]
    },
    {
      "id": 6,
      "callFrame": {
        "functionName": "fetch-data",
        "scriptId": "2",
        "url": "file:///b.js",
        "lineNumber": 120,
        "columnNumber": 3
      },
      "children": [7]
    },
    {
      "id": 7,
      "callFrame": {
        "functionName": "parse-result",
        "scriptId": "2",
        "url": "file:///b.js",
        "lineNumber": 121,
        "columnNumber": 5
      }
    }
  ],
  "startTime": 1,
  "endTime": 160,
  "samples": [1, 2, 4, 5, 4, 2, 1, 1, 3, 6, 7, 6, 7, 6, 3, 1],
  "timeDeltas": [0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
}
```

This example draws the same node sequence (1->2->3) twice.

*   The first time, it draws them as a _tower_, where each frame has the same width (takes the same time).
    
    `"samples": [1, 3, 3, 1], "timeDeltas": [0, 100, 100, 100]`
    
    (visualized as ▀▀)
    
*   The second time, it draws them as a _flame_, where each frame is slightly narrower and nested inside its parent.
    
    `"samples": [1, 2, 3, 2, 1], "timeDeltas": [0, 100, 100, 100, 100]`
    
    (visualized as ▔▀▔)
    

**DevTools Performance Tab:**

## Nodes

```
/**
 * Represents a node in the CPU profile.
 * Each node corresponds to a function call and contains information about
 * the call frame, its children, and an optional hit count.
 */
type Node = {
  // Unique identifier for the node e.g. 1
  id: number;
  // Call frame information for the node
  callFrame: CallFrame;
  // Optional parent node ID, indicating the node that called this one. e.g. 1
  parent?: number;
  // List of child node IDs called by this node. e.g. [2,3]
  children: number[];
  // Optional hit count for the node, indicating how many times it was executed
  hitCount?: number;
};
```

### Parent and child nodes

Node relationships define the call hierarchy through parent-child references that mirror the JavaScript call stack. These relationships enable reconstruction of the complete call tree for flame chart visualization and performance analysis.

```
export interface Node {
  id: number;
  callFrame: CallFrame;
  parent?: number;
  children: number[];
  hitCount?: number;
}
```

`parent` contains the optional ID of the immediate caller in the call tree (omitted on the root node). `children` lists the IDs of frames this node directly invokes, mirroring the inverted parent relationships.

Traversal starts at the root (no `parent`) and recursively follows its `children` to rebuild the full call hierarchy, useful for flame chart rendering and aggregating inclusive vs. exclusive hit counts.

**Profile:** [minimal-cpu-profile-nodes-parent-child.cpuprofile](https://github.com/push-based/cpu-prof/blob/main/packages/cpu-prof/docs/examples/minimal-cpu-profile-nodes-parent-child.cpuprofile)

```
{
  "nodes": [
    {
      "id": 1,
      "callFrame": {
        "functionName": "(root)",
        "scriptId": "0",
        "url": "",
        "lineNumber": -1,
        "columnNumber": -1
      },
      "children": [2]
    },
    {
      "id": 2,
      "callFrame": {
        "functionName": "runMainESM",
        "scriptId": "1",
        "url": "node:internal/modules/run_main",
        "lineNumber": 92,
        "columnNumber": 19
      },
      "parent": 1,
      "children": [3, 4]
    },
    {
      "id": 3,
      "callFrame": {
        "functionName": "child-1",
        "scriptId": "2",
        "url": "index.js",
        "lineNumber": 3,
        "columnNumber": 63
      },
      "parent": 2,
      "children": []
    },
    {
      "id": 4,
      "callFrame": {
        "functionName": "child-2",
        "scriptId": "2",
        "url": "index.js",
        "lineNumber": 3,
        "columnNumber": 63
      },
      "parent": 2,
      "children": [5]
    },
    {
      "id": 5,
      "callFrame": {
        "functionName": "child-2-1",
        "scriptId": "2",
        "url": "index.js",
        "lineNumber": 3,
        "columnNumber": 63
      },
      "parent": 4,
      "children": [6]
    },
    {
      "id": 6,
      "callFrame": {
        "functionName": "child-2-1-1",
        "scriptId": "2",
        "url": "index.js",
        "lineNumber": 3,
        "columnNumber": 63
      },
      "parent": 4
    }
  ],
  "startTime": 1,
  "endTime": 10,
  "samples": [6],
  "timeDeltas": [0]
}
```

**DevTools Performance Tab:**

This view shows only part of the node tree. In the `samples` array, node 6 appears as the last visible node. From that point, the full tree can be reconstructed using the `parent` and `children` properties. Leaf nodes from 3 onward are not visible in the chart because walking up from node 6 does not reach them.

### CallFrame

A `callFrame` objects contains source location details, such as file, function name, line, column that DevTools displays. In a flame chart, the call depth determines vertical stacking, with deeper calls shown lower in the visualization.

```
type CallFrame = {
  // Name of the function being executed
  functionName: string;
  // Unique identifier for the script
  scriptId: string;
  // URL or path of the script file
  url: string;
  // Line number in the script
  lineNumber: number;
  // Column number in the script
  columnNumber: number;
};
```

#### Source Location and label

The `callFrame` object provides details about the source code location and helps identify functions in the DevTools profiler.

*   `functionName`: The name of the executed function.
    
*   `url`: The file path or URL of the script. This also determines the color-coding of frames in the flame chart (same URL means same color).
    
*   `scriptId`: A unique identifier for the script.
    
*   `lineNumber`: The line number within the script.
    
*   `columnNumber`: The column number within the script.
    

**Profile:** [minimal-cpu-profile-nodes-call-frame-source-location.cpuprofile](https://github.com/push-based/cpu-prof/blob/main/packages/cpu-prof/docs/examples/minimal-cpu-profile-nodes-call-frame-source-location.cpuprofile)

```
{
  "nodes": [
    {
      "id": 1,
      "callFrame": {
        "functionName": "(root)",
        "scriptId": "0",
        "url": "",
        "lineNumber": -1,
        "columnNumber": -1
      },
      "children": [2]
    },
    {
      "id": 2,
      "callFrame": {
        "functionName": "work-1",
        "scriptId": "1",
        "url": "file:///1.js",
        "lineNumber": 92,
        "columnNumber": 19
      },
      "children": [3, 4]
    },
    {
      "id": 3,
      "callFrame": {
        "functionName": "child-work-1",
        "scriptId": "1",
        "url": "file:///1.js",
        "lineNumber": 12,
        "columnNumber": 34
      }
    },
    {
      "id": 4,
      "callFrame": {
        "functionName": "work-2",
        "scriptId": "2",
        "url": "file:///2.js",
        "lineNumber": 12,
        "columnNumber": 34
      }
    }
  ],
  "startTime": 1,
  "endTime": 40,
  "samples": [1, 3, 4, 1],
  "timeDeltas": [0, 10, 10, 10]
}
```

**DevTools Performance Tab:**

### URL and coloring

The `url` property in the `callFrame` object is crucial for visualizing a profile in DevTools. It determines the color-coding of frames in the flame chart, with frames from the same URL sharing the same color. This helps quickly identify which parts of your code are executing and how they relate to each other.

**Profile:** [minimal-cpu-profile-nodes-call-frame-url.cpuprofile](https://github.com/push-based/cpu-prof/blob/main/packages/cpu-prof/docs/examples/minimal-cpu-profile-nodes-call-frame-url.cpuprofile)

```
{
  "nodes": [
    {
      "id": 1,
      "callFrame": {
        "functionName": "(root)",
        "scriptId": "0",
        "url": "",
        "lineNumber": -1,
        "columnNumber": -1
      },
      "children": [2, 3, 4, 5, 6]
    },
    {
      "id": 2,
      "callFrame": {
        "functionName": "run",
        "scriptId": "2",
        "url": "file:///script-1.mjs",
        "lineNumber": 10,
        "columnNumber": 0
      },
      "parent": 1
    },
    {
      "id": 3,
      "callFrame": {
        "functionName": "run",
        "scriptId": "2",
        "url": "file:///run-script-3.mjs",
        "lineNumber": 11,
        "columnNumber": 1
      },
      "parent": 1
    },
    {
      "id": 4,
      "callFrame": {
        "functionName": "run",
        "scriptId": "2",
        "url": "file:///run-script-2.mjs",
        "lineNumber": 11,
        "columnNumber": 2
      },
      "parent": 1
    },
    {
      "id": 5,
      "callFrame": {
        "functionName": "run",
        "scriptId": "2",
        "url": "file:///run-script-3.mjs",
        "lineNumber": 12,
        "columnNumber": 4
      },
      "parent": 1
    },
    {
      "id": 6,
      "callFrame": {
        "functionName": "run",
        "scriptId": "2",
        "url": "file:///run-script-4.mjs",
        "lineNumber": 13,
        "columnNumber": 6
      },
      "parent": 1
    }
  ],
  "startTime": 1,
  "endTime": 70,
  "samples": [1, 2, 3, 4, 5, 6],
  "timeDeltas": [0, 10, 10, 10, 10, 10]
}
```

**DevTools Performance Tab:**

#### Synthetic and Internal Frames

Synthetic frames are not actual lines from your code but markers added by the V8 engine (the JavaScript engine in Chrome and Node.js). These frames provide important context about the execution environment and system-level operations.

A synthetic frame has `functionName` in parentheses to represent entry points, top-level script evaluation, idle time, or garbage collection cycles. Its `scriptId` is always `"0"`, the `url` is an empty string (`""`), and `lineNumber` and `columnNumber` are typically `-1`. These frames help distinguish between user code execution and runtime overhead in performance analysis.

What they represent:

**Frame Name**

**What It Means**

`(root)`

The very start of the profile session (entry point)

`(program)`

Your top-level script (code not inside any function)

`(idle)`

Time when nothing is happening — app is waiting

`(garbage collector)`

Time spent cleaning memory — V8 is reclaiming RAM

**Profile:** [minimal-cpu-profile-nodes-call-frame-synthetic-frames.cpuprofile](https://github.com/push-based/cpu-prof/blob/main/packages/cpu-prof/docs/examples/minimal-cpu-profile-nodes-call-frame-synthetic-frames.cpuprofile)

```
{
  "nodes": [
    {
      "id": 1,
      "callFrame": {
        "functionName": "(root)",
        "scriptId": "0",
        "url": "",
        "lineNumber": -1,
        "columnNumber": -1
      },
      "children": [2, 3, 5, 6]
    },
    {
      "id": 2,
      "callFrame": {
        "functionName": "(program)",
        "scriptId": "0",
        "url": "",
        "lineNumber": -1,
        "columnNumber": -1
      },
      "children": []
    },
    {
      "id": 3,
      "callFrame": {
        "functionName": "main-work",
        "scriptId": "2",
        "url": "file:///index.mjs",
        "lineNumber": 10,
        "columnNumber": 0
      },
      "children": [4]
    },
    {
      "id": 4,
      "callFrame": {
        "functionName": "child-work-1",
        "scriptId": "2",
        "url": "file:///index.mjs",
        "lineNumber": 11,
        "columnNumber": 2
      }
    },
    {
      "id": 5,
      "callFrame": {
        "functionName": "(garbage collector)",
        "scriptId": "0",
        "url": "",
        "lineNumber": -1,
        "columnNumber": -1
      },
      "children": []
    },
    {
      "id": 6,
      "callFrame": {
        "functionName": "(idle)",
        "scriptId": "0",
        "url": "",
        "lineNumber": -1,
        "columnNumber": -1
      },
      "children": []
    }
  ],
  "startTime": 1,
  "endTime": 700,
  "samples": [1, 2, 3, 4, 5, 6],
  "timeDeltas": [0, 100, 100, 100, 100, 100]
}
```

**DevTools Performance Tab:**

## Conclusion

Demystifying the core data structures behind CPU profiles is an important step toward effective performance analysis in Chrome DevTools. With this knowledge, you will find it easier to read and use actual profiling output in your daily work. The next article will show these concepts in action with real-world profiling examples and hands-on analysis.

If you need expert help with profiling or a full performance audit, our team is here to assist.
