@memlab/core 1.1.14 → 1.1.16

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.
package/dist/index.d.ts CHANGED
@@ -50,5 +50,9 @@ export * from './lib/PackageInfoLoader';
50
50
  /** @internal */
51
51
  export { default as SequentialClustering } from './trace-cluster/SequentialClustering';
52
52
  /** @internal */
53
+ export { default as MultiIterationSeqClustering } from './trace-cluster/MultiIterationSeqClustering';
54
+ /** @internal */
53
55
  export { default as TraceFinder } from './paths/TraceFinder';
56
+ /** @internal */
57
+ export * from './trace-cluster/ClusterUtils';
54
58
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -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.TraceFinder = exports.SequentialClustering = exports.EvaluationMetric = exports.NormalizedTrace = exports.leakClusterLogger = exports.ProcessManager = exports.modes = exports.constant = exports.analysis = exports.browserInfo = exports.serializer = exports.fileManager = exports.utils = exports.BaseOption = exports.info = exports.config = exports.registerPackage = void 0;
38
+ exports.TraceFinder = exports.MultiIterationSeqClustering = exports.SequentialClustering = exports.EvaluationMetric = exports.NormalizedTrace = exports.leakClusterLogger = exports.ProcessManager = exports.modes = exports.constant = exports.analysis = exports.browserInfo = exports.serializer = exports.fileManager = exports.utils = exports.BaseOption = exports.info = exports.config = exports.registerPackage = void 0;
39
39
  const path_1 = __importDefault(require("path"));
40
40
  const PackageInfoLoader_1 = require("./lib/PackageInfoLoader");
41
41
  /** @internal */
@@ -101,5 +101,10 @@ __exportStar(require("./lib/PackageInfoLoader"), exports);
101
101
  var SequentialClustering_1 = require("./trace-cluster/SequentialClustering");
102
102
  Object.defineProperty(exports, "SequentialClustering", { enumerable: true, get: function () { return __importDefault(SequentialClustering_1).default; } });
103
103
  /** @internal */
104
+ var MultiIterationSeqClustering_1 = require("./trace-cluster/MultiIterationSeqClustering");
105
+ Object.defineProperty(exports, "MultiIterationSeqClustering", { enumerable: true, get: function () { return __importDefault(MultiIterationSeqClustering_1).default; } });
106
+ /** @internal */
104
107
  var TraceFinder_1 = require("./paths/TraceFinder");
105
108
  Object.defineProperty(exports, "TraceFinder", { enumerable: true, get: function () { return __importDefault(TraceFinder_1).default; } });
109
+ /** @internal */
110
+ __exportStar(require("./trace-cluster/ClusterUtils"), exports);
@@ -188,9 +188,13 @@ export declare class MemLabConfig {
188
188
  mlClusteringLinkageMaxDistance: number;
189
189
  mlMaxDF: number;
190
190
  isSequentialClustering: boolean;
191
+ isMultiIterationSeqClustering: boolean;
191
192
  seqClusteringSplitCount: number;
193
+ multiIterSeqClusteringIteration: number;
194
+ multiIterSeqClusteringSampleSize: number;
192
195
  seqClusteringIsRandomChunks: boolean;
193
196
  instrumentJS: boolean;
197
+ interceptScript: boolean;
194
198
  constructor(options?: ConfigOption);
195
199
  private initInternalConfigs;
196
200
  private init;
@@ -116,7 +116,9 @@ class MemLabConfig {
116
116
  this.snapshotHasDetachedness = false;
117
117
  // by default running in regular mode
118
118
  this.runningMode = RunningModes_1.default.get('regular', this);
119
- // intercept and rewrite JavaScript Code in browser
119
+ // intercept and log JavaScript Code in browser
120
+ this.interceptScript = false;
121
+ // rewrite JavaScript Code in browser
120
122
  this.instrumentJS = false;
121
123
  // external heap snapshot paths, if enabled
122
124
  this.externalSnapshotFilePaths = [];
@@ -139,8 +141,15 @@ class MemLabConfig {
139
141
  this.mlMaxDF = 1;
140
142
  // if true, evaluating results with sequential clustering
141
143
  this.isSequentialClustering = false;
144
+ // if true, evaluating results with sequential
145
+ // clustering with multiple iterations
146
+ this.isMultiIterationSeqClustering = false;
142
147
  // split the sample leak traces into 4 smaller ones by default.
143
148
  this.seqClusteringSplitCount = 4;
149
+ // the number of iterations for multi-iteration sequential clustering
150
+ this.multiIterSeqClusteringIteration = 1;
151
+ // the number of trace samples to retain from each cluster
152
+ this.multiIterSeqClusteringSampleSize = Infinity;
144
153
  // if true, split dataset into trunks
145
154
  // with random order for sequential clustering
146
155
  this.seqClusteringIsRandomChunks = false;
@@ -274,7 +274,7 @@ function JSONifyCode(node, args, options) {
274
274
  }
275
275
  function JSONifyContext(node, args, options) {
276
276
  const info = Object.create(null);
277
- const key = 'variables in scope (used by nested closures)';
277
+ const key = 'variables in defining scope (used by nested closures)';
278
278
  const closure_vars = (info[key] = Object.create(null));
279
279
  iterateSelectedEdges(node, (edge) => {
280
280
  const key = filterJSONPropName(edge.name_or_index);
package/dist/lib/Utils.js CHANGED
@@ -540,7 +540,10 @@ function getScenarioName(scenario) {
540
540
  if (!scenario.name) {
541
541
  return Constant_1.default.namePrefixForScenarioFromFile;
542
542
  }
543
- return Constant_1.default.namePrefixForScenarioFromFile + '-' + scenario.name();
543
+ if (Constant_1.default.namePrefixForScenarioFromFile.length > 0) {
544
+ return Constant_1.default.namePrefixForScenarioFromFile + '-' + scenario.name();
545
+ }
546
+ return scenario.name();
544
547
  }
545
548
  function handleSnapshotError(e) {
546
549
  haltOrThrow(e, {
@@ -821,8 +824,11 @@ function extractHTMLElementNodeInfo(node) {
821
824
  return `${node.name} ${extractFiberNodeInfo(reactFiberEdge.toNode)}`;
822
825
  }
823
826
  function hasOnlyWeakReferrers(node) {
824
- const referrer = node.findAnyReferrer((edge) => edge.type !== 'weak' && edge.type !== 'shortcut');
825
- return !!referrer;
827
+ const referrer = node.findAnyReferrer(
828
+ // shortcut references are added by JS engine
829
+ // GC won't consider shortcut as a retaining edge
830
+ (edge) => edge.type !== 'weak' && edge.type !== 'shortcut');
831
+ return referrer == null;
826
832
  }
827
833
  function getRunMetaFilePath() {
828
834
  return Config_1.default.useExternalSnapshot
@@ -7,8 +7,22 @@
7
7
  * @format
8
8
  * @oncall web_perf_infra
9
9
  */
10
+ import type { LeakTrace } from '../lib/Types';
10
11
  declare const _default: {
11
- isSimilarTrace: (t1: import("..").LeakTrace, t2: import("..").LeakTrace) => boolean;
12
+ isSimilarTrace: (t1: LeakTrace, t2: LeakTrace) => boolean;
12
13
  };
13
14
  export default _default;
15
+ /**
16
+ * const elements = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
17
+ * randomChunks(elements, 3) -> [[4, 8, 3], [9, 5, 1], [2, 6, 7, 10]]
18
+ * @internal
19
+ */
20
+ export declare const randomChunks: <T>(items: T[], n: number) => T[][];
21
+ /**
22
+ * chunks(elements, 3) -> [[1, 2, 3], [4, 5, 6], [7, 8, 9, 10]]
23
+ * @internal
24
+ */
25
+ export declare const chunks: <T>(items: T[], n: number) => T[][];
26
+ /** @internal*/
27
+ export declare const lastNodeFromTrace: (trace: LeakTrace) => import("../lib/Types").LeakTraceElement;
14
28
  //# sourceMappingURL=ClusterUtils.d.ts.map
@@ -12,6 +12,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.lastNodeFromTrace = exports.chunks = exports.randomChunks = void 0;
15
16
  const ClusterUtilsHelper_1 = __importDefault(require("./ClusterUtilsHelper"));
16
17
  const ClusteringHeuristics_1 = __importDefault(require("./ClusteringHeuristics"));
17
18
  exports.default = ClusterUtilsHelper_1.default.initialize(ClusteringHeuristics_1.default);
19
+ /**
20
+ * const elements = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
21
+ * randomChunks(elements, 3) -> [[4, 8, 3], [9, 5, 1], [2, 6, 7, 10]]
22
+ * @internal
23
+ */
24
+ const randomChunks = (items, n) => {
25
+ const array = [...items];
26
+ const size = Math.floor(array.length / n);
27
+ const chunks = [];
28
+ for (let i = 0; i < n - 1; i++) {
29
+ const chunk = [];
30
+ for (let j = 0; j < size; j++) {
31
+ const idx = Math.floor(Math.random() * array.length);
32
+ chunk[j] = array[idx];
33
+ array.splice(idx, 1);
34
+ }
35
+ chunks.push(chunk);
36
+ }
37
+ chunks.push(array);
38
+ return chunks;
39
+ };
40
+ exports.randomChunks = randomChunks;
41
+ /**
42
+ * chunks(elements, 3) -> [[1, 2, 3], [4, 5, 6], [7, 8, 9, 10]]
43
+ * @internal
44
+ */
45
+ const chunks = (items, n) => {
46
+ const array = [...items];
47
+ const size = Math.floor(array.length / n);
48
+ const chunks = [];
49
+ for (let i = 0; i < n - 1; i++) {
50
+ const chunk = array.splice(0, size);
51
+ chunks.push(chunk);
52
+ }
53
+ chunks.push(array);
54
+ return chunks;
55
+ };
56
+ exports.chunks = chunks;
57
+ /** @internal*/
58
+ const lastNodeFromTrace = (trace) => trace[trace.length - 1];
59
+ exports.lastNodeFromTrace = lastNodeFromTrace;
@@ -0,0 +1,21 @@
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 { LeakTrace } from '../lib/Types';
11
+ declare type ClusterOption = {
12
+ numberOfIteration?: number;
13
+ numberOfTraceToRetainInCluster?: number;
14
+ };
15
+ export default class MultiIterationSeqClustering {
16
+ private traceSimilarity;
17
+ constructor();
18
+ cluster(newLeakTraces: LeakTrace[], options?: ClusterOption): LeakTrace[][];
19
+ }
20
+ export {};
21
+ //# sourceMappingURL=MultiIterationSeqClustering.d.ts.map
@@ -0,0 +1,112 @@
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 ClusterUtils_1 = require("./ClusterUtils");
16
+ const TraceSimilarityStrategy_1 = __importDefault(require("./strategies/TraceSimilarityStrategy"));
17
+ const MLTraceSimilarityStrategy_1 = __importDefault(require("./strategies/MLTraceSimilarityStrategy"));
18
+ const Config_1 = __importDefault(require("../lib/Config"));
19
+ const Utils_1 = __importDefault(require("../lib/Utils"));
20
+ const Console_1 = __importDefault(require("../lib/Console"));
21
+ class MultiIterationSeqClustering {
22
+ constructor() {
23
+ this.traceSimilarity = Config_1.default.isMLClustering
24
+ ? new MLTraceSimilarityStrategy_1.default()
25
+ : new TraceSimilarityStrategy_1.default();
26
+ }
27
+ cluster(newLeakTraces, options = {}) {
28
+ const maxNumOfSampleTraceInCluster = positiveIntOrDefaultValue(options.numberOfTraceToRetainInCluster, Infinity);
29
+ const numIteration = positiveIntOrDefaultValue(options.numberOfIteration, 1);
30
+ if (Config_1.default.verbose) {
31
+ Console_1.default.lowLevel(`maxNumOfSampleTraceInCluster: ${maxNumOfSampleTraceInCluster}`);
32
+ Console_1.default.lowLevel(`numIteration: ${numIteration}`);
33
+ }
34
+ // build trace and id mapping
35
+ const traceId2RepTraceId = new Map();
36
+ const traceId2Trace = new Map();
37
+ for (const trace of newLeakTraces) {
38
+ traceId2Trace.set(traceId(trace), trace);
39
+ }
40
+ // split all traces into several batches
41
+ const splitFn = Config_1.default.seqClusteringIsRandomChunks ? ClusterUtils_1.randomChunks : ClusterUtils_1.chunks;
42
+ const traceGroups = splitFn(newLeakTraces, numIteration);
43
+ let clusteredTraceSamples = [];
44
+ for (let iter = 0; iter < numIteration; ++iter) {
45
+ Console_1.default.overwrite(`Iteration: ${iter + 1}`);
46
+ // mix the current traces to cluster with all clustered trace samples
47
+ const curTraceGroup = traceGroups[iter].concat(clusteredTraceSamples);
48
+ // cluster the trace group
49
+ const { allClusters: clusters } = this.traceSimilarity.diffTraces(curTraceGroup, []);
50
+ // assign trace id to representative trace id
51
+ updateTraceId2RepTraceIdMap(clusters, traceId2RepTraceId);
52
+ // sample each group
53
+ for (let i = 0; i < clusters.length; ++i) {
54
+ clusters[i] = clusters[i].slice(0, maxNumOfSampleTraceInCluster);
55
+ }
56
+ // update samples
57
+ clusteredTraceSamples = clusters.reduce((acc, cluster) => acc.concat(cluster), []);
58
+ }
59
+ // rebuild full clusters based on the mappings
60
+ const repTraceId2Cluster = new Map();
61
+ for (const id of traceId2RepTraceId.keys()) {
62
+ const repTraceId = traceId2RepTraceId.get(id);
63
+ if (!repTraceId2Cluster.has(repTraceId)) {
64
+ repTraceId2Cluster.set(repTraceId, []);
65
+ }
66
+ const cluster = repTraceId2Cluster.get(repTraceId);
67
+ if (cluster.length === 0) {
68
+ const repTrace = traceId2Trace.get(repTraceId);
69
+ cluster.push(repTrace);
70
+ }
71
+ if (id !== repTraceId) {
72
+ const trace = traceId2Trace.get(id);
73
+ cluster.push(trace);
74
+ }
75
+ }
76
+ return Array.from(repTraceId2Cluster.values());
77
+ }
78
+ }
79
+ exports.default = MultiIterationSeqClustering;
80
+ function traceId(trace) {
81
+ const lastNode = (0, ClusterUtils_1.lastNodeFromTrace)(trace);
82
+ if (lastNode.id == null) {
83
+ throw Utils_1.default.haltOrThrow('last node id missing');
84
+ }
85
+ return lastNode.id;
86
+ }
87
+ function updateTraceId2RepTraceIdMap(clusters, traceId2RepTraceId) {
88
+ for (const cluster of clusters) {
89
+ const repTrace = cluster[0];
90
+ for (const trace of cluster) {
91
+ traceId2RepTraceId.set(traceId(trace), traceId(repTrace));
92
+ }
93
+ }
94
+ // update trace id to representative trace id closure
95
+ for (const id of traceId2RepTraceId.keys()) {
96
+ const queue = [];
97
+ let cur = id;
98
+ let repTraceId = traceId2RepTraceId.get(cur);
99
+ while (repTraceId !== cur) {
100
+ queue.push(cur);
101
+ cur = repTraceId;
102
+ repTraceId = traceId2RepTraceId.get(cur);
103
+ }
104
+ for (const idInQueue of queue) {
105
+ traceId2RepTraceId.set(idInQueue, repTraceId);
106
+ }
107
+ }
108
+ return traceId2RepTraceId;
109
+ }
110
+ function positiveIntOrDefaultValue(v, d) {
111
+ return typeof v !== 'number' || v <= 0 ? d : v;
112
+ }
@@ -21,6 +21,7 @@ const TraceElement_1 = require("./TraceElement");
21
21
  const TraceSimilarityStrategy_1 = __importDefault(require("./strategies/TraceSimilarityStrategy"));
22
22
  const TraceAsClusterStrategy_1 = __importDefault(require("./strategies/TraceAsClusterStrategy"));
23
23
  const MLTraceSimilarityStrategy_1 = __importDefault(require("./strategies/MLTraceSimilarityStrategy"));
24
+ const ClusterUtils_1 = require("./ClusterUtils");
24
25
  // sync up with html/intern/js/webspeed/memlab/lib/LeakCluster.js
25
26
  class NormalizedTrace {
26
27
  constructor(p = null, snapshot = null) {
@@ -166,10 +167,9 @@ class NormalizedTrace {
166
167
  return NormalizedTrace.clusteredLeakTracesToRecord(allClusters);
167
168
  }
168
169
  static clusteredLeakTracesToRecord(allClusters) {
169
- const lastNodeFromTrace = (trace) => trace[trace.length - 1];
170
170
  const labaledLeakTraces = allClusters.reduce((acc, bucket) => {
171
- const lastNodeFromFirstTrace = lastNodeFromTrace(bucket[0]);
172
- bucket.map(lastNodeFromTrace).forEach(lastNodeInTrace => {
171
+ const lastNodeFromFirstTrace = (0, ClusterUtils_1.lastNodeFromTrace)(bucket[0]);
172
+ bucket.map(ClusterUtils_1.lastNodeFromTrace).forEach(lastNodeInTrace => {
173
173
  if (lastNodeInTrace.id == null || lastNodeFromFirstTrace.id == null) {
174
174
  throw new Error('node id not found in last node of the leak trace');
175
175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memlab/core",
3
- "version": "1.1.14",
3
+ "version": "1.1.16",
4
4
  "license": "MIT",
5
5
  "description": "memlab core libraries",
6
6
  "author": "Liang Gong <lgong@fb.com>",