@sussudio/platform
Version:
Internal APIs for VS Code's service injection the base services.
130 lines (129 loc) • 4.36 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { basename } from '@sussudio/base/common/path.mjs';
import { TernarySearchTree } from '@sussudio/base/common/ternarySearchTree.mjs';
import { URI } from '@sussudio/base/common/uri.mjs';
import { Utils } from '../common/profiling.mjs';
import { buildModel, BottomUpNode, processNode } from '../common/profilingModel.mjs';
export function create() {
return new ProfileAnalysisWorker();
}
class ProfileAnalysisWorker {
_requestHandlerBrand;
analyseBottomUp(profile) {
if (!Utils.isValidProfile(profile)) {
return { kind: 1 /* ProfilingOutput.Irrelevant */, samples: [] };
}
const model = buildModel(profile);
const samples = bottomUp(model, 5, false).filter((s) => !s.isSpecial);
if (samples.length === 0 || samples[0].percentage < 10) {
// ignore this profile because 90% of the time is spent inside "special" frames
// like idle, GC, or program
return { kind: 1 /* ProfilingOutput.Irrelevant */, samples: [] };
}
return { kind: 2 /* ProfilingOutput.Interesting */, samples };
}
analyseByUrlCategory(profile, categories) {
// build search tree
const searchTree = TernarySearchTree.forUris();
searchTree.fill(categories);
// cost by categories
const model = buildModel(profile);
const aggegrateByCategory = new Map();
for (const node of model.nodes) {
const loc = model.locations[node.locationId];
let category;
try {
category = searchTree.findSubstr(URI.parse(loc.callFrame.url));
} catch {
// ignore
}
if (!category) {
category = printCallFrame(loc.callFrame, false);
}
const value = aggegrateByCategory.get(category) ?? 0;
const newValue = value + node.selfTime;
aggegrateByCategory.set(category, newValue);
}
const result = [];
for (const [key, value] of aggegrateByCategory) {
result.push([key, value]);
}
return result;
}
}
function isSpecial(call) {
return call.functionName.startsWith('(') && call.functionName.endsWith(')');
}
function printCallFrame(frame, fullPaths) {
let result = frame.functionName || '(anonymous)';
if (frame.url) {
result += '#';
result += fullPaths ? frame.url : basename(frame.url);
if (frame.lineNumber >= 0) {
result += ':';
result += frame.lineNumber + 1;
}
if (frame.columnNumber >= 0) {
result += ':';
result += frame.columnNumber + 1;
}
}
return result;
}
function getHeaviestLocationIds(model, topN) {
const stackSelfTime = {};
for (const node of model.nodes) {
stackSelfTime[node.locationId] = (stackSelfTime[node.locationId] || 0) + node.selfTime;
}
const locationIds = Object.entries(stackSelfTime)
.sort(([, a], [, b]) => b - a)
.slice(0, topN)
.map(([locationId]) => Number(locationId));
return new Set(locationIds);
}
function bottomUp(model, topN, fullPaths = false) {
const root = BottomUpNode.root();
const locationIds = getHeaviestLocationIds(model, topN);
for (const node of model.nodes) {
if (locationIds.has(node.locationId)) {
processNode(root, node, model);
root.addNode(node);
}
}
const result = Object.values(root.children)
.sort((a, b) => b.selfTime - a.selfTime)
.slice(0, topN);
const samples = [];
for (const node of result) {
const sample = {
selfTime: Math.round(node.selfTime / 1000),
totalTime: Math.round(node.aggregateTime / 1000),
location: printCallFrame(node.callFrame, fullPaths),
url: node.callFrame.url,
caller: [],
percentage: Math.round(node.selfTime / (model.duration / 100)),
isSpecial: isSpecial(node.callFrame),
};
// follow the heaviest caller paths
const stack = [node];
while (stack.length) {
const node = stack.pop();
let top;
for (const candidate of Object.values(node.children)) {
if (!top || top.selfTime < candidate.selfTime) {
top = candidate;
}
}
if (top) {
const percentage = Math.round(top.selfTime / (node.selfTime / 100));
sample.caller.push({ percentage, location: printCallFrame(top.callFrame, false) });
stack.push(top);
}
}
samples.push(sample);
}
return samples;
}