@memlab/core 1.1.21 → 1.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/__tests__/lib/TestUtils.d.ts +13 -0
  2. package/dist/__tests__/lib/TestUtils.js +39 -0
  3. package/dist/lib/Config.d.ts +10 -1
  4. package/dist/lib/Config.js +21 -2
  5. package/dist/lib/Constant.js +1 -0
  6. package/dist/lib/FileManager.js +1 -1
  7. package/dist/lib/HeapAnalyzer.js +49 -27
  8. package/dist/lib/RunInfoUtils.d.ts +1 -0
  9. package/dist/lib/RunInfoUtils.js +8 -4
  10. package/dist/lib/SerializationHelper.d.ts +18 -0
  11. package/dist/lib/SerializationHelper.js +36 -0
  12. package/dist/lib/Serializer.d.ts +2 -6
  13. package/dist/lib/Serializer.js +12 -3
  14. package/dist/lib/TraceSampler.d.ts +36 -0
  15. package/dist/lib/TraceSampler.js +78 -0
  16. package/dist/lib/Types.d.ts +26 -3
  17. package/dist/lib/Utils.d.ts +8 -0
  18. package/dist/lib/Utils.js +74 -6
  19. package/dist/lib/charts/MemoryBarChart.js +2 -2
  20. package/dist/lib/heap-data/HeapStringNode.js +25 -16
  21. package/dist/lib/leak-filters/LeakFilterRuleList.js +2 -0
  22. package/dist/lib/leak-filters/rules/FilterDetachedDOMElement.rule.d.ts +0 -1
  23. package/dist/lib/leak-filters/rules/FilterDetachedDOMElement.rule.js +22 -9
  24. package/dist/lib/leak-filters/rules/FilterOverSizedNodeAsLeak.rule.js +50 -0
  25. package/dist/lib/leak-filters/rules/FilterUnmountedFiberNode.rule.js +4 -3
  26. package/dist/lib/leak-filters/rules/FilterXMLHTTPRequest.rule.d.ts +20 -0
  27. package/dist/lib/leak-filters/rules/FilterXMLHTTPRequest.rule.js +30 -0
  28. package/dist/lib/trace-filters/TraceFilterRuleList.js +2 -0
  29. package/dist/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.d.ts +15 -0
  30. package/dist/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.js +44 -0
  31. package/dist/paths/TraceFinder.js +1 -1
  32. package/dist/trace-cluster/TraceBucket.d.ts +1 -1
  33. package/dist/trace-cluster/TraceBucket.js +54 -22
  34. package/package.json +1 -1
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall web_perf_infra
9
+ */
10
+ import { IHeapSnapshot } from '../../lib/Types';
11
+ /** @internal */
12
+ export declare function getFullHeapFromFile(file: string): Promise<IHeapSnapshot>;
13
+ //# sourceMappingURL=TestUtils.d.ts.map
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @format
9
+ * @oncall web_perf_infra
10
+ */
11
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
12
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
13
+ return new (P || (P = Promise))(function (resolve, reject) {
14
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
15
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
16
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
17
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
18
+ });
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.getFullHeapFromFile = void 0;
22
+ const __1 = require("../..");
23
+ /** @internal */
24
+ function getFullHeapFromFile(file) {
25
+ return __awaiter(this, void 0, void 0, function* () {
26
+ return yield loadProcessedSnapshot({ file });
27
+ });
28
+ }
29
+ exports.getFullHeapFromFile = getFullHeapFromFile;
30
+ function loadProcessedSnapshot(options = {}) {
31
+ return __awaiter(this, void 0, void 0, function* () {
32
+ const opt = { buildNodeIdIndex: true, verbose: true };
33
+ const file = options.file || __1.utils.getSnapshotFilePathWithTabType(/.*/);
34
+ const snapshot = yield __1.utils.getSnapshotFromFile(file, opt);
35
+ __1.analysis.preparePathFinder(snapshot);
36
+ __1.info.flush();
37
+ return snapshot;
38
+ });
39
+ }
@@ -38,6 +38,11 @@ declare type ConfigOption = {
38
38
  workDir?: string;
39
39
  };
40
40
  /** @internal */
41
+ export declare enum TraceObjectMode {
42
+ Default = 1,
43
+ SelectedJSObjects = 2
44
+ }
45
+ /** @internal */
41
46
  export declare enum ErrorHandling {
42
47
  Halt = 1,
43
48
  Throw = 2
@@ -109,7 +114,7 @@ export declare class MemLabConfig {
109
114
  qes: QuickExperiment[];
110
115
  isOndemand: boolean;
111
116
  useExternalSnapshot: boolean;
112
- externalRunMetaFile: string;
117
+ externalRunMetaTemplateFile: string;
113
118
  externalSnapshotVisitOrderFile: string;
114
119
  externalSnapshotDir: Nullable<string>;
115
120
  externalSnapshotFilePaths: string[];
@@ -177,6 +182,7 @@ export declare class MemLabConfig {
177
182
  nodeIgnoreSetInShape: Set<string>;
178
183
  oversizeObjectAsLeak: boolean;
179
184
  oversizeThreshold: number;
185
+ traceAllObjectsMode: TraceObjectMode;
180
186
  clusterRetainedSizeThreshold: number;
181
187
  externalLeakFilter?: Optional<ILeakFilter>;
182
188
  monoRepoDir: string;
@@ -199,6 +205,9 @@ export declare class MemLabConfig {
199
205
  interceptScript: boolean;
200
206
  isAnalyzingMainThread: boolean;
201
207
  targetWorkerTitle: Nullable<string>;
208
+ noReCluster: boolean;
209
+ maxSamplesForClustering: number;
210
+ filterTraceByName: Nullable<string>;
202
211
  constructor(options?: ConfigOption);
203
212
  private initInternalConfigs;
204
213
  private init;
@@ -35,7 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
36
  };
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.MemLabConfig = exports.ErrorHandling = void 0;
38
+ exports.MemLabConfig = exports.ErrorHandling = exports.TraceObjectMode = void 0;
39
39
  const path_1 = __importDefault(require("path"));
40
40
  const RunningModes_1 = __importDefault(require("../modes/RunningModes"));
41
41
  const Console_1 = __importDefault(require("./Console"));
@@ -57,6 +57,12 @@ const defaultViewport = {
57
57
  deviceScaleFactor: 1,
58
58
  };
59
59
  /** @internal */
60
+ var TraceObjectMode;
61
+ (function (TraceObjectMode) {
62
+ TraceObjectMode[TraceObjectMode["Default"] = 1] = "Default";
63
+ TraceObjectMode[TraceObjectMode["SelectedJSObjects"] = 2] = "SelectedJSObjects";
64
+ })(TraceObjectMode = exports.TraceObjectMode || (exports.TraceObjectMode = {}));
65
+ /** @internal */
60
66
  var ErrorHandling;
61
67
  (function (ErrorHandling) {
62
68
  ErrorHandling[ErrorHandling["Halt"] = 1] = "Halt";
@@ -176,12 +182,17 @@ class MemLabConfig {
176
182
  // if true, split dataset into trunks
177
183
  // with random order for sequential clustering
178
184
  this.seqClusteringIsRandomChunks = false;
185
+ // maximum number of samples as input for leak trace clustering
186
+ this.maxSamplesForClustering = 5000;
179
187
  // extra E2E run info (other than the fields defined in
180
188
  // RunMetaInfo like app, interaction, browserInfo).
181
189
  // Information saved in this map will be
182
190
  // auto-serialized to run-meta.json when the file is saved
183
191
  // and auto-deserialized from run-meta.json when the file is loaded
184
192
  this.extraRunInfoMap = new Map();
193
+ // if specified via CLI options, this will filter leak traces by
194
+ // node and edge names in the leak trace
195
+ this.filterTraceByName = null;
185
196
  }
186
197
  // initialize configurable parameters
187
198
  init(options = {}) {
@@ -209,6 +220,8 @@ class MemLabConfig {
209
220
  this.skipGC = false;
210
221
  // true if running in ContinuousTest
211
222
  this.isContinuousTest = false;
223
+ // true if reclustering is turned off
224
+ this.noReCluster = false;
212
225
  // true if running a local test
213
226
  this.isTest = false;
214
227
  // true if running in local puppeteer mode
@@ -308,7 +321,10 @@ class MemLabConfig {
308
321
  // node names excluded from the trace finding
309
322
  this.nodeNameBlockList = new Set(['system / PropertyCell']);
310
323
  // edge names excluded from the trace finding
311
- this.edgeNameBlockList = new Set(['feedback_cell']);
324
+ this.edgeNameBlockList = new Set([
325
+ 'feedback_cell',
326
+ 'part of key -> value pair in ephemeron table',
327
+ ]);
312
328
  // node names less preferable in trace finding
313
329
  this.nodeNameGreyList = new Set([
314
330
  'InternalNode',
@@ -341,6 +357,9 @@ class MemLabConfig {
341
357
  this.oversizeObjectAsLeak = false;
342
358
  // if larger than this threshold, consider as memory leak
343
359
  this.oversizeThreshold = 0;
360
+ // when specified default, this mode will trace/diff all objects
361
+ // you can specified other modes (e.g., selected JS objects only)
362
+ this.traceAllObjectsMode = TraceObjectMode.Default;
344
363
  // only report leak clusters with aggregated retained size
345
364
  // bigger than this threshold
346
365
  this.clusterRetainedSizeThreshold = 0;
@@ -46,6 +46,7 @@ const constants = {
46
46
  '(Write barrier)',
47
47
  '(Retain maps)',
48
48
  '(Unknown)',
49
+ '<Synthetic>',
49
50
  ],
50
51
  namePrefixForScenarioFromFile: '',
51
52
  unset: 'UNSET',
@@ -427,7 +427,7 @@ class FileManager {
427
427
  config.heapAnalysisLogDir = joinAndProcessDir(options, this.getHeapAnalysisLogDir(options));
428
428
  config.metricsOutDir = joinAndProcessDir(options, loggerOutDir, 'metrics');
429
429
  config.reportScreenshotFile = path_1.default.join(outDir, 'report.png');
430
- config.externalRunMetaFile = this.getRunMetaExternalTemplateFile();
430
+ config.externalRunMetaTemplateFile = this.getRunMetaExternalTemplateFile();
431
431
  config.externalSnapshotVisitOrderFile =
432
432
  this.getSnapshotSequenceExternalTemplateFile();
433
433
  joinAndProcessDir(options, this.getUniqueTraceClusterDir(options));
@@ -35,6 +35,7 @@ const TraceBucket_1 = __importDefault(require("../trace-cluster/TraceBucket"));
35
35
  const LeakObjectFilter_1 = require("./leak-filters/LeakObjectFilter");
36
36
  const MLTraceSimilarityStrategy_1 = __importDefault(require("../trace-cluster/strategies/MLTraceSimilarityStrategy"));
37
37
  const LeakTraceFilter_1 = require("./trace-filters/LeakTraceFilter");
38
+ const TraceSampler_1 = __importDefault(require("./TraceSampler"));
38
39
  class MemoryAnalyst {
39
40
  checkLeak() {
40
41
  return __awaiter(this, void 0, void 0, function* () {
@@ -48,13 +49,13 @@ class MemoryAnalyst {
48
49
  const controlSnapshotDirs = options.controlWorkDirs.map(controlWorkDir => FileManager_1.default.getCurDataDir({
49
50
  workDir: controlWorkDir,
50
51
  }));
51
- const treatmentSnapshotDir = FileManager_1.default.getCurDataDir({
52
- workDir: options.treatmentWorkDir,
53
- });
52
+ const treatmentSnapshotDirs = options.treatmentWorkDirs.map(treatmentWorkDir => FileManager_1.default.getCurDataDir({
53
+ workDir: treatmentWorkDir,
54
+ }));
54
55
  // check control working dir
55
56
  controlSnapshotDirs.forEach(controlSnapshotDir => Utils_1.default.checkSnapshots({ snapshotDir: controlSnapshotDir }));
56
57
  // check treatment working dir
57
- Utils_1.default.checkSnapshots({ snapshotDir: treatmentSnapshotDir });
58
+ treatmentSnapshotDirs.forEach(treatmentSnapshotDir => Utils_1.default.checkSnapshots({ snapshotDir: treatmentSnapshotDir }));
58
59
  // display control and treatment memory
59
60
  MemoryBarChart_1.default.plotMemoryBarChart(options);
60
61
  return this.diffMemoryLeakTraces(options);
@@ -75,17 +76,26 @@ class MemoryAnalyst {
75
76
  leakPathsFromControlRuns.push(this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: controlWorkDir }));
76
77
  controlSnapshots.push(snapshotDiff.snapshot);
77
78
  }
78
- // diff snapshots and get treatment raw paths
79
- const snapshotDiff = yield this.diffSnapshots({
80
- loadAllSnapshots: true,
81
- workDir: options.treatmentWorkDir,
82
- });
83
- const treatmentLeakPaths = this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: options.treatmentWorkDir });
84
- const treatmentSnapshot = snapshotDiff.snapshot;
79
+ // diff snapshots from treatment dirs and get treatment raw paths array
80
+ const treatmentSnapshots = [];
81
+ const leakPathsFromTreatmentRuns = [];
82
+ let firstTreatmentSnapshotDiff = null;
83
+ for (const treatmentWorkDir of options.treatmentWorkDirs) {
84
+ const snapshotDiff = yield this.diffSnapshots({
85
+ loadAllSnapshots: true,
86
+ workDir: treatmentWorkDir,
87
+ });
88
+ if (firstTreatmentSnapshotDiff == null) {
89
+ firstTreatmentSnapshotDiff = snapshotDiff;
90
+ }
91
+ leakPathsFromTreatmentRuns.push(this.filterLeakPaths(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, { workDir: treatmentWorkDir }));
92
+ treatmentSnapshots.push(snapshotDiff.snapshot);
93
+ }
85
94
  const controlPathCounts = JSON.stringify(leakPathsFromControlRuns.map(leakPaths => leakPaths.length));
95
+ const treatmentPathCounts = JSON.stringify(leakPathsFromTreatmentRuns.map(leakPaths => leakPaths.length));
86
96
  Console_1.default.topLevel(`${controlPathCounts} traces from control group`);
87
- Console_1.default.topLevel(`${treatmentLeakPaths.length} traces from treatment group`);
88
- const result = TraceBucket_1.default.clusterControlTreatmentPaths(leakPathsFromControlRuns, controlSnapshots, treatmentLeakPaths, treatmentSnapshot, Utils_1.default.aggregateDominatorMetrics, {
97
+ Console_1.default.topLevel(`${treatmentPathCounts} traces from treatment group`);
98
+ const result = TraceBucket_1.default.clusterControlTreatmentPaths(leakPathsFromControlRuns, controlSnapshots, leakPathsFromTreatmentRuns, treatmentSnapshots, Utils_1.default.aggregateDominatorMetrics, {
89
99
  strategy: Config_1.default.isMLClustering
90
100
  ? new MLTraceSimilarityStrategy_1.default()
91
101
  : undefined,
@@ -94,7 +104,10 @@ class MemoryAnalyst {
94
104
  yield this.serializeClusterUpdate(result.treatmentOnlyClusters);
95
105
  // serialize JSON file with detailed leak trace information
96
106
  const treatmentOnlyPaths = result.treatmentOnlyClusters.map(c => c.path);
97
- return LeakTraceDetailsLogger_1.default.logTraces(snapshotDiff.leakedHeapNodeIdSet, snapshotDiff.snapshot, snapshotDiff.listOfLeakedHeapNodeIdSet, treatmentOnlyPaths, Config_1.default.traceJsonOutDir);
107
+ if (firstTreatmentSnapshotDiff == null) {
108
+ throw Utils_1.default.haltOrThrow('treatemnt snapshot diff result not found');
109
+ }
110
+ return LeakTraceDetailsLogger_1.default.logTraces(firstTreatmentSnapshotDiff.leakedHeapNodeIdSet, firstTreatmentSnapshotDiff.snapshot, firstTreatmentSnapshotDiff.listOfLeakedHeapNodeIdSet, treatmentOnlyPaths, Config_1.default.traceJsonOutDir);
98
111
  });
99
112
  }
100
113
  // find all unique pattern of leaks
@@ -357,26 +370,35 @@ class MemoryAnalyst {
357
370
  this.filterLeakedObjects(leakedNodeIds, snapshot);
358
371
  const leakTraceFilter = new LeakTraceFilter_1.LeakTraceFilter();
359
372
  const nodeIdInPaths = new Set();
360
- const paths = [];
361
- let numOfLeakedObjects = 0;
362
- let i = 0;
373
+ const samplePool = [];
363
374
  // analysis for each node
364
375
  Utils_1.default.applyToNodes(leakedNodeIds, snapshot, node => {
365
- if (!Config_1.default.isContinuousTest && ++i % 11 === 0) {
366
- Console_1.default.overwrite(`progress: ${i} / ${leakedNodeIds.size} @${node.id}`);
367
- }
368
376
  // BFS search for path from the leaked node to GC roots
369
377
  const p = finder.getPathToGCRoots(snapshot, node);
370
378
  if (p == null ||
371
379
  !leakTraceFilter.filter(p, { config: Config_1.default, leakedNodeIds, snapshot })) {
372
380
  return;
373
381
  }
374
- ++numOfLeakedObjects;
375
- paths.push(p);
376
- this.logLeakTraceSummary(p, nodeIdInPaths, snapshot, options);
382
+ // filter leak trace based on CLI-specified node or edge names
383
+ if (!Utils_1.default.pathHasNodeOrEdgeWithName(p, Config_1.default.filterTraceByName)) {
384
+ return;
385
+ }
386
+ // ignore if the leak trace is too long
387
+ if (Utils_1.default.getLeakTracePathLength(p) > 100) {
388
+ return;
389
+ }
390
+ samplePool.push(p);
377
391
  }, { reverse: true });
392
+ const sampler = new TraceSampler_1.default(samplePool.length);
393
+ const paths = samplePool.filter(p => {
394
+ if (sampler.sample()) {
395
+ this.logLeakTraceSummary(p, nodeIdInPaths, snapshot, options);
396
+ return true;
397
+ }
398
+ return false;
399
+ });
378
400
  if (Config_1.default.verbose) {
379
- Console_1.default.midLevel(`${numOfLeakedObjects} leaked objects`);
401
+ Console_1.default.midLevel(`Filter and select ${paths.length} leaked trace`);
380
402
  }
381
403
  return paths;
382
404
  }
@@ -410,11 +432,11 @@ class MemoryAnalyst {
410
432
  clusterHeapObjects(objectIds, snapshot) {
411
433
  const finder = this.preparePathFinder(snapshot);
412
434
  const paths = [];
413
- let i = 0;
435
+ const sampler = new TraceSampler_1.default(objectIds.size);
414
436
  // analysis for each node
415
437
  Utils_1.default.applyToNodes(objectIds, snapshot, node => {
416
- if (++i % 11 === 0) {
417
- Console_1.default.overwrite(`progress: ${i} / ${objectIds.size} @${node.id}`);
438
+ if (!sampler.sample()) {
439
+ return;
418
440
  }
419
441
  // BFS search for path from the leaked node to GC roots
420
442
  const p = finder.getPathToGCRoots(snapshot, node);
@@ -11,6 +11,7 @@ import type { Nullable, Optional, RunMetaInfo } from './Types';
11
11
  export declare class RunMetaInfoManager {
12
12
  getRunMetaFilePath(options?: {
13
13
  workDir?: Optional<string>;
14
+ readonly?: Optional<boolean>;
14
15
  }): string;
15
16
  saveRunMetaInfo(runMetaInfo: RunMetaInfo, options?: {
16
17
  workDir?: Optional<string>;
@@ -16,8 +16,10 @@ class RunMetaInfoManager {
16
16
  if ((options === null || options === void 0 ? void 0 : options.workDir) != null) {
17
17
  return FileManager_1.default.getRunMetaFile({ workDir: options.workDir });
18
18
  }
19
- if (Config_1.default.useExternalSnapshot) {
20
- return Config_1.default.externalRunMetaFile;
19
+ if ((options === null || options === void 0 ? void 0 : options.readonly) && Config_1.default.useExternalSnapshot) {
20
+ // only returns the template file if the
21
+ // run meta file is used for readonly purpose
22
+ return Config_1.default.externalRunMetaTemplateFile;
21
23
  }
22
24
  if (Config_1.default.runMetaFile != null) {
23
25
  return Config_1.default.runMetaFile;
@@ -40,7 +42,8 @@ class RunMetaInfoManager {
40
42
  return runMetaInfo;
41
43
  }
42
44
  loadRunMetaInfo(options) {
43
- const file = (options === null || options === void 0 ? void 0 : options.metaFile) || this.getRunMetaFilePath(options);
45
+ const file = (options === null || options === void 0 ? void 0 : options.metaFile) ||
46
+ this.getRunMetaFilePath(Object.assign({ readonly: true }, options));
44
47
  try {
45
48
  return this.loadRunMetaInfoFromFile(file);
46
49
  }
@@ -49,7 +52,8 @@ class RunMetaInfoManager {
49
52
  }
50
53
  }
51
54
  loadRunMetaInfoSilentFail(options) {
52
- const file = (options === null || options === void 0 ? void 0 : options.metaFile) || this.getRunMetaFilePath(options);
55
+ const file = (options === null || options === void 0 ? void 0 : options.metaFile) ||
56
+ this.getRunMetaFilePath(Object.assign({ readonly: true }, options));
53
57
  try {
54
58
  return this.loadRunMetaInfoFromFile(file);
55
59
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall web_perf_infra
9
+ */
10
+ import type { IHeapNode, IHeapSnapshot, ISerializationHelper, ISerializedInfo, JSONifyArgs, JSONifyOptions, Nullable } from './Types';
11
+ export declare class SerializationHelper implements ISerializationHelper {
12
+ protected snapshot: Nullable<IHeapSnapshot>;
13
+ setSnapshot(snapshot: IHeapSnapshot): void;
14
+ createOrMergeWrapper(info: ISerializedInfo, _node: IHeapNode, _args: JSONifyArgs, _options: JSONifyOptions): ISerializedInfo;
15
+ }
16
+ declare const _default: typeof SerializationHelper;
17
+ export default _default;
18
+ //# sourceMappingURL=SerializationHelper.d.ts.map
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @format
9
+ * @oncall web_perf_infra
10
+ */
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SerializationHelper = void 0;
16
+ const InternalValueSetter_1 = require("./InternalValueSetter");
17
+ const Constant_1 = __importDefault(require("./Constant"));
18
+ class SerializationHelper {
19
+ constructor() {
20
+ this.snapshot = null;
21
+ }
22
+ setSnapshot(snapshot) {
23
+ this.snapshot = snapshot;
24
+ }
25
+ createOrMergeWrapper(info,
26
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
27
+ _node,
28
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
29
+ _args,
30
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
31
+ _options) {
32
+ return info;
33
+ }
34
+ }
35
+ exports.SerializationHelper = SerializationHelper;
36
+ exports.default = (0, InternalValueSetter_1.setInternalValue)(SerializationHelper, __filename, Constant_1.default.internalDir);
@@ -7,12 +7,8 @@
7
7
  * @format
8
8
  * @oncall web_perf_infra
9
9
  */
10
- import { E2EStepInfo, HeapNodeIdSet, IHeapEdge, IHeapNode, IHeapSnapshot, ISerializedInfo, LeakTracePathItem, Nullable } from './Types';
11
- declare type JSONifyArgs = {
12
- leakedIdSet?: Set<number>;
13
- nodeIdsInSnapshots?: Array<Set<number>>;
14
- };
15
- declare function JSONifyPath(path: LeakTracePathItem, _snapshot: IHeapSnapshot, args: JSONifyArgs): Nullable<ISerializedInfo>;
10
+ import { E2EStepInfo, HeapNodeIdSet, IHeapEdge, IHeapNode, IHeapSnapshot, ISerializedInfo, JSONifyArgs, LeakTracePathItem, Nullable } from './Types';
11
+ declare function JSONifyPath(path: LeakTracePathItem, snapshot: IHeapSnapshot, args: JSONifyArgs): Nullable<ISerializedInfo>;
16
12
  declare type SummarizeOptions = {
17
13
  compact?: boolean;
18
14
  color?: boolean;
@@ -18,6 +18,7 @@ const Config_1 = __importDefault(require("./Config"));
18
18
  const Utils_1 = __importDefault(require("./Utils"));
19
19
  const Console_1 = __importDefault(require("./Console"));
20
20
  const TraceFinder_1 = __importDefault(require("../paths/TraceFinder"));
21
+ const SerializationHelper_1 = __importDefault(require("./SerializationHelper"));
21
22
  const REGEXP_NAME_CLEANUP = /[[]\(\)]/g;
22
23
  const EMPTY_JSONIFY_OPTIONS = {
23
24
  fiberNodeReturnTrace: {},
@@ -374,7 +375,12 @@ function JSONifyNode(node, args, options) {
374
375
  if (node.dominatorNode) {
375
376
  info['dominator id (extra)'] = `@${node.dominatorNode.id}`;
376
377
  }
377
- return info;
378
+ // use serialization helper to wrap around
379
+ // the JSON node with additional tagging information
380
+ const { serializationHelper } = options;
381
+ return serializationHelper
382
+ ? serializationHelper.createOrMergeWrapper(info, node, args, options)
383
+ : info;
378
384
  }
379
385
  function JSONifyTabsOrder() {
380
386
  const file = Utils_1.default.getSnapshotSequenceFilePath();
@@ -383,7 +389,7 @@ function JSONifyTabsOrder() {
383
389
  function shouldHighlight(node) {
384
390
  return Utils_1.default.isDetachedDOMNode(node) || Utils_1.default.isDetachedFiberNode(node);
385
391
  }
386
- function JSONifyPath(path, _snapshot, args) {
392
+ function JSONifyPath(path, snapshot, args) {
387
393
  if (!path.node) {
388
394
  return null;
389
395
  }
@@ -393,6 +399,9 @@ function JSONifyPath(path, _snapshot, args) {
393
399
  ret['$tabsOrder:' + JSONifyTabsOrder()] = '';
394
400
  ret[`${idx++}: ${getNodeNameInJSON(path.node, args)}`] = JSONifyNode(path.node, args, Object.assign(Object.assign({}, EMPTY_JSONIFY_OPTIONS), { processedNodeId: new Set() }));
395
401
  let pathItem = path;
402
+ // initialize serialization helper
403
+ const serializationHelper = new SerializationHelper_1.default();
404
+ serializationHelper.setSnapshot(snapshot);
396
405
  while (pathItem === null || pathItem === void 0 ? void 0 : pathItem.edge) {
397
406
  const edge = pathItem.edge;
398
407
  const nextNode = edge.toNode;
@@ -401,7 +410,7 @@ function JSONifyPath(path, _snapshot, args) {
401
410
  nextNode.highlight = true;
402
411
  }
403
412
  const edgeRetainSize = pathItem.edgeRetainSize;
404
- ret[`${idx++}: ${getEdgeNameInJSON(edge, edgeRetainSize)}${getNodeNameInJSON(nextNode, args)}`] = JSONifyNode(nextNode, args, Object.assign(Object.assign({}, EMPTY_JSONIFY_OPTIONS), { processedNodeId: new Set() }));
413
+ ret[`${idx++}: ${getEdgeNameInJSON(edge, edgeRetainSize)}${getNodeNameInJSON(nextNode, args)}`] = JSONifyNode(nextNode, args, Object.assign(Object.assign({}, EMPTY_JSONIFY_OPTIONS), { processedNodeId: new Set(), serializationHelper }));
405
414
  pathItem = pathItem.next;
406
415
  }
407
416
  return ret;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall web_perf_infra
9
+ */
10
+ export declare type TraceSamplerOption = {
11
+ maxSample?: number;
12
+ };
13
+ export default class TraceSampler {
14
+ private maxCount;
15
+ private processed;
16
+ private selected;
17
+ private population;
18
+ constructor(n: number, options?: TraceSamplerOption);
19
+ init(n: number): void;
20
+ private calculateSampleRatio;
21
+ /**
22
+ * The caller decide to give up sampling this time.
23
+ * This `giveup` and the `sample` method in aggregation should be
24
+ * called `this.population` times.
25
+ *
26
+ * For example, if `giveup` is called n1 times,
27
+ * and `sample` is called n2 times, then n1 + n2 === this.population.
28
+ */
29
+ giveup(): void;
30
+ /**
31
+ * This sample method should be called precisely this.population times.
32
+ * @returns true if this sample should be taken
33
+ */
34
+ sample(): boolean;
35
+ }
36
+ //# sourceMappingURL=TraceSampler.d.ts.map
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @format
9
+ * @oncall web_perf_infra
10
+ */
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const Config_1 = __importDefault(require("./Config"));
16
+ const Console_1 = __importDefault(require("./Console"));
17
+ const Utils_1 = __importDefault(require("./Utils"));
18
+ class TraceSampler {
19
+ constructor(n, options = {}) {
20
+ var _a;
21
+ // the max number of traces after sampling
22
+ this.maxCount = Config_1.default.maxSamplesForClustering;
23
+ this.processed = 0;
24
+ this.selected = 0;
25
+ this.population = -1;
26
+ this.maxCount = (_a = options.maxSample) !== null && _a !== void 0 ? _a : Config_1.default.maxSamplesForClustering;
27
+ this.init(n);
28
+ }
29
+ init(n) {
30
+ this.processed = 0;
31
+ this.selected = 0;
32
+ this.population = n;
33
+ this.calculateSampleRatio(n);
34
+ }
35
+ calculateSampleRatio(n) {
36
+ const sampleRatio = Math.min(1, this.maxCount / n);
37
+ if (sampleRatio < 1) {
38
+ Console_1.default.warning('Sampling trace due to a large number of traces:');
39
+ Console_1.default.lowLevel(` Number of Traces: ${n}`);
40
+ Console_1.default.lowLevel(` Sampling Ratio: ${Utils_1.default.getReadablePercent(sampleRatio)}`);
41
+ }
42
+ return sampleRatio;
43
+ }
44
+ /**
45
+ * The caller decide to give up sampling this time.
46
+ * This `giveup` and the `sample` method in aggregation should be
47
+ * called `this.population` times.
48
+ *
49
+ * For example, if `giveup` is called n1 times,
50
+ * and `sample` is called n2 times, then n1 + n2 === this.population.
51
+ */
52
+ giveup() {
53
+ ++this.processed;
54
+ }
55
+ /**
56
+ * This sample method should be called precisely this.population times.
57
+ * @returns true if this sample should be taken
58
+ */
59
+ sample() {
60
+ if (this.processed >= this.population) {
61
+ throw Utils_1.default.haltOrThrow(`processing ${this.processed + 1} samples but total population is ${this.population}`);
62
+ }
63
+ // use large number to mod here to avoid too much console I/O
64
+ if (!Config_1.default.isContinuousTest && this.processed % 771 === 0) {
65
+ const percent = Utils_1.default.getReadablePercent(this.processed / this.population);
66
+ Console_1.default.overwrite(`progress: ${this.processed} / ${this.population} (${percent})`);
67
+ }
68
+ const dynamicRatio = (this.maxCount - this.selected) / (this.population - this.processed);
69
+ // increase the counter indicating how many samples has been processed
70
+ ++this.processed;
71
+ if (Math.random() <= dynamicRatio) {
72
+ ++this.selected;
73
+ return true;
74
+ }
75
+ return false;
76
+ }
77
+ }
78
+ exports.default = TraceSampler;
@@ -806,8 +806,9 @@ export declare type TraceClusterMetaInfo = {
806
806
  };
807
807
  /** @internal */
808
808
  export declare type ControlTreatmentClusterResult = {
809
- controlOnlyClusters: TraceCluster[];
809
+ controlLikelyOrOnlyClusters: TraceCluster[];
810
810
  treatmentOnlyClusters: TraceCluster[];
811
+ treatmentLikelyClusters: TraceCluster[];
811
812
  hybridClusters: Array<{
812
813
  control: TraceCluster;
813
814
  treatment: TraceCluster;
@@ -1930,12 +1931,12 @@ export interface IOveralLeakInfo extends Partial<IOveralHeapInfo> {
1930
1931
  /** @internal */
1931
1932
  export declare type DiffLeakOptions = {
1932
1933
  controlWorkDirs: string[];
1933
- treatmentWorkDir: string;
1934
+ treatmentWorkDirs: string[];
1934
1935
  };
1935
1936
  /** @internal */
1936
1937
  export declare type PlotMemoryOptions = {
1937
1938
  controlWorkDirs?: string[];
1938
- treatmentWorkDir?: string;
1939
+ treatmentWorkDirs?: string[];
1939
1940
  workDir?: string;
1940
1941
  } & IMemoryAnalystOptions;
1941
1942
  /** @internal */
@@ -1971,4 +1972,26 @@ export interface IHeapConfig {
1971
1972
  }
1972
1973
  /** @internal */
1973
1974
  export declare type ErrorWithMessage = Pick<Error, 'message'>;
1975
+ /** @internal */
1976
+ export declare type CommandOptionExample = string | {
1977
+ description?: string;
1978
+ cliOptionExample: string;
1979
+ };
1980
+ /** @internal */
1981
+ export declare type JSONifyArgs = {
1982
+ leakedIdSet?: Set<number>;
1983
+ nodeIdsInSnapshots?: Array<Set<number>>;
1984
+ };
1985
+ /** @internal */
1986
+ export interface ISerializationHelper {
1987
+ setSnapshot(snapshot: IHeapSnapshot): void;
1988
+ createOrMergeWrapper(info: ISerializedInfo, node: IHeapNode, args: JSONifyArgs, options: JSONifyOptions): ISerializedInfo;
1989
+ }
1990
+ /** @internal */
1991
+ export declare type JSONifyOptions = {
1992
+ fiberNodeReturnTrace: Record<number, string>;
1993
+ processedNodeId: Set<number>;
1994
+ forceJSONifyDepth?: number;
1995
+ serializationHelper?: ISerializationHelper;
1996
+ };
1974
1997
  //# sourceMappingURL=Types.d.ts.map