@memlab/core
Advanced tools
Comparing version 1.1.20 to 1.1.21
@@ -29,2 +29,4 @@ /** | ||
/** @internal */ | ||
export { default as runInfoUtils } from './lib/RunInfoUtils'; | ||
/** @internal */ | ||
export * from './lib/FileManager'; | ||
@@ -31,0 +33,0 @@ /** @internal */ |
@@ -38,3 +38,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.TraceFinder = exports.MultiIterationSeqClustering = exports.SequentialClustering = exports.EvaluationMetric = exports.NormalizedTrace = exports.leakClusterLogger = exports.ProcessManager = exports.modes = exports.memoryBarChart = exports.constant = exports.analysis = exports.browserInfo = exports.serializer = exports.fileManager = exports.utils = exports.BaseOption = exports.info = exports.config = exports.registerPackage = void 0; | ||
exports.TraceFinder = exports.MultiIterationSeqClustering = exports.SequentialClustering = exports.EvaluationMetric = exports.NormalizedTrace = exports.leakClusterLogger = exports.ProcessManager = exports.modes = exports.memoryBarChart = exports.constant = exports.analysis = exports.browserInfo = exports.serializer = exports.runInfoUtils = exports.fileManager = exports.utils = exports.BaseOption = exports.info = exports.config = exports.registerPackage = void 0; | ||
const path_1 = __importDefault(require("path")); | ||
@@ -71,2 +71,5 @@ const PackageInfoLoader_1 = require("./lib/PackageInfoLoader"); | ||
/** @internal */ | ||
var RunInfoUtils_1 = require("./lib/RunInfoUtils"); | ||
Object.defineProperty(exports, "runInfoUtils", { enumerable: true, get: function () { return __importDefault(RunInfoUtils_1).default; } }); | ||
/** @internal */ | ||
__exportStar(require("./lib/FileManager"), exports); | ||
@@ -73,0 +76,0 @@ /** @internal */ |
@@ -13,2 +13,3 @@ /** | ||
plotMemoryBarChart(options?: PlotMemoryOptions): void; | ||
private isPlotDataValid; | ||
private loadPlotDataFromTabsOrder; | ||
@@ -15,0 +16,0 @@ private loadPlotDataFromWorkDir; |
@@ -33,3 +33,3 @@ /** | ||
} | ||
if (plotData.length === 0) { | ||
if (!this.isPlotDataValid(plotData)) { | ||
if (Config_1.default.verbose) { | ||
@@ -61,2 +61,17 @@ Console_1.default.warning('no memory usage data to plot'); | ||
} | ||
isPlotDataValid(plotData) { | ||
if (plotData.length === 0) { | ||
return false; | ||
} | ||
let isEntryValueAllZero = true; | ||
for (const entry of plotData) { | ||
if (entry.length !== 2) { | ||
return false; | ||
} | ||
if (entry[1] !== 0) { | ||
isEntryValueAllZero = false; | ||
} | ||
} | ||
return !isEntryValueAllZero; | ||
} | ||
loadPlotDataFromTabsOrder(tabsOrder) { | ||
@@ -87,3 +102,3 @@ for (const tab of tabsOrder) { | ||
// plot data for a single run | ||
if (!options.controlWorkDir && !options.treatmentWorkDir) { | ||
if (!options.controlWorkDirs && !options.treatmentWorkDir) { | ||
return this.loadPlotDataFromWorkDir(options); | ||
@@ -93,3 +108,3 @@ } | ||
const controlPlotData = this.loadPlotDataFromWorkDir({ | ||
workDir: options.controlWorkDir, | ||
workDir: options.controlWorkDirs && options.controlWorkDirs[0], | ||
}); | ||
@@ -96,0 +111,0 @@ const testPlotData = this.loadPlotDataFromWorkDir({ |
@@ -102,2 +102,3 @@ /** | ||
externalCookiesFile: Optional<string>; | ||
extraRunInfoMap: Map<string, string>; | ||
heapConfig: Optional<IHeapConfig>; | ||
@@ -229,2 +230,3 @@ puppeteerConfig: LaunchOptions & BrowserLaunchArgumentOptions & BrowserConnectOptions; | ||
}): void; | ||
setRunInfo(key: string, value: string): void; | ||
private removeFromSet; | ||
@@ -231,0 +233,0 @@ private addToSet; |
@@ -179,2 +179,8 @@ "use strict"; | ||
this.seqClusteringIsRandomChunks = false; | ||
// extra E2E run info (other than the fields defined in | ||
// RunMetaInfo like app, interaction, browserInfo). | ||
// Information saved in this map will be | ||
// auto-serialized to run-meta.json when the file is saved | ||
// and auto-deserialized from run-meta.json when the file is loaded | ||
this.extraRunInfoMap = new Map(); | ||
} | ||
@@ -511,2 +517,5 @@ // initialize configurable parameters | ||
} | ||
setRunInfo(key, value) { | ||
this.extraRunInfoMap.set(key, value); | ||
} | ||
removeFromSet(set, list) { | ||
@@ -513,0 +522,0 @@ for (const v of list) { |
@@ -13,2 +13,4 @@ /** | ||
/** @internal */ | ||
export declare function joinAndProcessDir(options: FileOption, ...args: AnyValue[]): string; | ||
/** @internal */ | ||
export declare class FileManager { | ||
@@ -79,2 +81,3 @@ private memlabConfigCache; | ||
createDefaultVisitOrderMetaFile(options?: FileOption): void; | ||
createDefaultVisitOrderMetaFileWithSingleSnapshot(options: FileOption | undefined, snapshotFile: string): void; | ||
initDirs(config: MemLabConfig, options?: FileOption): void; | ||
@@ -81,0 +84,0 @@ } |
@@ -15,3 +15,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.FileManager = void 0; | ||
exports.FileManager = exports.joinAndProcessDir = void 0; | ||
const fs_extra_1 = __importDefault(require("fs-extra")); | ||
@@ -23,2 +23,3 @@ const os_1 = __importDefault(require("os")); | ||
const Utils_1 = __importDefault(require("./Utils")); | ||
/** @internal */ | ||
function joinAndProcessDir(options, ...args) { | ||
@@ -39,2 +40,3 @@ const filepath = path_1.default.join(...args); | ||
} | ||
exports.joinAndProcessDir = joinAndProcessDir; | ||
/** @internal */ | ||
@@ -316,2 +318,5 @@ class FileManager { | ||
isDirectory(file) { | ||
if (!fs_extra_1.default.existsSync(file)) { | ||
return false; | ||
} | ||
const stats = fs_extra_1.default.statSync(file); | ||
@@ -363,2 +368,6 @@ return stats.isDirectory(); | ||
// First, get the meta file for leak detection in a single heap snapshot | ||
this.createDefaultVisitOrderMetaFileWithSingleSnapshot(options, snapshotFile); | ||
} | ||
createDefaultVisitOrderMetaFileWithSingleSnapshot(options = FileManager.defaultFileOption, snapshotFile) { | ||
const snapshotSeqMetaFile = this.getSnapshotSequenceMetaFile(options); | ||
const codeDataDir = this.getCodeDataDir(); | ||
@@ -365,0 +374,0 @@ const singleSnapshotMetaFile = path_1.default.join(codeDataDir, 'visit-order-single-snapshot.json'); |
@@ -19,2 +19,6 @@ /** | ||
}; | ||
declare type GetTraceOptions = { | ||
workDir?: string; | ||
printConsoleOnly?: boolean; | ||
}; | ||
declare class MemoryAnalyst { | ||
@@ -54,3 +58,3 @@ checkLeak(): Promise<ISerializedInfo[]>; | ||
}): Promise<void>; | ||
dumpPathByNodeId(leakedIdSet: HeapNodeIdSet, snapshot: IHeapSnapshot, nodeIdsInSnapshots: Array<HeapNodeIdSet>, id: number, pathLoaderFile: string, summaryFile: string, options?: WorkDirOptions): void; | ||
dumpPathByNodeId(leakedIdSet: HeapNodeIdSet, snapshot: IHeapSnapshot, nodeIdsInSnapshots: Array<HeapNodeIdSet>, id: number, pathLoaderFile: string, summaryFile: string, options?: GetTraceOptions): void; | ||
} | ||
@@ -57,0 +61,0 @@ declare const _default: MemoryAnalyst; |
@@ -37,2 +37,3 @@ /** | ||
const MLTraceSimilarityStrategy_1 = __importDefault(require("../trace-cluster/strategies/MLTraceSimilarityStrategy")); | ||
const LeakTraceFilter_1 = require("./trace-filters/LeakTraceFilter"); | ||
class MemoryAnalyst { | ||
@@ -48,5 +49,5 @@ checkLeak() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const controlSnapshotDir = FileManager_1.default.getCurDataDir({ | ||
workDir: options.controlWorkDir, | ||
}); | ||
const controlSnapshotDirs = options.controlWorkDirs.map(controlWorkDir => FileManager_1.default.getCurDataDir({ | ||
workDir: controlWorkDir, | ||
})); | ||
const treatmentSnapshotDir = FileManager_1.default.getCurDataDir({ | ||
@@ -56,3 +57,3 @@ workDir: options.treatmentWorkDir, | ||
// check control working dir | ||
Utils_1.default.checkSnapshots({ snapshotDir: controlSnapshotDir }); | ||
controlSnapshotDirs.forEach(controlSnapshotDir => Utils_1.default.checkSnapshots({ snapshotDir: controlSnapshotDir })); | ||
// check treatment working dir | ||
@@ -69,19 +70,24 @@ Utils_1.default.checkSnapshots({ snapshotDir: treatmentSnapshotDir }); | ||
Config_1.default.dumpNodeInfo = false; | ||
// diff snapshots and get control raw paths | ||
let snapshotDiff = yield this.diffSnapshots({ | ||
loadAllSnapshots: true, | ||
workDir: options.controlWorkDir, | ||
}); | ||
const controlLeakPaths = this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: options.controlWorkDir }); | ||
const controlSnapshot = snapshotDiff.snapshot; | ||
// diff snapshots from control dirs and get control raw paths array | ||
const controlSnapshots = []; | ||
const leakPathsFromControlRuns = []; | ||
for (const controlWorkDir of options.controlWorkDirs) { | ||
const snapshotDiff = yield this.diffSnapshots({ | ||
loadAllSnapshots: true, | ||
workDir: controlWorkDir, | ||
}); | ||
leakPathsFromControlRuns.push(this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: controlWorkDir })); | ||
controlSnapshots.push(snapshotDiff.snapshot); | ||
} | ||
// diff snapshots and get treatment raw paths | ||
snapshotDiff = yield this.diffSnapshots({ | ||
const snapshotDiff = yield this.diffSnapshots({ | ||
loadAllSnapshots: true, | ||
workDir: options.treatmentWorkDir, | ||
}); | ||
const treatmentLeakPaths = this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: options.controlWorkDir }); | ||
const treatmentLeakPaths = this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: options.treatmentWorkDir }); | ||
const treatmentSnapshot = snapshotDiff.snapshot; | ||
Console_1.default.topLevel(`${controlLeakPaths.length} traces from control group`); | ||
const controlPathCounts = JSON.stringify(leakPathsFromControlRuns.map(leakPaths => leakPaths.length)); | ||
Console_1.default.topLevel(`${controlPathCounts} traces from control group`); | ||
Console_1.default.topLevel(`${treatmentLeakPaths.length} traces from treatment group`); | ||
const result = TraceBucket_1.default.clusterControlTreatmentPaths(controlLeakPaths, controlSnapshot, treatmentLeakPaths, treatmentSnapshot, Utils_1.default.aggregateDominatorMetrics, { | ||
const result = TraceBucket_1.default.clusterControlTreatmentPaths(leakPathsFromControlRuns, controlSnapshots, treatmentLeakPaths, treatmentSnapshot, Utils_1.default.aggregateDominatorMetrics, { | ||
strategy: Config_1.default.isMLClustering | ||
@@ -93,4 +99,5 @@ ? new MLTraceSimilarityStrategy_1.default() | ||
yield this.serializeClusterUpdate(result.treatmentOnlyClusters); | ||
// TODO (lgong): log leak traces | ||
return []; | ||
// serialize JSON file with detailed leak trace information | ||
const treatmentOnlyPaths = result.treatmentOnlyClusters.map(c => c.path); | ||
return LeakTraceDetailsLogger_1.default.logTraces(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, snapshotDiff.listOfLeakedHeapNodeIdSet, treatmentOnlyPaths, Config_1.default.traceJsonOutDir); | ||
}); | ||
@@ -132,3 +139,3 @@ } | ||
} | ||
this.dumpPathByNodeId(snapshotLeakedHeapNodeIdSet, snapshot, nodeIdsInSnapshots, Config_1.default.focusFiberNodeId, Config_1.default.viewJsonFile, Config_1.default.singleReportSummary); | ||
this.dumpPathByNodeId(snapshotLeakedHeapNodeIdSet, snapshot, nodeIdsInSnapshots, Config_1.default.focusFiberNodeId, Config_1.default.viewJsonFile, Config_1.default.singleReportSummary, { printConsoleOnly: true }); | ||
}); | ||
@@ -357,2 +364,3 @@ } | ||
this.filterLeakedObjects(leakedNodeIds, snapshot); | ||
const leakTraceFilter = new LeakTraceFilter_1.LeakTraceFilter(); | ||
const nodeIdInPaths = new Set(); | ||
@@ -369,3 +377,4 @@ const paths = []; | ||
const p = finder.getPathToGCRoots(snapshot, node); | ||
if (!p || !Utils_1.default.isInterestingPath(p)) { | ||
if (p == null || | ||
!leakTraceFilter.filter(p, { config: Config_1.default, leakedNodeIds, snapshot })) { | ||
return; | ||
@@ -462,6 +471,9 @@ } | ||
LeakTraceDetailsLogger_1.default.logTrace(leakedIdSet, snapshot, nodeIdsInSnapshots, path, pathLoaderFile); | ||
let pathSummary = Serializer_1.default.summarizePath(path, nodeIdInPaths, snapshot, { color: true }); | ||
Console_1.default.topLevel(pathSummary); | ||
if (options.printConsoleOnly) { | ||
return; | ||
} | ||
const tabsOrder = Utils_1.default.loadTabsOrder(FileManager_1.default.getSnapshotSequenceMetaFile(options)); | ||
const interactionSummary = Serializer_1.default.summarizeTabsOrder(tabsOrder); | ||
let pathSummary = Serializer_1.default.summarizePath(path, nodeIdInPaths, snapshot, { color: true }); | ||
Console_1.default.topLevel(pathSummary); | ||
pathSummary = Serializer_1.default.summarizePath(path, nodeIdInPaths, snapshot); | ||
@@ -468,0 +480,0 @@ const summary = `Page Interaction: \n${interactionSummary}\n\n` + |
@@ -10,5 +10,5 @@ /** | ||
*/ | ||
import type { HaltOrThrowOptions, HeapNodeIdSet, ShellOptions } from './Types'; | ||
import type { HaltOrThrowOptions, HeapNodeIdSet, ShellOptions, StringRecord } from './Types'; | ||
import type { Browser, Page } from 'puppeteer'; | ||
import type { AnyAyncFunction, AnyOptions, E2EStepInfo, IHeapSnapshot, IHeapNode, IHeapEdge, IScenario, ILeakFilter, LeakTracePathItem, RunMetaInfo, RawHeapSnapshot, Nullable, Optional } from './Types'; | ||
import type { AnyAyncFunction, AnyOptions, E2EStepInfo, IHeapSnapshot, IHeapNode, IHeapEdge, IScenario, ILeakFilter, LeakTracePathItem, RawHeapSnapshot, Nullable, Optional } from './Types'; | ||
declare function isHermesInternalObject(node: IHeapNode): boolean; | ||
@@ -18,2 +18,4 @@ declare function isStackTraceFrame(node: IHeapNode): boolean; | ||
declare function isDetachedFiberNode(node: IHeapNode): boolean; | ||
declare function isDOMInternalNode(node: Optional<IHeapNode>): boolean; | ||
declare function isGlobalHandlesNode(node: Optional<IHeapNode>): boolean; | ||
declare function isDetachedDOMNode(node: Optional<IHeapNode>, args?: AnyOptions): boolean; | ||
@@ -28,2 +30,5 @@ declare function isWeakMapEdge(edge: IHeapEdge): boolean; | ||
declare function isDOMNodeIncomplete(node: IHeapNode): boolean; | ||
declare function isXMLDocumentNode(node: IHeapNode): boolean; | ||
declare function isHTMLDocumentNode(node: IHeapNode): boolean; | ||
declare function isDOMTextNode(node: IHeapNode): boolean; | ||
declare function isRootNode(node: IHeapNode, opt?: AnyOptions): boolean; | ||
@@ -70,8 +75,4 @@ declare function isDirectPropEdge(edge: IHeapEdge): boolean; | ||
declare function hasOnlyWeakReferrers(node: IHeapNode): boolean; | ||
declare function getRunMetaFilePath(): string; | ||
declare function loadRunMetaInfo(metaFile?: Optional<string>): RunMetaInfo; | ||
declare function loadTargetInfoFromRunMeta(): void; | ||
declare function getSnapshotSequenceFilePath(): string; | ||
declare function loadTabsOrder(metaFile?: Optional<string>): E2EStepInfo[]; | ||
declare function isInterestingPath(p: LeakTracePathItem): boolean; | ||
declare function isObjectNode(node: IHeapNode): boolean; | ||
@@ -136,2 +137,4 @@ declare function isPlainJSObjectNode(node: IHeapNode): boolean; | ||
declare function getNumberAtPercentile(arr: number[], percentile: number): number; | ||
declare function mapToObject(map: Map<string, string>): StringRecord; | ||
declare function objectToMap(object: StringRecord): Map<string, string>; | ||
declare const _default: { | ||
@@ -142,5 +145,5 @@ aggregateDominatorMetrics: typeof aggregateDominatorMetrics; | ||
camelCaseToReadableString: typeof camelCaseToReadableString; | ||
checkIsChildOfParent: typeof checkIsChildOfParent; | ||
checkSnapshots: typeof checkSnapshots; | ||
checkUninstalledLibrary: typeof checkUninstalledLibrary; | ||
checkIsChildOfParent: typeof checkIsChildOfParent; | ||
closePuppeteer: typeof closePuppeteer; | ||
@@ -154,9 +157,10 @@ dumpSnapshot: typeof dumpSnapshot; | ||
getAllDominators: typeof getAllDominators; | ||
getBooleanNodeValue: typeof getBooleanNodeValue; | ||
getClosureSourceUrl: typeof getClosureSourceUrl; | ||
getConditionalDominatorIds: typeof getConditionalDominatorIds; | ||
getEdgeByNameAndType: typeof getEdgeByNameAndType; | ||
getError: typeof getError; | ||
getEdgeByNameAndType: typeof getEdgeByNameAndType; | ||
getLastNodeId: typeof getLastNodeId; | ||
getLeakTracePathLength: typeof getLeakTracePathLength; | ||
getLeakedNode: typeof getLeakedNode; | ||
getLeakTracePathLength: typeof getLeakTracePathLength; | ||
getNodesIdSet: typeof getNodesIdSet; | ||
@@ -169,13 +173,12 @@ getNumberAtPercentile: typeof getNumberAtPercentile; | ||
getRetainedSize: typeof getRetainedSize; | ||
getRunMetaFilePath: typeof getRunMetaFilePath; | ||
getScenarioName: typeof getScenarioName; | ||
getSingleSnapshotFileForAnalysis: typeof getSingleSnapshotFileForAnalysis; | ||
getSnapshotDirForAnalysis: typeof getSnapshotDirForAnalysis; | ||
getSnapshotFilePath: typeof getSnapshotFilePath; | ||
getSnapshotFilePathWithTabType: typeof getSnapshotFilePathWithTabType; | ||
getSnapshotFilesFromTabsOrder: typeof getSnapshotFilesFromTabsOrder; | ||
getSnapshotFilesInDir: typeof getSnapshotFilesInDir; | ||
getSnapshotFilesFromTabsOrder: typeof getSnapshotFilesFromTabsOrder; | ||
getSnapshotFromFile: typeof getSnapshotFromFile; | ||
getSnapshotNodeIdsFromFile: typeof getSnapshotNodeIdsFromFile; | ||
getSnapshotSequenceFilePath: typeof getSnapshotSequenceFilePath; | ||
getSnapshotFilePath: typeof getSnapshotFilePath; | ||
getSnapshotFilePathWithTabType: typeof getSnapshotFilePathWithTabType; | ||
getStringNodeValue: typeof getStringNodeValue; | ||
@@ -191,14 +194,17 @@ getToNodeByEdge: typeof getToNodeByEdge; | ||
isBlinkRootNode: typeof isBlinkRootNode; | ||
isDOMInternalNode: typeof isDOMInternalNode; | ||
isDOMNodeIncomplete: typeof isDOMNodeIncomplete; | ||
isDOMTextNode: typeof isDOMTextNode; | ||
isDebuggableNode: typeof isDebuggableNode; | ||
isDetachedDOMNode: typeof isDetachedDOMNode; | ||
isDetachedFiberNode: typeof isDetachedFiberNode; | ||
isDetachedDOMNode: typeof isDetachedDOMNode; | ||
isDirectPropEdge: typeof isDirectPropEdge; | ||
isDocumentDOMTreesRoot: typeof isDocumentDOMTreesRoot; | ||
isDOMNodeIncomplete: typeof isDOMNodeIncomplete; | ||
isEssentialEdge: typeof isEssentialEdge; | ||
isFiberNode: typeof isFiberNode; | ||
isFiberNodeDeletionsEdge: typeof isFiberNodeDeletionsEdge; | ||
isGlobalHandlesNode: typeof isGlobalHandlesNode; | ||
isHTMLDocumentNode: typeof isHTMLDocumentNode; | ||
isHermesInternalObject: typeof isHermesInternalObject; | ||
isHostRoot: typeof isHostRoot; | ||
isInterestingPath: typeof isInterestingPath; | ||
isMeaningfulEdge: typeof isMeaningfulEdge; | ||
@@ -222,9 +228,9 @@ isMeaningfulNode: typeof isMeaningfulNode; | ||
isWeakMapEdgeToValue: typeof isWeakMapEdgeToValue; | ||
isXMLDocumentNode: typeof isXMLDocumentNode; | ||
iterateChildFiberNodes: typeof iterateChildFiberNodes; | ||
iterateDescendantFiberNodes: typeof iterateDescendantFiberNodes; | ||
loadRunMetaInfo: typeof loadRunMetaInfo; | ||
loadLeakFilter: typeof loadLeakFilter; | ||
loadScenario: typeof loadScenario; | ||
loadTabsOrder: typeof loadTabsOrder; | ||
loadTargetInfoFromRunMeta: typeof loadTargetInfoFromRunMeta; | ||
mapToObject: typeof mapToObject; | ||
markAllDetachedFiberNode: typeof markAllDetachedFiberNode; | ||
@@ -234,2 +240,3 @@ markAlternateFiberNode: typeof markAlternateFiberNode; | ||
normalizeBaseUrl: typeof normalizeBaseUrl; | ||
objectToMap: typeof objectToMap; | ||
pathHasDetachedHTMLNode: typeof pathHasDetachedHTMLNode; | ||
@@ -247,5 +254,4 @@ pathHasEdgeWithIndex: typeof pathHasEdgeWithIndex; | ||
upperCaseFirstCharacter: typeof upperCaseFirstCharacter; | ||
getBooleanNodeValue: typeof getBooleanNodeValue; | ||
}; | ||
export default _default; | ||
//# sourceMappingURL=Utils.d.ts.map |
@@ -56,3 +56,2 @@ "use strict"; | ||
const HeapParser_1 = __importDefault(require("./HeapParser")); | ||
const BrowserInfo_1 = __importDefault(require("./BrowserInfo")); | ||
const memCache = Object.create(null); | ||
@@ -137,2 +136,17 @@ const FileManager_1 = __importDefault(require("./FileManager")); | ||
} | ||
// return true if this node is InternalNode (native) | ||
function isDOMInternalNode(node) { | ||
if (node == null) { | ||
return false; | ||
} | ||
return (node.type === 'native' && | ||
(node.name === 'InternalNode' || node.name === 'Detached InternalNode')); | ||
} | ||
// return true the the nodee is a global handles node | ||
function isGlobalHandlesNode(node) { | ||
if (node == null) { | ||
return false; | ||
} | ||
return node.name === '(Global handles)' && node.type === 'synthetic'; | ||
} | ||
// this function returns a more general sense of DOM nodes. Specifically, | ||
@@ -216,9 +230,28 @@ // any detached DOM nodes (e.g., HTMLXXElement, IntersectionObserver etc.) | ||
} | ||
return node.type === 'synthetic' && node.name === 'Pending activities'; | ||
if (node.type !== 'synthetic' && node.type !== 'native') { | ||
return false; | ||
} | ||
return node.name === 'Pending activities'; | ||
} | ||
const htmlElementRegex = /^HTML.*Element$/; | ||
const svgElementRegex = /^SVG.*Element$/; | ||
const htmlCollectionRegex = /^HTML.*Collection$/; | ||
const cssElementRegex = /^CSS/; | ||
const styleSheetRegex = /StyleSheet/; | ||
// special DOM element names that are not | ||
// included in the previous regex definitions | ||
const domElementSpecialNames = new Set([ | ||
'DOMTokenList', | ||
'HTMLDocument', | ||
'InternalNode', | ||
'Text', | ||
'XMLDocument', | ||
]); | ||
// check the node against a curated list of known HTML Elements | ||
// the list may be incomplete | ||
function isDOMNodeIncomplete(node) { | ||
if (node.type !== 'native') { | ||
return false; | ||
} | ||
let name = node.name; | ||
const pattern = /^HTML.*Element$/; | ||
const detachedPrefix = 'Detached '; | ||
@@ -228,4 +261,18 @@ if (name.startsWith(detachedPrefix)) { | ||
} | ||
return pattern.test(name); | ||
return (htmlElementRegex.test(name) || | ||
svgElementRegex.test(name) || | ||
cssElementRegex.test(name) || | ||
styleSheetRegex.test(name) || | ||
htmlCollectionRegex.test(name) || | ||
domElementSpecialNames.has(name)); | ||
} | ||
function isXMLDocumentNode(node) { | ||
return node.type === 'native' && node.name === 'XMLDocument'; | ||
} | ||
function isHTMLDocumentNode(node) { | ||
return node.type === 'native' && node.name === 'HTMLDocument'; | ||
} | ||
function isDOMTextNode(node) { | ||
return node.type === 'native' && node.name === 'Text'; | ||
} | ||
function isRootNode(node, opt = {}) { | ||
@@ -478,3 +525,5 @@ if (!node) { | ||
if (!node) { | ||
Console_1.default.warning(`node @${id} is not found`); | ||
if (Config_1.default.verbose) { | ||
Console_1.default.warning(`node @${id} is not found`); | ||
} | ||
return; | ||
@@ -839,23 +888,2 @@ } | ||
} | ||
function getRunMetaFilePath() { | ||
return Config_1.default.useExternalSnapshot | ||
? Config_1.default.externalRunMetaFile | ||
: Config_1.default.runMetaFile; | ||
} | ||
function loadRunMetaInfo(metaFile = undefined) { | ||
const file = metaFile || getRunMetaFilePath(); | ||
try { | ||
const content = fs_1.default.readFileSync(file, 'UTF-8'); | ||
return JSON.parse(content); | ||
} | ||
catch (_) { | ||
throw haltOrThrow('Run info missing. Please make sure `memlab run` is complete.'); | ||
} | ||
} | ||
function loadTargetInfoFromRunMeta() { | ||
const meta = loadRunMetaInfo(); | ||
Config_1.default.targetApp = meta.app; | ||
Config_1.default.targetTab = meta.interaction; | ||
BrowserInfo_1.default.load(meta.browserInfo); | ||
} | ||
function getSnapshotSequenceFilePath() { | ||
@@ -879,3 +907,5 @@ if (!Config_1.default.useExternalSnapshot) { | ||
try { | ||
const file = metaFile || getSnapshotSequenceFilePath(); | ||
const file = metaFile != null && fs_1.default.existsSync(metaFile) | ||
? metaFile | ||
: getSnapshotSequenceFilePath(); | ||
const content = fs_1.default.readFileSync(file, 'UTF-8'); | ||
@@ -888,27 +918,2 @@ return JSON.parse(content); | ||
} | ||
// if true the leak trace is will be reported | ||
function isInterestingPath(p) { | ||
// do not filter paths when analyzing Hermes snapshots | ||
if (Config_1.default.jsEngine === 'hermes') { | ||
return true; | ||
} | ||
// if the path has pattern: Window -> [InternalNode]+ -> DetachedElement | ||
if (Config_1.default.hideBrowserLeak && internalNodeRetainsDetachedElement(p)) { | ||
return false; | ||
} | ||
// if the path has pattern: ShadowRoot -> DetachedElement | ||
if (Config_1.default.hideBrowserLeak && shadowRootRetainsDetachedElement(p)) { | ||
return false; | ||
} | ||
// if the path has pattern: StyleEngine -> InternalNode -> DetachedElement | ||
if (Config_1.default.hideBrowserLeak && styleEngineRetainsDetachedElement(p)) { | ||
return false; | ||
} | ||
// if the path has pattern: Pending activitiies -> DetachedElement | ||
if (Config_1.default.hideBrowserLeak && | ||
pendingActivitiesRetainsDetachedElementChain(p)) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
// return true if the heap node represents JS object or closure | ||
@@ -931,85 +936,2 @@ function isObjectNode(node) { | ||
} | ||
// check if the path has pattern: | ||
// Window -> [InternalNode | Text]+ -> DetachedElement | ||
function internalNodeRetainsDetachedElement(path) { | ||
var _a, _b; | ||
if (!path) { | ||
return false; | ||
} | ||
let p = path; | ||
// GC root is not Window | ||
if (!p.node || !p.node.name.startsWith('Window')) { | ||
return false; | ||
} | ||
p = p.next; | ||
// Window is not poining to InternalNode | ||
if (!p || !p.node || p.node.name !== 'InternalNode') { | ||
return false; | ||
} | ||
// skip the rest InternalNode | ||
while (((_a = p.node) === null || _a === void 0 ? void 0 : _a.name) === 'InternalNode' || ((_b = p.node) === null || _b === void 0 ? void 0 : _b.name) === 'Text') { | ||
p = p.next; | ||
if (!p) { | ||
return false; | ||
} | ||
} | ||
// check if the node is a detached element | ||
return p && isDetachedDOMNode(p.node); | ||
} | ||
// check if the path has pattern: ShadowRoot -> DetachedElement | ||
function shadowRootRetainsDetachedElement(path) { | ||
let p = path; | ||
// find the ShadowRoot | ||
while (p && p.node && p.node.name !== 'ShadowRoot') { | ||
p = p.next; | ||
if (!p) { | ||
return false; | ||
} | ||
} | ||
p = p.next; | ||
// check if the node is a detached element | ||
return !!p && isDetachedDOMNode(p.node); | ||
} | ||
// check if the path has pattern: StyleEngine -> InternalNode -> DetachedElement | ||
function styleEngineRetainsDetachedElement(path) { | ||
let p = path; | ||
// find the StyleEngine | ||
while (p && p.node && p.node.name !== 'StyleEngine') { | ||
p = p.next; | ||
if (!p) { | ||
return false; | ||
} | ||
} | ||
p = p.next; | ||
// StyleEngine is not poining to InternalNode | ||
if (!p || !p.node || p.node.name !== 'InternalNode') { | ||
return false; | ||
} | ||
p = p.next; | ||
// check if the InternalNode is pointing to a detached element | ||
return !!p && isDetachedDOMNode(p.node); | ||
} | ||
function pendingActivitiesRetainsDetachedElementChain(path) { | ||
let p = path; | ||
// find the Pending activities | ||
while (p && p.node && !isPendingActivityNode(p.node)) { | ||
p = p.next; | ||
if (!p) { | ||
return false; | ||
} | ||
} | ||
p = p.next; | ||
if (!p || !p.node) { | ||
return false; | ||
} | ||
// all the following reference chain is detached DOM elements | ||
// pointing to other detached DOM elements | ||
while (p && p.node) { | ||
if (!isDetachedDOMNode(p.node)) { | ||
return false; | ||
} | ||
p = p.next; | ||
} | ||
return true; | ||
} | ||
function pathHasDetachedHTMLNode(path) { | ||
@@ -1794,2 +1716,5 @@ if (!path) { | ||
} | ||
if (options.throwError) { | ||
throw ex; | ||
} | ||
if (options.ignoreError === true) { | ||
@@ -1837,2 +1762,16 @@ return ''; | ||
} | ||
function mapToObject(map) { | ||
const ret = Object.create(null); | ||
map.forEach((v, k) => { | ||
ret[k] = v; | ||
}); | ||
return ret; | ||
} | ||
function objectToMap(object) { | ||
const ret = new Map(); | ||
for (const k of Object.keys(object)) { | ||
ret.set(k, object[k]); | ||
} | ||
return ret; | ||
} | ||
exports.default = { | ||
@@ -1843,5 +1782,5 @@ aggregateDominatorMetrics, | ||
camelCaseToReadableString, | ||
checkIsChildOfParent, | ||
checkSnapshots, | ||
checkUninstalledLibrary, | ||
checkIsChildOfParent, | ||
closePuppeteer, | ||
@@ -1855,9 +1794,10 @@ dumpSnapshot, | ||
getAllDominators, | ||
getBooleanNodeValue, | ||
getClosureSourceUrl, | ||
getConditionalDominatorIds, | ||
getEdgeByNameAndType, | ||
getError, | ||
getEdgeByNameAndType, | ||
getLastNodeId, | ||
getLeakTracePathLength, | ||
getLeakedNode, | ||
getLeakTracePathLength, | ||
getNodesIdSet, | ||
@@ -1870,13 +1810,12 @@ getNumberAtPercentile, | ||
getRetainedSize, | ||
getRunMetaFilePath, | ||
getScenarioName, | ||
getSingleSnapshotFileForAnalysis, | ||
getSnapshotDirForAnalysis, | ||
getSnapshotFilePath, | ||
getSnapshotFilePathWithTabType, | ||
getSnapshotFilesFromTabsOrder, | ||
getSnapshotFilesInDir, | ||
getSnapshotFilesFromTabsOrder, | ||
getSnapshotFromFile, | ||
getSnapshotNodeIdsFromFile, | ||
getSnapshotSequenceFilePath, | ||
getSnapshotFilePath, | ||
getSnapshotFilePathWithTabType, | ||
getStringNodeValue, | ||
@@ -1892,14 +1831,17 @@ getToNodeByEdge, | ||
isBlinkRootNode, | ||
isDOMInternalNode, | ||
isDOMNodeIncomplete, | ||
isDOMTextNode, | ||
isDebuggableNode, | ||
isDetachedDOMNode, | ||
isDetachedFiberNode, | ||
isDetachedDOMNode, | ||
isDirectPropEdge, | ||
isDocumentDOMTreesRoot, | ||
isDOMNodeIncomplete, | ||
isEssentialEdge, | ||
isFiberNode, | ||
isFiberNodeDeletionsEdge, | ||
isGlobalHandlesNode, | ||
isHTMLDocumentNode, | ||
isHermesInternalObject, | ||
isHostRoot, | ||
isInterestingPath, | ||
isMeaningfulEdge, | ||
@@ -1923,9 +1865,9 @@ isMeaningfulNode, | ||
isWeakMapEdgeToValue, | ||
isXMLDocumentNode, | ||
iterateChildFiberNodes, | ||
iterateDescendantFiberNodes, | ||
loadRunMetaInfo, | ||
loadLeakFilter, | ||
loadScenario, | ||
loadTabsOrder, | ||
loadTargetInfoFromRunMeta, | ||
mapToObject, | ||
markAllDetachedFiberNode, | ||
@@ -1935,2 +1877,3 @@ markAlternateFiberNode, | ||
normalizeBaseUrl, | ||
objectToMap, | ||
pathHasDetachedHTMLNode, | ||
@@ -1948,3 +1891,2 @@ pathHasEdgeWithIndex, | ||
upperCaseFirstCharacter, | ||
getBooleanNodeValue, | ||
}; |
@@ -190,2 +190,3 @@ "use strict"; | ||
meta_data: JSON.stringify({ | ||
extraRunInfo: Utils_1.default.mapToObject(Config_1.default.extraRunInfoMap), | ||
browser_info: BrowserInfo_1.default, | ||
@@ -192,0 +193,0 @@ visit_plan: tabsOrder, |
@@ -431,7 +431,21 @@ "use strict"; | ||
isLessPreferableEdge(edge) { | ||
const fromNode = edge.fromNode; | ||
const toNode = edge.toNode; | ||
// pending activities -> DOM element is less preferrable | ||
if (Utils_1.default.isPendingActivityNode(edge.fromNode) && | ||
Utils_1.default.isDOMNodeIncomplete(edge.toNode)) { | ||
if (Utils_1.default.isPendingActivityNode(fromNode) && | ||
Utils_1.default.isDOMNodeIncomplete(toNode)) { | ||
return true; | ||
} | ||
// detached DOM node -> non-detached DOM node is less preferable | ||
if (Utils_1.default.isDetachedDOMNode(fromNode) && | ||
Utils_1.default.isDOMNodeIncomplete(toNode) && | ||
!Utils_1.default.isDetachedDOMNode(toNode)) { | ||
return true; | ||
} | ||
// non-detached DOM node -> detached DOM node is less preferable | ||
if (Utils_1.default.isDOMNodeIncomplete(fromNode) && | ||
!Utils_1.default.isDetachedDOMNode(fromNode) && | ||
Utils_1.default.isDetachedDOMNode(toNode)) { | ||
return true; | ||
} | ||
return Config_1.default.edgeNameGreyList.has(String(edge.name_or_index)); | ||
@@ -438,0 +452,0 @@ } |
@@ -63,2 +63,3 @@ "use strict"; | ||
} | ||
Console_1.default.overwrite(''); | ||
return { staleClusters, clustersToAdd, allClusters: clusters }; | ||
@@ -65,0 +66,0 @@ } |
@@ -40,3 +40,3 @@ /** | ||
private static initEmptyCluster; | ||
static clusterControlTreatmentPaths(controlPaths: LeakTracePathItem[], controlSnapshot: IHeapSnapshot, treatmentPaths: LeakTracePathItem[], treatmentSnapshot: IHeapSnapshot, aggregateDominatorMetrics: AggregateNodeCb, option?: { | ||
static clusterControlTreatmentPaths(leakPathsFromControlRuns: LeakTracePathItem[][], controlSnapshots: IHeapSnapshot[], treatmentPaths: LeakTracePathItem[], treatmentSnapshot: IHeapSnapshot, aggregateDominatorMetrics: AggregateNodeCb, option?: { | ||
strategy?: IClusterStrategy; | ||
@@ -43,0 +43,0 @@ }): ControlTreatmentClusterResult; |
@@ -311,3 +311,3 @@ "use strict"; | ||
} | ||
static clusterControlTreatmentPaths(controlPaths, controlSnapshot, treatmentPaths, treatmentSnapshot, aggregateDominatorMetrics, option = {}) { | ||
static clusterControlTreatmentPaths(leakPathsFromControlRuns, controlSnapshots, treatmentPaths, treatmentSnapshot, aggregateDominatorMetrics, option = {}) { | ||
const result = { | ||
@@ -319,3 +319,4 @@ controlOnlyClusters: [], | ||
Console_1.default.overwrite('Clustering leak traces'); | ||
if (controlPaths.length === 0 && treatmentPaths.length === 0) { | ||
const totalControlPaths = leakPathsFromControlRuns.reduce((count, leakPaths) => count + leakPaths.length, 0); | ||
if (totalControlPaths === 0 && treatmentPaths.length === 0) { | ||
Console_1.default.midLevel('No leaks found'); | ||
@@ -325,3 +326,4 @@ return result; | ||
// sample paths if there are too many | ||
controlPaths = this.samplePaths(controlPaths); | ||
const flattenedLeakPathsFromControlRuns = leakPathsFromControlRuns.reduce((arr, leakPaths) => [...arr, ...leakPaths], []); | ||
const controlPaths = this.samplePaths(flattenedLeakPathsFromControlRuns); | ||
treatmentPaths = this.samplePaths(treatmentPaths); | ||
@@ -336,2 +338,4 @@ // build control trace to control path map | ||
const { allClusters } = NormalizedTrace.diffTraces([...controlTraces, ...treatmentTraces], [], option); | ||
// pick one of the control heap snapshots | ||
const controlSnapshot = controlSnapshots[0]; | ||
// construct TraceCluster from clustering result | ||
@@ -338,0 +342,0 @@ allClusters.forEach((traces) => { |
{ | ||
"name": "@memlab/core", | ||
"version": "1.1.20", | ||
"version": "1.1.21", | ||
"license": "MIT", | ||
@@ -5,0 +5,0 @@ "description": "memlab core libraries", |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
610454
152
15654
19