@oaklean/profiler-core
Version:
Part of the @oaklean suite. It provides all basic functions to work with the `.oak` file format. It allows parsing the `.oak` file format as well as tools for analyzing the measurement values. It also provides all necessary capabilities required for prec
186 lines • 15.3 kB
JavaScript
;
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildModel = void 0;
const model_1 = require("../common/model");
const getBestLocation_1 = require("../getBestLocation");
const path_1 = require("../path");
/**
* 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);
};
/**
* Ensures that all profile nodes have a location ID, setting them if they
* aren't provided by default.
*/
const ensureSourceLocations = (profile) => {
var _a;
if (profile.$vscode) {
return profile.$vscode.locations; // profiles we generate are already good
}
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: (0, path_1.maybeFileUrlToPath)(callFrame.url),
path: (0, path_1.maybeFileUrlToPath)(callFrame.url),
sourceReference: 0
}
}
});
return id;
};
for (const node of profile.nodes) {
node.locationId = getLocationIdFor(node.callFrame);
node.positionTicks = (_a = node.positionTicks) === null || _a === void 0 ? void 0 : _a.map((tick) => (Object.assign(Object.assign({}, 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(Object.assign(Object.assign({}, node.callFrame), { lineNumber: tick.line - 1, columnNumber: 0 })), endLocationId: getLocationIdFor(Object.assign(Object.assign({}, 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.
*/
const buildModel = (profile) => {
var _a, _b, _c;
if (!profile.timeDeltas || !profile.samples) {
return {
nodes: [],
locations: [],
samples: profile.samples || [],
timeDeltas: profile.timeDeltas || [],
rootPath: (_a = profile.$vscode) === null || _a === void 0 ? void 0 : _a.rootPath,
duration: profile.endTime - profile.startTime
};
}
const { samples } = profile;
const timeDeltas = [...profile.timeDeltas];
const sourceLocations = ensureSourceLocations(profile);
const locations = sourceLocations.map((l, id) => {
const src = (0, getBestLocation_1.getBestLocation)(profile, l.locations);
return {
id,
selfTime: 0,
aggregateTime: 0,
ticks: 0,
category: (0, model_1.categorize)(l.callFrame, src),
callFrame: Object.assign(Object.assign({}, l.callFrame), { scriptId: l.callFrame.scriptId.toString() }),
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: ((_b = node.children) === null || _b === void 0 ? void 0 : _b.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
const calcAggregatedTimeOfLocations = (node, visited) => {
const location = locations[node.locationId];
let selfAdded = false;
if (!visited.has(node.locationId)) {
// node is not an ancestor of a node with the same location
visited.add(node.locationId);
selfAdded = true;
location.aggregateTime += computeAggregateTime(node.id, nodes);
}
location.selfTime += node.selfTime;
for (const child of node.children) {
calcAggregatedTimeOfLocations(nodes[child], visited);
}
if (selfAdded) {
visited.delete(node.locationId);
}
};
calcAggregatedTimeOfLocations(nodes[0], new Set());
return {
nodes,
locations,
samples: samples.map(mapId),
timeDeltas,
rootPath: (_c = profile.$vscode) === null || _c === void 0 ? void 0 : _c.rootPath,
duration
};
};
exports.buildModel = buildModel;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9kZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9saWIvdnNjb2RlLWpzLXByb2ZpbGUtY29yZS9zcmMvY3B1L21vZGVsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7NERBRTREOzs7QUFHNUQsMkNBQW1EO0FBRW5ELHdEQUFvRDtBQUVwRCxrQ0FBNEM7QUFpRDVDOzs7R0FHRztBQUNILE1BQU0sb0JBQW9CLEdBQUcsQ0FDNUIsS0FBYSxFQUNiLEtBQXNCLEVBQ2IsRUFBRTtJQUNYLE1BQU0sR0FBRyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQTtJQUN4QixJQUFJLEdBQUcsQ0FBQyxhQUFhLEVBQUUsQ0FBQztRQUN2QixPQUFPLEdBQUcsQ0FBQyxhQUFhLENBQUE7SUFDekIsQ0FBQztJQUVELElBQUksS0FBSyxHQUFHLEdBQUcsQ0FBQyxRQUFRLENBQUE7SUFDeEIsS0FBSyxNQUFNLEtBQUssSUFBSSxHQUFHLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDbEMsS0FBSyxJQUFJLG9CQUFvQixDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBQTtJQUM1QyxDQUFDO0lBRUQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxhQUFhLEdBQUcsS0FBSyxDQUFDLENBQUE7QUFDbkMsQ0FBQyxDQUFBO0FBRUQ7OztHQUdHO0FBQ0gsTUFBTSxxQkFBcUIsR0FBRyxDQUM3QixPQUF1QixFQUNjLEVBQUU7O0lBQ3ZDLElBQUksT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3JCLE9BQU8sT0FBTyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUEsQ0FBQyx3Q0FBd0M7SUFDMUUsQ0FBQztJQUVELElBQUksaUJBQWlCLEdBQUcsQ0FBQyxDQUFBO0lBQ3pCLE1BQU0sY0FBYyxHQUFHLElBQUksR0FBRyxFQUczQixDQUFBO0lBRUgsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLFNBQWdDLEVBQUUsRUFBRTtRQUM3RCxNQUFNLEdBQUcs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