Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 74 additions & 22 deletions ts/src/profile-serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ import {
TimeProfileNode,
} from './v8-types';

export const NON_JS_THREADS_FUNCTION_NAME = '(non-JS threads)';
export const NON_JS_THREADS_FUNCTION_NAME = 'Non JS threads activity';
export const GARBAGE_COLLECTION_FUNCTION_NAME = 'Garbage Collection';

/**
* A stack of function IDs.
Expand Down Expand Up @@ -110,7 +111,6 @@ function serialize<T extends ProfileNode>(
if (ignoreSamplesPath && node.scriptName.indexOf(ignoreSamplesPath) > -1) {
continue;
}
if (node.name === '(idle)' || node.name === '(program)') continue;
const stack = entry.stack;
const location = getLocation(node, sourceMapper);
stack.unshift(location.id as number);
Expand Down Expand Up @@ -262,6 +262,75 @@ function computeTotalHitCount(root: TimeProfileNode): number {
);
}

/** Perform some modifications on time profile:
* - Add non-JS thread activity node if available
* - Remove `(idle)` and `(program)` nodes
* - Convert `(garbage collector)` node to `Garbage Collection`
* - Put `non-JS thread activity` node and `Garbage Collection` under a top level `Node.js` node
* This function does not change the input profile.
*/
function updateTimeProfile(prof: TimeProfile): TimeProfile {
const newTopLevelChildren: TimeProfileNode[] = [];

let runtimeNode: TimeProfileNode | undefined;

function getRuntimeNode(): TimeProfileNode {
if (!runtimeNode) {
runtimeNode = {
name: 'Node.js',
scriptName: '',
scriptId: 0,
lineNumber: 0,
columnNumber: 0,
children: [],
hitCount: 0,
};
newTopLevelChildren.push(runtimeNode);
}
return runtimeNode;
}

for (const child of prof.topDownRoot.children as TimeProfileNode[]) {
if (child.name === '(idle)' || child.name === '(program)') {
continue;
}
if (child.name === '(garbage collector)') {
// Create a new node to avoid modifying the input one
const newChild: TimeProfileNode = {
...child,
name: GARBAGE_COLLECTION_FUNCTION_NAME,
};
getRuntimeNode().children.push(newChild);
} else {
newTopLevelChildren.push(child);
}
}

if (prof.hasCpuTime && prof.nonJSThreadsCpuTime) {
const node: TimeProfileNode = {
name: NON_JS_THREADS_FUNCTION_NAME,
scriptName: '',
scriptId: 0,
lineNumber: 0,
columnNumber: 0,
children: [],
hitCount: 0, // 0 because this should not be accounted for wall time
contexts: [
{
context: {},
timestamp: BigInt(0),
cpuTime: prof.nonJSThreadsCpuTime,
},
],
};
getRuntimeNode().children.push(node);
}
return {
...prof,
topDownRoot: {...prof.topDownRoot, children: newTopLevelChildren},
};
}

/**
* Converts v8 time profile into into a profile proto.
* (https://github.com/google/pprof/blob/master/proto/profile.proto)
Expand Down Expand Up @@ -360,28 +429,11 @@ export function serializeTimeProfile(
period: intervalNanos,
};

if (prof.hasCpuTime && prof.nonJSThreadsCpuTime) {
const node: TimeProfileNode = {
name: NON_JS_THREADS_FUNCTION_NAME,
scriptName: '',
scriptId: 0,
lineNumber: 0,
columnNumber: 0,
children: [],
hitCount: 0, // 0 because this should not be accounted for wall time
contexts: [
{
context: {},
timestamp: BigInt(0),
cpuTime: prof.nonJSThreadsCpuTime,
},
],
};
prof.topDownRoot.children.push(node);
}
const updatedProf = updateTimeProfile(prof);

serialize(
profile,
prof.topDownRoot,
updatedProf.topDownRoot,
appendTimeEntryToSamples,
stringTable,
undefined,
Expand Down
7 changes: 6 additions & 1 deletion ts/src/time-profiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import delay from 'delay';

import {
serializeTimeProfile,
GARBAGE_COLLECTION_FUNCTION_NAME,
NON_JS_THREADS_FUNCTION_NAME,
} from './profile-serializer';
import {SourceMapper} from './sourcemapper/sourcemapper';
Expand Down Expand Up @@ -171,5 +172,9 @@ export function v8ProfilerStuckEventLoopDetected() {
return gV8ProfilerStuckEventLoopDetected;
}

export const constants = {kSampleCount, NON_JS_THREADS_FUNCTION_NAME};
export const constants = {
kSampleCount,
GARBAGE_COLLECTION_FUNCTION_NAME,
NON_JS_THREADS_FUNCTION_NAME,
};
export {getNativeThreadId};
4 changes: 2 additions & 2 deletions ts/test/profiles-for-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ const timeNode2 = {
children: [timeLeaf3],
};

const timeRoot = {
const timeRoot = Object.freeze({
name: '(root)',
scriptName: 'root',
scriptId: 0,
lineNumber: 0,
columnNumber: 0,
hitCount: 0,
children: [timeNode1, timeNode2],
};
});

export const v8TimeProfile: TimeProfile = Object.freeze({
startTime: 0,
Expand Down
2 changes: 1 addition & 1 deletion ts/test/test-profile-serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ describe('profile-serializer', () => {
const heapProfileOut = serializeHeapProfile(v8HeapProfile, 0, 512 * 1024);
assert.deepEqual(heapProfileOut, heapProfile);
});
it('should produce expected profile when there is anyonmous function', () => {
it('should produce expected profile when there is anonymous function', () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const heapProfileOut = serializeHeapProfile(
v8AnonymousFunctionHeapProfile,
0,
Expand Down