@memlab/core 1.1.20 → 1.1.22

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 (48) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +4 -1
  3. package/dist/lib/Config.d.ts +11 -0
  4. package/dist/lib/Config.js +26 -1
  5. package/dist/lib/Constant.js +1 -0
  6. package/dist/lib/FileManager.d.ts +3 -0
  7. package/dist/lib/FileManager.js +10 -1
  8. package/dist/lib/HeapAnalyzer.d.ts +5 -1
  9. package/dist/lib/HeapAnalyzer.js +56 -34
  10. package/dist/lib/RunInfoUtils.d.ts +39 -0
  11. package/dist/lib/RunInfoUtils.js +86 -0
  12. package/dist/lib/TraceSampler.d.ts +36 -0
  13. package/dist/lib/TraceSampler.js +78 -0
  14. package/dist/lib/Types.d.ts +12 -2
  15. package/dist/lib/Utils.d.ts +31 -19
  16. package/dist/lib/Utils.js +124 -145
  17. package/dist/lib/charts/MemoryBarChart.d.ts +1 -0
  18. package/dist/lib/charts/MemoryBarChart.js +18 -3
  19. package/dist/lib/heap-data/HeapStringNode.js +25 -16
  20. package/dist/lib/leak-filters/rules/FilterOverSizedNodeAsLeak.rule.js +50 -0
  21. package/dist/lib/trace-filters/BaseTraceFilter.rule.d.ts +29 -0
  22. package/dist/lib/trace-filters/BaseTraceFilter.rule.js +22 -0
  23. package/dist/lib/trace-filters/LeakTraceFilter.d.ts +20 -0
  24. package/dist/lib/trace-filters/LeakTraceFilter.js +37 -0
  25. package/dist/lib/trace-filters/TraceFilterRuleList.d.ts +13 -0
  26. package/dist/lib/trace-filters/TraceFilterRuleList.js +35 -0
  27. package/dist/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.d.ts +15 -0
  28. package/dist/lib/trace-filters/rules/FilterAttachedDOMToDetachedDOMTrace.rule.js +55 -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/lib/trace-filters/rules/FilterDOMNodeChainTrace.rule.d.ts +15 -0
  32. package/dist/lib/trace-filters/rules/FilterDOMNodeChainTrace.rule.js +41 -0
  33. package/dist/lib/trace-filters/rules/FilterHermesTrace.rule.d.ts +15 -0
  34. package/dist/lib/trace-filters/rules/FilterHermesTrace.rule.js +29 -0
  35. package/dist/lib/trace-filters/rules/FilterInternalNodeTrace.rule.d.ts +15 -0
  36. package/dist/lib/trace-filters/rules/FilterInternalNodeTrace.rule.js +57 -0
  37. package/dist/lib/trace-filters/rules/FilterPendingActivitiesTrace.rule.d.ts +15 -0
  38. package/dist/lib/trace-filters/rules/FilterPendingActivitiesTrace.rule.js +62 -0
  39. package/dist/lib/trace-filters/rules/FilterShadowRootTrace.rule.d.ts +15 -0
  40. package/dist/lib/trace-filters/rules/FilterShadowRootTrace.rule.js +44 -0
  41. package/dist/lib/trace-filters/rules/FilterStyleEngineTrace.rule.d.ts +15 -0
  42. package/dist/lib/trace-filters/rules/FilterStyleEngineTrace.rule.js +49 -0
  43. package/dist/logger/LeakClusterLogger.js +1 -0
  44. package/dist/paths/TraceFinder.js +17 -3
  45. package/dist/trace-cluster/TraceBucket.d.ts +1 -1
  46. package/dist/trace-cluster/TraceBucket.js +20 -14
  47. package/dist/trace-cluster/strategies/TraceSimilarityStrategy.js +1 -0
  48. package/package.json +1 -1
@@ -0,0 +1,62 @@
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.FilterPendingActivitiesTraceRule = void 0;
16
+ const Config_1 = __importDefault(require("../../Config"));
17
+ const Utils_1 = __importDefault(require("../../Utils"));
18
+ const BaseTraceFilter_rule_1 = require("../BaseTraceFilter.rule");
19
+ class FilterPendingActivitiesTraceRule {
20
+ filter(p, options = {}) {
21
+ var _a;
22
+ const curConfig = (_a = options.config) !== null && _a !== void 0 ? _a : Config_1.default;
23
+ // if the path has pattern: Pending activitiies -> DetachedElement
24
+ if (curConfig.hideBrowserLeak &&
25
+ pendingActivitiesRetainsDetachedElementChain(p)) {
26
+ return BaseTraceFilter_rule_1.TraceDecision.NOT_INSIGHTFUL;
27
+ }
28
+ return BaseTraceFilter_rule_1.TraceDecision.MAYBE_INSIGHTFUL;
29
+ }
30
+ }
31
+ exports.FilterPendingActivitiesTraceRule = FilterPendingActivitiesTraceRule;
32
+ function pendingActivitiesRetainsDetachedElementChain(path) {
33
+ let p = path;
34
+ // find the Pending activities
35
+ while (p && p.node && !Utils_1.default.isPendingActivityNode(p.node)) {
36
+ p = p.next;
37
+ if (!p) {
38
+ return false;
39
+ }
40
+ }
41
+ p = p.next;
42
+ if (!p || !p.node) {
43
+ return false;
44
+ }
45
+ // Scan the rest of the trace, if the following check is met,
46
+ // the leak trace is considered as not suitable for debugging:
47
+ // If the scanner encounters an object o on the
48
+ // rest of the leak trace, where o is neither a detached DOM node nor a
49
+ // Fiber Node and if the scanner didn't hit a detached DOM node first
50
+ while (p && p.node) {
51
+ if (Utils_1.default.isDetachedDOMNode(p.node)) {
52
+ return true;
53
+ }
54
+ if (!Utils_1.default.isDOMInternalNode(p.node) &&
55
+ !Utils_1.default.isDetachedDOMNode(p.node) &&
56
+ !Utils_1.default.isFiberNode(p.node)) {
57
+ return false;
58
+ }
59
+ p = p.next;
60
+ }
61
+ return true;
62
+ }
@@ -0,0 +1,15 @@
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 { LeakTracePathItem } from '../../Types';
11
+ import { ILeakTraceFilterRule, LeakTraceFilterOptions, TraceDecision } from '../BaseTraceFilter.rule';
12
+ export declare class FilterShadowRootTraceRule implements ILeakTraceFilterRule {
13
+ filter(p: LeakTracePathItem, options?: LeakTraceFilterOptions): TraceDecision;
14
+ }
15
+ //# sourceMappingURL=FilterShadowRootTrace.rule.d.ts.map
@@ -0,0 +1,44 @@
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.FilterShadowRootTraceRule = void 0;
16
+ const Config_1 = __importDefault(require("../../Config"));
17
+ const Utils_1 = __importDefault(require("../../Utils"));
18
+ const BaseTraceFilter_rule_1 = require("../BaseTraceFilter.rule");
19
+ class FilterShadowRootTraceRule {
20
+ filter(p, options = {}) {
21
+ var _a;
22
+ const curConfig = (_a = options.config) !== null && _a !== void 0 ? _a : Config_1.default;
23
+ // if the path has pattern: ShadowRoot -> DetachedElement
24
+ if (curConfig.hideBrowserLeak && shadowRootRetainsDetachedElement(p)) {
25
+ return BaseTraceFilter_rule_1.TraceDecision.NOT_INSIGHTFUL;
26
+ }
27
+ return BaseTraceFilter_rule_1.TraceDecision.MAYBE_INSIGHTFUL;
28
+ }
29
+ }
30
+ exports.FilterShadowRootTraceRule = FilterShadowRootTraceRule;
31
+ // check if the path has pattern: ShadowRoot -> DetachedElement
32
+ function shadowRootRetainsDetachedElement(path) {
33
+ let p = path;
34
+ // find the ShadowRoot
35
+ while (p && p.node && p.node.name !== 'ShadowRoot') {
36
+ p = p.next;
37
+ if (!p) {
38
+ return false;
39
+ }
40
+ }
41
+ p = p.next;
42
+ // check if the node is a detached element
43
+ return !!p && Utils_1.default.isDetachedDOMNode(p.node);
44
+ }
@@ -0,0 +1,15 @@
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 { LeakTracePathItem } from '../../Types';
11
+ import { ILeakTraceFilterRule, LeakTraceFilterOptions, TraceDecision } from '../BaseTraceFilter.rule';
12
+ export declare class FilterStyleEngineTraceRule implements ILeakTraceFilterRule {
13
+ filter(p: LeakTracePathItem, options?: LeakTraceFilterOptions): TraceDecision;
14
+ }
15
+ //# sourceMappingURL=FilterStyleEngineTrace.rule.d.ts.map
@@ -0,0 +1,49 @@
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.FilterStyleEngineTraceRule = void 0;
16
+ const Config_1 = __importDefault(require("../../Config"));
17
+ const Utils_1 = __importDefault(require("../../Utils"));
18
+ const BaseTraceFilter_rule_1 = require("../BaseTraceFilter.rule");
19
+ class FilterStyleEngineTraceRule {
20
+ filter(p, options = {}) {
21
+ var _a;
22
+ const curConfig = (_a = options.config) !== null && _a !== void 0 ? _a : Config_1.default;
23
+ // if the path has pattern: StyleEngine -> InternalNode -> DetachedElement
24
+ if (curConfig.hideBrowserLeak && styleEngineRetainsDetachedElement(p)) {
25
+ return BaseTraceFilter_rule_1.TraceDecision.NOT_INSIGHTFUL;
26
+ }
27
+ return BaseTraceFilter_rule_1.TraceDecision.MAYBE_INSIGHTFUL;
28
+ }
29
+ }
30
+ exports.FilterStyleEngineTraceRule = FilterStyleEngineTraceRule;
31
+ // check if the path has pattern: StyleEngine -> InternalNode -> DetachedElement
32
+ function styleEngineRetainsDetachedElement(path) {
33
+ let p = path;
34
+ // find the StyleEngine
35
+ while (p && p.node && p.node.name !== 'StyleEngine') {
36
+ p = p.next;
37
+ if (!p) {
38
+ return false;
39
+ }
40
+ }
41
+ p = p.next;
42
+ // StyleEngine is not poining to InternalNode
43
+ if (!p || !p.node || p.node.name !== 'InternalNode') {
44
+ return false;
45
+ }
46
+ p = p.next;
47
+ // check if the InternalNode is pointing to a detached element
48
+ return !!p && Utils_1.default.isDetachedDOMNode(p.node);
49
+ }
@@ -187,6 +187,7 @@ class LeakClusterLogger {
187
187
  leak_trace_summary: trace.getTraceSummary(),
188
188
  interaction_vector: interactionVector,
189
189
  meta_data: JSON.stringify({
190
+ extraRunInfo: Utils_1.default.mapToObject(Config_1.default.extraRunInfoMap),
190
191
  browser_info: BrowserInfo_1.default,
191
192
  visit_plan: tabsOrder,
192
193
  trace_record: TraceBucket_1.default.pathToTrace(cluster.path),
@@ -428,15 +428,29 @@ class TraceFinder {
428
428
  return false;
429
429
  }
430
430
  isLessPreferableEdge(edge) {
431
+ const fromNode = edge.fromNode;
432
+ const toNode = edge.toNode;
431
433
  // pending activities -> DOM element is less preferrable
432
- if (Utils_1.default.isPendingActivityNode(edge.fromNode) &&
433
- Utils_1.default.isDOMNodeIncomplete(edge.toNode)) {
434
+ if (Utils_1.default.isPendingActivityNode(fromNode) &&
435
+ Utils_1.default.isDOMNodeIncomplete(toNode)) {
436
+ return true;
437
+ }
438
+ // detached DOM node -> non-detached DOM node is less preferable
439
+ if (Utils_1.default.isDetachedDOMNode(fromNode) &&
440
+ Utils_1.default.isDOMNodeIncomplete(toNode) &&
441
+ !Utils_1.default.isDetachedDOMNode(toNode)) {
442
+ return true;
443
+ }
444
+ // non-detached DOM node -> detached DOM node is less preferable
445
+ if (Utils_1.default.isDOMNodeIncomplete(fromNode) &&
446
+ !Utils_1.default.isDetachedDOMNode(fromNode) &&
447
+ Utils_1.default.isDetachedDOMNode(toNode)) {
434
448
  return true;
435
449
  }
436
450
  return Config_1.default.edgeNameGreyList.has(String(edge.name_or_index));
437
451
  }
438
452
  isLessPreferableNode(node) {
439
- return Config_1.default.nodeNameGreyList.has(node.name);
453
+ return Config_1.default.nodeNameGreyList.has(node.name) || Utils_1.default.isCppRootsNode(node);
440
454
  }
441
455
  // each edge is indexed by fromNode's ID, toNode's ID, edge name, and edge type
442
456
  getEdgeKey(edge) {
@@ -37,7 +37,7 @@ export default class NormalizedTrace {
37
37
  private static buildTraceToPathMap;
38
38
  private static pushLeakPathToCluster;
39
39
  private static initEmptyCluster;
40
- static clusterControlTreatmentPaths(controlPaths: LeakTracePathItem[], controlSnapshot: IHeapSnapshot, treatmentPaths: LeakTracePathItem[], treatmentSnapshot: IHeapSnapshot, aggregateDominatorMetrics: AggregateNodeCb, option?: {
40
+ static clusterControlTreatmentPaths(leakPathsFromControlRuns: LeakTracePathItem[][], controlSnapshots: IHeapSnapshot[], treatmentPaths: LeakTracePathItem[], treatmentSnapshot: IHeapSnapshot, aggregateDominatorMetrics: AggregateNodeCb, option?: {
41
41
  strategy?: IClusterStrategy;
42
42
  }): ControlTreatmentClusterResult;
43
43
  static generateUnClassifiedClusters(paths: LeakTracePathItem[], snapshot: IHeapSnapshot, aggregateDominatorMetrics: AggregateNodeCb): TraceCluster[];
@@ -22,6 +22,7 @@ const TraceSimilarityStrategy_1 = __importDefault(require("./strategies/TraceSim
22
22
  const TraceAsClusterStrategy_1 = __importDefault(require("./strategies/TraceAsClusterStrategy"));
23
23
  const MLTraceSimilarityStrategy_1 = __importDefault(require("./strategies/MLTraceSimilarityStrategy"));
24
24
  const ClusterUtils_1 = require("./ClusterUtils");
25
+ const TraceSampler_1 = __importDefault(require("../lib/TraceSampler"));
25
26
  // sync up with html/intern/js/webspeed/memlab/lib/LeakCluster.js
26
27
  class NormalizedTrace {
27
28
  constructor(p = null, snapshot = null) {
@@ -127,26 +128,19 @@ class NormalizedTrace {
127
128
  return Math.max(30, Utils_1.default.getNumberAtPercentile(lengthArr, 80));
128
129
  }
129
130
  static samplePaths(paths) {
130
- const maxCount = 5000;
131
+ const maxCount = Config_1.default.maxSamplesForClustering;
131
132
  if (paths.length <= maxCount) {
132
133
  return [...paths];
133
134
  }
134
- const sampleRatio = Math.min(1, maxCount / paths.length);
135
- if (sampleRatio < 1) {
136
- Console_1.default.warning('Sampling trace due to a large number of traces:');
137
- Console_1.default.lowLevel(` Number of Traces: ${paths.length}`);
138
- Console_1.default.lowLevel(` Sampling Ratio: ${Utils_1.default.getReadablePercent(sampleRatio)}`);
139
- }
135
+ const sampler = new TraceSampler_1.default(paths.length);
140
136
  const ret = [];
141
137
  const samplePathMaxLength = NormalizedTrace.getSamplePathMaxLength(paths);
142
138
  if (Config_1.default.verbose) {
143
139
  Console_1.default.lowLevel(` Sample Trace's Max Length: ${samplePathMaxLength}`);
144
140
  }
141
+ paths = paths.filter(p => Utils_1.default.getLeakTracePathLength(p) <= samplePathMaxLength);
145
142
  for (const p of paths) {
146
- if (Utils_1.default.getLeakTracePathLength(p) > samplePathMaxLength) {
147
- continue;
148
- }
149
- if (Math.random() < sampleRatio) {
143
+ if (sampler.sample()) {
150
144
  ret.push(p);
151
145
  }
152
146
  else {
@@ -157,6 +151,9 @@ class NormalizedTrace {
157
151
  }
158
152
  }
159
153
  }
154
+ if (Config_1.default.verbose) {
155
+ Console_1.default.lowLevel(`Number of samples after sampling: ${ret.length}.`);
156
+ }
160
157
  return ret;
161
158
  }
162
159
  static diffTraces(newTraces, existingTraces, // existing representative traces
@@ -194,6 +191,11 @@ class NormalizedTrace {
194
191
  }
195
192
  return traceToClusterMap.get(trace);
196
193
  };
194
+ if (Config_1.default.isContinuousTest) {
195
+ Console_1.default.lowLevel(`${staleClusters.length} stale clusters`);
196
+ Console_1.default.lowLevel(`${clustersToAdd.length} new clusters`);
197
+ Console_1.default.lowLevel(`${allClusters.length} clusters in total`);
198
+ }
197
199
  return {
198
200
  staleClusters: staleClusters.map(traceToCluster),
199
201
  clustersToAdd: clustersToAdd.map(traceToCluster),
@@ -308,19 +310,21 @@ class NormalizedTrace {
308
310
  leakedNodeIds: new Set(),
309
311
  };
310
312
  }
311
- static clusterControlTreatmentPaths(controlPaths, controlSnapshot, treatmentPaths, treatmentSnapshot, aggregateDominatorMetrics, option = {}) {
313
+ static clusterControlTreatmentPaths(leakPathsFromControlRuns, controlSnapshots, treatmentPaths, treatmentSnapshot, aggregateDominatorMetrics, option = {}) {
312
314
  const result = {
313
315
  controlOnlyClusters: [],
314
316
  treatmentOnlyClusters: [],
315
317
  hybridClusters: [],
316
318
  };
317
319
  Console_1.default.overwrite('Clustering leak traces');
318
- if (controlPaths.length === 0 && treatmentPaths.length === 0) {
320
+ const totalControlPaths = leakPathsFromControlRuns.reduce((count, leakPaths) => count + leakPaths.length, 0);
321
+ if (totalControlPaths === 0 && treatmentPaths.length === 0) {
319
322
  Console_1.default.midLevel('No leaks found');
320
323
  return result;
321
324
  }
322
325
  // sample paths if there are too many
323
- controlPaths = this.samplePaths(controlPaths);
326
+ const flattenedLeakPathsFromControlRuns = leakPathsFromControlRuns.reduce((arr, leakPaths) => [...arr, ...leakPaths], []);
327
+ const controlPaths = this.samplePaths(flattenedLeakPathsFromControlRuns);
324
328
  treatmentPaths = this.samplePaths(treatmentPaths);
325
329
  // build control trace to control path map
326
330
  const controlTraceToPathMap = NormalizedTrace.buildTraceToPathMap(controlPaths);
@@ -330,6 +334,8 @@ class NormalizedTrace {
330
334
  const treatmentTraces = Array.from(treatmentTraceToPathMap.keys());
331
335
  // cluster traces from both the control group and the treatment group
332
336
  const { allClusters } = NormalizedTrace.diffTraces([...controlTraces, ...treatmentTraces], [], option);
337
+ // pick one of the control heap snapshots
338
+ const controlSnapshot = controlSnapshots[0];
333
339
  // construct TraceCluster from clustering result
334
340
  allClusters.forEach((traces) => {
335
341
  var _a, _b;
@@ -60,6 +60,7 @@ class TraceSimilarityStrategy {
60
60
  clustersToAdd.push(traceToCheck);
61
61
  clusters.push([traceToCheck]);
62
62
  }
63
+ Console_1.default.overwrite('');
63
64
  return { staleClusters, clustersToAdd, allClusters: clusters };
64
65
  }
65
66
  static isSimilarTrace(t1, t2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memlab/core",
3
- "version": "1.1.20",
3
+ "version": "1.1.22",
4
4
  "license": "MIT",
5
5
  "description": "memlab core libraries",
6
6
  "author": "Liang Gong <lgong@fb.com>",