mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
213 lines
5.7 KiB
TypeScript
213 lines
5.7 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
const { decode_bytes } = require('@vscode/v8-heap-parser');
|
|
import { Code } from './code';
|
|
import { PlaywrightDriver } from './playwrightDriver';
|
|
|
|
export class Profiler {
|
|
constructor(private readonly code: Code) {
|
|
}
|
|
|
|
async checkObjectLeaks(classNames: string | string[], fn: () => Promise<void>): Promise<void> {
|
|
await this.code.driver.startCDP();
|
|
|
|
const classNamesArray = Array.isArray(classNames) ? classNames : [classNames];
|
|
const countsBefore = await getInstances(this.code.driver, classNamesArray);
|
|
|
|
await fn();
|
|
|
|
const countAfter = await getInstances(this.code.driver, classNamesArray);
|
|
const leaks: string[] = [];
|
|
for (const className of classNamesArray) {
|
|
const count = countAfter[className] ?? 0;
|
|
const countBefore = countsBefore[className] ?? 0;
|
|
if (count !== countBefore) {
|
|
leaks.push(`Leaked ${count - countBefore} ${className}`);
|
|
}
|
|
}
|
|
|
|
if (leaks.length > 0) {
|
|
throw new Error(leaks.join('\n'));
|
|
}
|
|
}
|
|
|
|
async checkHeapLeaks(classNames: string | string[], fn: () => Promise<void>): Promise<void> {
|
|
await this.code.driver.startCDP();
|
|
await fn();
|
|
|
|
const heapSnapshotAfter = await this.code.driver.takeHeapSnapshot();
|
|
const buff = Buffer.from(heapSnapshotAfter);
|
|
const graph = await decode_bytes(buff);
|
|
const counts: number[] = Array.from(graph.get_class_counts(classNames));
|
|
const leaks: string[] = [];
|
|
for (let i = 0; i < classNames.length; i++) {
|
|
if (counts[i] > 0) {
|
|
leaks.push(`Leaked ${counts[i]} ${classNames[i]}`);
|
|
}
|
|
}
|
|
|
|
if (leaks.length > 0) {
|
|
throw new Error(leaks.join('\n'));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copied from src/vs/base/common/uuid.ts
|
|
*/
|
|
export function generateUuid(): string {
|
|
// use `randomUUID` if possible
|
|
if (typeof crypto.randomUUID === 'function') {
|
|
// see https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto
|
|
// > Although crypto is available on all windows, the returned Crypto object only has one
|
|
// > usable feature in insecure contexts: the getRandomValues() method.
|
|
// > In general, you should use this API only in secure contexts.
|
|
|
|
return crypto.randomUUID.bind(crypto)();
|
|
}
|
|
|
|
// prep-work
|
|
const _data = new Uint8Array(16);
|
|
const _hex: string[] = [];
|
|
for (let i = 0; i < 256; i++) {
|
|
_hex.push(i.toString(16).padStart(2, '0'));
|
|
}
|
|
|
|
// get data
|
|
crypto.getRandomValues(_data);
|
|
|
|
// set version bits
|
|
_data[6] = (_data[6] & 0x0f) | 0x40;
|
|
_data[8] = (_data[8] & 0x3f) | 0x80;
|
|
|
|
// print as string
|
|
let i = 0;
|
|
let result = '';
|
|
result += _hex[_data[i++]];
|
|
result += _hex[_data[i++]];
|
|
result += _hex[_data[i++]];
|
|
result += _hex[_data[i++]];
|
|
result += '-';
|
|
result += _hex[_data[i++]];
|
|
result += _hex[_data[i++]];
|
|
result += '-';
|
|
result += _hex[_data[i++]];
|
|
result += _hex[_data[i++]];
|
|
result += '-';
|
|
result += _hex[_data[i++]];
|
|
result += _hex[_data[i++]];
|
|
result += '-';
|
|
result += _hex[_data[i++]];
|
|
result += _hex[_data[i++]];
|
|
result += _hex[_data[i++]];
|
|
result += _hex[_data[i++]];
|
|
result += _hex[_data[i++]];
|
|
result += _hex[_data[i++]];
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------------------------
|
|
* The MIT License (MIT)
|
|
* Copyright (c) 2023-present, Simon Siefke
|
|
*
|
|
* This code is derived from https://github.com/SimonSiefke/vscode-memory-leak-finder
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
const getInstances = async (driver: PlaywrightDriver, classNames: string[]): Promise<{ [key: string]: number }> => {
|
|
await driver.collectGarbage();
|
|
const objectGroup = `og:${generateUuid()}`;
|
|
const prototypeDescriptor = await driver.evaluate({
|
|
expression: 'Object.prototype',
|
|
returnByValue: false,
|
|
objectGroup,
|
|
});
|
|
const objects = await driver.queryObjects({
|
|
prototypeObjectId: prototypeDescriptor.result.objectId!,
|
|
objectGroup,
|
|
});
|
|
const fnResult1 = await driver.callFunctionOn({
|
|
functionDeclaration: `function(){
|
|
const objects = this
|
|
const classNames = ${JSON.stringify(classNames)}
|
|
|
|
const nativeConstructors = [
|
|
Object,
|
|
Array,
|
|
Function,
|
|
Set,
|
|
Map,
|
|
WeakMap,
|
|
WeakSet,
|
|
RegExp,
|
|
Node,
|
|
HTMLScriptElement,
|
|
DOMRectReadOnly,
|
|
DOMRect,
|
|
HTMLHtmlElement,
|
|
Node,
|
|
DOMTokenList,
|
|
HTMLUListElement,
|
|
HTMLStyleElement,
|
|
HTMLDivElement,
|
|
HTMLCollection,
|
|
FocusEvent,
|
|
Promise,
|
|
HTMLLinkElement,
|
|
HTMLLIElement,
|
|
HTMLAnchorElement,
|
|
HTMLSpanElement,
|
|
ArrayBuffer,
|
|
Uint16Array,
|
|
HTMLLabelElement,
|
|
TrustedTypePolicy,
|
|
Uint8Array,
|
|
Uint32Array,
|
|
HTMLHeadingElement,
|
|
MediaQueryList,
|
|
HTMLDocument,
|
|
TextDecoder,
|
|
TextEncoder,
|
|
HTMLInputElement,
|
|
HTMLCanvasElement,
|
|
HTMLIFrameElement,
|
|
Int32Array,
|
|
CSSStyleDeclaration
|
|
]
|
|
|
|
const isNativeConstructor = object => {
|
|
return nativeConstructors.includes(object.constructor) ||
|
|
object.constructor.name === 'AsyncFunction' ||
|
|
object.constructor.name === 'GeneratorFunction' ||
|
|
object.constructor.name === 'AsyncGeneratorFunction'
|
|
}
|
|
|
|
const isInstance = (object) => {
|
|
return object && !isNativeConstructor(object)
|
|
}
|
|
|
|
const instances = objects.filter(isInstance)
|
|
|
|
const counts = Object.create(null)
|
|
for(const instance of instances){
|
|
const name=instance.constructor.name
|
|
if(classNames.includes(name)){
|
|
counts[name]||= 0
|
|
counts[name]++
|
|
}
|
|
}
|
|
return counts
|
|
}`,
|
|
objectId: objects.objects.objectId,
|
|
returnByValue: true,
|
|
objectGroup,
|
|
});
|
|
|
|
const returnObject = fnResult1.result.value;
|
|
await driver.releaseObjectGroup({ objectGroup: objectGroup });
|
|
return returnObject;
|
|
};
|