UNPKG

@sussudio/platform

Version:

Internal APIs for VS Code's service injection the base services.

222 lines (221 loc) 6.2 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ /** * Recursive function that computes and caches the aggregate time for the * children of the computed now. */ const computeAggregateTime = (index, nodes) => { const row = nodes[index]; if (row.aggregateTime) { return row.aggregateTime; } let total = row.selfTime; for (const child of row.children) { total += computeAggregateTime(child, nodes); } return (row.aggregateTime = total); }; const ensureSourceLocations = (profile) => { let locationIdCounter = 0; const locationsByRef = new Map(); const getLocationIdFor = (callFrame) => { const ref = [ callFrame.functionName, callFrame.url, callFrame.scriptId, callFrame.lineNumber, callFrame.columnNumber, ].join(':'); const existing = locationsByRef.get(ref); if (existing) { return existing.id; } const id = locationIdCounter++; locationsByRef.set(ref, { id, callFrame, location: { lineNumber: callFrame.lineNumber + 1, columnNumber: callFrame.columnNumber + 1, // source: { // name: maybeFileUrlToPath(callFrame.url), // path: maybeFileUrlToPath(callFrame.url), // sourceReference: 0, // }, }, }); return id; }; for (const node of profile.nodes) { node.locationId = getLocationIdFor(node.callFrame); node.positionTicks = node.positionTicks?.map((tick) => ({ ...tick, // weirdly, line numbers here are 1-based, not 0-based. The position tick // only gives line-level granularity, so 'mark' the entire range of source // code the tick refers to startLocationId: getLocationIdFor({ ...node.callFrame, lineNumber: tick.line - 1, columnNumber: 0, }), endLocationId: getLocationIdFor({ ...node.callFrame, lineNumber: tick.line, columnNumber: 0, }), })); } return [...locationsByRef.values()] .sort((a, b) => a.id - b.id) .map((l) => ({ locations: [l.location], callFrame: l.callFrame })); }; /** * Computes the model for the given profile. */ export const buildModel = (profile) => { if (!profile.timeDeltas || !profile.samples) { return { nodes: [], locations: [], samples: profile.samples || [], timeDeltas: profile.timeDeltas || [], // rootPath: profile.$vscode?.rootPath, duration: profile.endTime - profile.startTime, }; } const { samples, timeDeltas } = profile; const sourceLocations = ensureSourceLocations(profile); const locations = sourceLocations.map((l, id) => { const src = l.locations[0]; //getBestLocation(profile, l.locations); return { id, selfTime: 0, aggregateTime: 0, ticks: 0, // category: categorize(l.callFrame, src), callFrame: l.callFrame, src, }; }); const idMap = new Map(); const mapId = (nodeId) => { let id = idMap.get(nodeId); if (id === undefined) { id = idMap.size; idMap.set(nodeId, id); } return id; }; // 1. Created a sorted list of nodes. It seems that the profile always has // incrementing IDs, although they are just not initially sorted. const nodes = new Array(profile.nodes.length); for (let i = 0; i < profile.nodes.length; i++) { const node = profile.nodes[i]; // make them 0-based: const id = mapId(node.id); nodes[id] = { id, selfTime: 0, aggregateTime: 0, locationId: node.locationId, children: node.children?.map(mapId) || [], }; for (const child of node.positionTicks || []) { if (child.startLocationId) { locations[child.startLocationId].ticks += child.ticks; } } } for (const node of nodes) { for (const child of node.children) { nodes[child].parent = node.id; } } // 2. The profile samples are the 'bottom-most' node, the currently running // code. Sum of these in the self time. const duration = profile.endTime - profile.startTime; let lastNodeTime = duration - timeDeltas[0]; for (let i = 0; i < timeDeltas.length - 1; i++) { const d = timeDeltas[i + 1]; nodes[mapId(samples[i])].selfTime += d; lastNodeTime -= d; } // Add in an extra time delta for the last sample. `timeDeltas[0]` is the // time before the first sample, and the time of the last sample is only // derived (approximately) by the missing time in the sum of deltas. Save // some work by calculating it here. if (nodes.length) { nodes[mapId(samples[timeDeltas.length - 1])].selfTime += lastNodeTime; timeDeltas.push(lastNodeTime); } // 3. Add the aggregate times for all node children and locations for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; const location = locations[node.locationId]; location.aggregateTime += computeAggregateTime(i, nodes); location.selfTime += node.selfTime; } return { nodes, locations, samples: samples.map(mapId), timeDeltas, // rootPath: profile.$vscode?.rootPath, duration, }; }; export class BottomUpNode { location; parent; static root() { return new BottomUpNode({ id: -1, selfTime: 0, aggregateTime: 0, ticks: 0, callFrame: { functionName: '(root)', lineNumber: -1, columnNumber: -1, scriptId: '0', url: '', }, }); } children = {}; aggregateTime = 0; selfTime = 0; ticks = 0; childrenSize = 0; get id() { return this.location.id; } get callFrame() { return this.location.callFrame; } get src() { return this.location.src; } constructor(location, parent) { this.location = location; this.parent = parent; } addNode(node) { this.selfTime += node.selfTime; this.aggregateTime += node.aggregateTime; } } export const processNode = (aggregate, node, model, initialNode = node) => { let child = aggregate.children[node.locationId]; if (!child) { child = new BottomUpNode(model.locations[node.locationId], aggregate); aggregate.childrenSize++; aggregate.children[node.locationId] = child; } child.addNode(initialNode); if (node.parent) { processNode(child, model.nodes[node.parent], model, initialNode); } };