@memlab/cli 1.0.23 → 1.0.24

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.
@@ -27,6 +27,7 @@ export default class BaseCommand extends Command {
27
27
  getExamples(): string[];
28
28
  getCategory(): CommandCategory;
29
29
  getDescription(): string;
30
+ getDocumenation(): string;
30
31
  getPrerequisites(): BaseCommand[];
31
32
  isInternalCommand(): boolean;
32
33
  getOptions(): BaseOption[];
@@ -101,6 +101,12 @@ class BaseCommand extends Command {
101
101
  const className = this.constructor.name;
102
102
  throw new Error(`${className}.getDescription is not implemented`);
103
103
  }
104
+ // More detailed description or documentation about this command.
105
+ // This will be printed as helper text in CLI for a specific command.
106
+ // Documentation generator will also use the description returned here.
107
+ getDocumenation() {
108
+ return '';
109
+ }
104
110
  // get a sequence of commands that must be executed before
105
111
  // running this command
106
112
  getPrerequisites() {
@@ -86,9 +86,8 @@ class RunMeasureCommand extends BaseCommand_1.default {
86
86
  ];
87
87
  }
88
88
  run(options) {
89
- var _a, _b;
90
89
  return __awaiter(this, void 0, void 0, function* () {
91
- const numRuns = (_b = (_a = options.configFromOptions) === null || _a === void 0 ? void 0 : _a.numOfRuns) !== null && _b !== void 0 ? _b : NumberOfRunsOption_1.default.DEFAULT_NUM_RUNS;
90
+ const numRuns = NumberOfRunsOption_1.default.getParsedOption(options.configFromOptions);
92
91
  core_1.config.runningMode = core_1.modes.get('measure', core_1.config);
93
92
  for (let i = 0; i < numRuns; ++i) {
94
93
  yield (0, Snapshot_1.runPageInteractionFromCLI)();
@@ -7,11 +7,21 @@
7
7
  * @format
8
8
  * @oncall web_perf_infra
9
9
  */
10
+ import type { ParsedArgs } from 'minimist';
10
11
  import type { BaseOption, CLIOptions } from '@memlab/core';
11
12
  import BaseCommand, { CommandCategory } from '../../BaseCommand';
13
+ export declare type CheckLeakCommandOptions = {
14
+ isMLClustering?: boolean;
15
+ };
12
16
  export default class CheckLeakCommand extends BaseCommand {
17
+ private isMLClustering;
18
+ private isMLClusteringSettingCache;
19
+ protected useDefaultMLClusteringSetting(cliArgs: ParsedArgs): void;
20
+ protected restoreDefaultMLClusteringSetting(cliArgs: ParsedArgs): void;
21
+ constructor(options?: CheckLeakCommandOptions);
13
22
  getCommandName(): string;
14
23
  getDescription(): string;
24
+ getDocumenation(): string;
15
25
  getCategory(): CommandCategory;
16
26
  getPrerequisites(): BaseCommand[];
17
27
  getOptions(): BaseOption[];
@@ -1,13 +1,4 @@
1
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
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
3
  if (k2 === undefined) k2 = k;
13
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -64,12 +55,37 @@ const MLClusteringMaxDFOption_1 = __importDefault(require("../../options/MLClust
64
55
  const CleanupSnapshotOption_1 = __importDefault(require("../../options/heap/CleanupSnapshotOption"));
65
56
  const SetWorkingDirectoryOption_1 = __importDefault(require("../../options/SetWorkingDirectoryOption"));
66
57
  class CheckLeakCommand extends BaseCommand_1.default {
58
+ constructor(options = {}) {
59
+ super();
60
+ this.isMLClustering = false;
61
+ this.isMLClusteringSettingCache = false;
62
+ this.isMLClustering = !!(options === null || options === void 0 ? void 0 : options.isMLClustering);
63
+ }
64
+ useDefaultMLClusteringSetting(cliArgs) {
65
+ if (!MLClusteringOption_1.default.hasOptionSet(cliArgs)) {
66
+ core_1.config.isMLClustering = this.isMLClustering;
67
+ this.isMLClusteringSettingCache = core_1.config.isMLClustering;
68
+ }
69
+ }
70
+ restoreDefaultMLClusteringSetting(cliArgs) {
71
+ if (!MLClusteringOption_1.default.hasOptionSet(cliArgs)) {
72
+ core_1.config.isMLClustering = this.isMLClusteringSettingCache;
73
+ }
74
+ }
67
75
  getCommandName() {
68
76
  return 'find-leaks';
69
77
  }
70
78
  getDescription() {
71
79
  return 'find memory leaks in heap snapshots';
72
80
  }
81
+ getDocumenation() {
82
+ return `There are three ways to specify inputs for the \`memlab find-leaks\` command:
83
+ 1. \`--baseline\`, \`--target\`, \`--final\` specifies each snapshot input individually;
84
+ 2. \`--snapshot-dir\` specifies the directory that holds all three heap snapshot files (MemLab will assign baseline, target, and final based on alphabetic order of the file);
85
+ 3. \`--work-dir\` specifies the output working directory of the \`memlab run\` or the \`memlab snapshot\` command;
86
+
87
+ Please only use one of the three ways to specify the input.`;
88
+ }
73
89
  getCategory() {
74
90
  return BaseCommand_1.CommandCategory.COMMON;
75
91
  }
@@ -100,8 +116,15 @@ class CheckLeakCommand extends BaseCommand_1.default {
100
116
  return __awaiter(this, void 0, void 0, function* () {
101
117
  const workDir = (_a = options.configFromOptions) === null || _a === void 0 ? void 0 : _a.workDir;
102
118
  core_1.fileManager.initDirs(core_1.config, { workDir });
119
+ const { runMetaInfoManager } = core_1.runInfoUtils;
120
+ runMetaInfoManager.setConfigFromRunMeta({
121
+ workDir,
122
+ silentFail: true,
123
+ });
103
124
  core_1.config.chaseWeakMapEdge = false;
125
+ this.useDefaultMLClusteringSetting(options.cliArgs);
104
126
  yield core_1.analysis.checkLeak();
127
+ this.restoreDefaultMLClusteringSetting(options.cliArgs);
105
128
  const configFromOptions = (_b = options.configFromOptions) !== null && _b !== void 0 ? _b : {};
106
129
  if (configFromOptions['cleanUpSnapshot']) {
107
130
  core_1.fileManager.removeSnapshotFiles();
@@ -7,9 +7,8 @@
7
7
  * @format
8
8
  * @oncall web_perf_infra
9
9
  */
10
- import { CLIOptions } from '@memlab/core';
10
+ import type { BaseOption, CLIOptions } from '@memlab/core';
11
11
  import BaseCommand, { CommandCategory } from '../../BaseCommand';
12
- import { BaseOption } from '@memlab/core';
13
12
  export default class CheckLeakCommand extends BaseCommand {
14
13
  getCommandName(): string;
15
14
  getDescription(): string;
@@ -1,13 +1,4 @@
1
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
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
3
  if (k2 === undefined) k2 = k;
13
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -45,9 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
45
36
  };
46
37
  Object.defineProperty(exports, "__esModule", { value: true });
47
38
  const core_1 = require("@memlab/core");
48
- const core_2 = require("@memlab/core");
49
39
  const BaseCommand_1 = __importStar(require("../../BaseCommand"));
50
- const core_3 = require("@memlab/core");
51
40
  const JSEngineOption_1 = __importDefault(require("../../options/heap/JSEngineOption"));
52
41
  const InitDirectoryCommand_1 = __importDefault(require("../InitDirectoryCommand"));
53
42
  const OversizeThresholdOption_1 = __importDefault(require("../../options/heap/OversizeThresholdOption"));
@@ -92,18 +81,23 @@ class CheckLeakCommand extends BaseCommand_1.default {
92
81
  run(options) {
93
82
  var _a, _b;
94
83
  return __awaiter(this, void 0, void 0, function* () {
95
- core_3.config.chaseWeakMapEdge = false;
84
+ core_1.config.chaseWeakMapEdge = false;
96
85
  // double check parameters
97
- if (!((_a = options.configFromOptions) === null || _a === void 0 ? void 0 : _a.controlWorkDir) ||
86
+ if (!((_a = options.configFromOptions) === null || _a === void 0 ? void 0 : _a.controlWorkDirs) ||
98
87
  !((_b = options.configFromOptions) === null || _b === void 0 ? void 0 : _b.treatmentWorkDir)) {
99
- core_2.info.error('Please specify control and test working directory');
88
+ core_1.info.error('Please specify control and test working directory');
100
89
  throw core_1.utils.haltOrThrow('No control or test working directory specified');
101
90
  }
102
91
  // get parameters
103
- const controlWorkDir = options.configFromOptions['controlWorkDir'];
92
+ const controlWorkDirs = options.configFromOptions['controlWorkDirs'];
104
93
  const treatmentWorkDir = options.configFromOptions['treatmentWorkDir'];
94
+ const { runMetaInfoManager } = core_1.runInfoUtils;
95
+ runMetaInfoManager.setConfigFromRunMeta({
96
+ workDir: treatmentWorkDir,
97
+ silentFail: true,
98
+ });
105
99
  // diff memory leaks
106
- yield core_3.analysis.diffLeakByWorkDir({ controlWorkDir, treatmentWorkDir });
100
+ yield core_1.analysis.diffLeakByWorkDir({ controlWorkDirs, treatmentWorkDir });
107
101
  });
108
102
  }
109
103
  }
@@ -12,6 +12,7 @@ export default class CliScreen {
12
12
  private currentFocuseKey;
13
13
  private keyToComponent;
14
14
  private heapController;
15
+ private fullScreenComponent;
15
16
  constructor(title: string, heap: IHeapSnapshot, objectCategory: Map<string, ComponentDataItem[]>);
16
17
  private setFirstObjectAsCurrrent;
17
18
  private initScreen;
@@ -19,9 +20,12 @@ export default class CliScreen {
19
20
  start(): void;
20
21
  private registerEvents;
21
22
  private registerScreenResize;
23
+ private updateAllComponentsSize;
22
24
  private updateComponentSize;
23
25
  private updateElementSize;
24
26
  private registerKeys;
27
+ private makeComponentFullScreen;
28
+ private makeNoComponentFullScreen;
25
29
  private addComponentToFocusKeyMap;
26
30
  private getNextFocusKey;
27
31
  private initClusteredObjectBox;
@@ -29,6 +33,7 @@ export default class CliScreen {
29
33
  private initReferrerBox;
30
34
  private getReferrerBoxSize;
31
35
  private initObjectBox;
36
+ private getComponentFullScreenSize;
32
37
  private getObjectBoxSize;
33
38
  private initObjectPropertyBox;
34
39
  private getObjectPropertyBoxSize;
@@ -40,6 +40,7 @@ function positionToNumber(info) {
40
40
  class CliScreen {
41
41
  constructor(title, heap, objectCategory) {
42
42
  this.currentFocuseKey = 1;
43
+ this.fullScreenComponent = null;
43
44
  this.heapController = new HeapViewController_1.default(heap, objectCategory);
44
45
  this.screen = this.initScreen(title);
45
46
  const callbacks = this.initCallbacks(this.heapController, this.screen);
@@ -118,16 +119,20 @@ class CliScreen {
118
119
  }
119
120
  registerScreenResize() {
120
121
  const screen = this.screen;
121
- screen.on('resize', () => {
122
- // all boxes/lists needs to resize
123
- this.updateComponentSize(this.clusteredObjectBox, this.getClusteredObjectBoxSize());
124
- this.updateComponentSize(this.referrerBox, this.getReferrerBoxSize());
125
- this.updateComponentSize(this.objectBox, this.getObjectBoxSize());
126
- this.updateComponentSize(this.objectPropertyBox, this.getObjectPropertyBoxSize());
127
- this.updateComponentSize(this.referenceBox, this.getReferenceBoxSize());
128
- this.updateComponentSize(this.retainerTraceBox, this.getRetainerTraceBoxSize());
129
- this.updateElementSize(this.helperTextElement, this.getHelperTextSize());
130
- });
122
+ screen.on('resize', this.updateAllComponentsSize.bind(this));
123
+ }
124
+ updateAllComponentsSize() {
125
+ // all boxes/lists needs to resize
126
+ this.updateComponentSize(this.clusteredObjectBox, this.getClusteredObjectBoxSize());
127
+ this.updateComponentSize(this.referrerBox, this.getReferrerBoxSize());
128
+ this.updateComponentSize(this.objectBox, this.getObjectBoxSize());
129
+ this.updateComponentSize(this.objectPropertyBox, this.getObjectPropertyBoxSize());
130
+ this.updateComponentSize(this.referenceBox, this.getReferenceBoxSize());
131
+ this.updateComponentSize(this.retainerTraceBox, this.getRetainerTraceBoxSize());
132
+ if (this.fullScreenComponent != null) {
133
+ this.updateComponentSize(this.fullScreenComponent, this.getComponentFullScreenSize());
134
+ }
135
+ this.updateElementSize(this.helperTextElement, this.getHelperTextSize());
131
136
  }
132
137
  updateComponentSize(component, size) {
133
138
  this.updateElementSize(component.element, size);
@@ -141,7 +146,16 @@ class CliScreen {
141
146
  registerKeys() {
142
147
  const screen = this.screen;
143
148
  // Quit on Escape, q, or Control-C.
144
- screen.key(['escape', 'q', 'C-c'], () => process.exit(0));
149
+ screen.key(['escape', 'q', 'C-c'], () => {
150
+ if (this.fullScreenComponent != null) {
151
+ // exit component full screen mode
152
+ this.makeNoComponentFullScreen();
153
+ }
154
+ else {
155
+ // quit the program
156
+ process.exit(0);
157
+ }
158
+ });
145
159
  const keyToComponent = this.keyToComponent;
146
160
  const heapController = this.heapController;
147
161
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -150,13 +164,30 @@ class CliScreen {
150
164
  // focus on the selected element
151
165
  const component = keyToComponent.get(char);
152
166
  if (component) {
167
+ if (component !== this.fullScreenComponent) {
168
+ // quit full screen mode if the component to focus
169
+ // is not the current full screen component
170
+ this.makeNoComponentFullScreen();
171
+ }
153
172
  heapController.focusOnComponent(component.id);
154
173
  screen.render();
155
174
  }
156
175
  }
176
+ // enter full screen
177
+ if (char === 'f') {
178
+ this.makeComponentFullScreen(heapController.getFocusedComponent());
179
+ }
157
180
  };
158
181
  screen.on('keypress', callback);
159
182
  }
183
+ makeComponentFullScreen(component) {
184
+ this.fullScreenComponent = component;
185
+ this.updateAllComponentsSize();
186
+ }
187
+ makeNoComponentFullScreen() {
188
+ this.fullScreenComponent = null;
189
+ this.updateAllComponentsSize();
190
+ }
160
191
  addComponentToFocusKeyMap(component) {
161
192
  const key = `${this.currentFocuseKey++}`;
162
193
  this.keyToComponent.set(key, component);
@@ -208,6 +239,14 @@ class CliScreen {
208
239
  this.addComponentToFocusKeyMap(box);
209
240
  return box;
210
241
  }
242
+ getComponentFullScreenSize() {
243
+ return {
244
+ width: positionToNumber(this.screen.width) - 4,
245
+ height: positionToNumber(this.screen.height) - 5,
246
+ top: 2,
247
+ left: 2,
248
+ };
249
+ }
211
250
  getObjectBoxSize() {
212
251
  return {
213
252
  width: Math.floor(positionToNumber(this.screen.width) / 3),
@@ -282,6 +321,7 @@ class CliScreen {
282
321
  '←': '',
283
322
  '→': '',
284
323
  Enter: 'select',
324
+ f: 'full screen',
285
325
  q: 'quit',
286
326
  };
287
327
  const keysToFocus = Array.from(this.keyToComponent.keys());
@@ -22,6 +22,7 @@ export default class HeapViewController {
22
22
  private componentIdToDataMap;
23
23
  private componentIdToComponentMap;
24
24
  private heap;
25
+ private focusedComponent;
25
26
  private clusteredBox;
26
27
  private referrerBox;
27
28
  private objectBox;
@@ -30,6 +31,7 @@ export default class HeapViewController {
30
31
  private retainerTracePropertyBox;
31
32
  private scriptManager;
32
33
  constructor(heap: IHeapSnapshot, objectCategory: ObjectCategory);
34
+ getFocusedComponent(): ListComponent;
33
35
  private getFlattenHeapObjectsInfo;
34
36
  private getFlattenClusteredObjectsInfo;
35
37
  private shouldClusterCategory;
@@ -27,6 +27,9 @@ class HeapViewController {
27
27
  this.scriptManager = new e2e_1.ScriptManager();
28
28
  this.scriptManager.loadFromFiles();
29
29
  }
30
+ getFocusedComponent() {
31
+ return this.focusedComponent;
32
+ }
30
33
  getFlattenHeapObjectsInfo(objectCategory) {
31
34
  let ret = [];
32
35
  for (const category of objectCategory.keys()) {
@@ -341,7 +344,7 @@ class HeapViewController {
341
344
  this.objectBox.setLabel('Objects');
342
345
  this.setSelectedHeapObject(node);
343
346
  if (!options.skipFocus) {
344
- this.focusOnComponent(this.objectBox.id);
347
+ this.focusOnComponent(this.clusteredBox.id);
345
348
  }
346
349
  }
347
350
  focusOnComponent(componentId) {
@@ -349,6 +352,7 @@ class HeapViewController {
349
352
  for (const component of this.componentIdToComponentMap.values()) {
350
353
  if (component.id === componentId) {
351
354
  component.focus();
355
+ this.focusedComponent = component;
352
356
  const data = this.componentIdToDataMap.get(componentId);
353
357
  const selectIndex = (_a = (data && data.selectedIdx)) !== null && _a !== void 0 ? _a : -1;
354
358
  this.setSelectedHeapObjectFromComponent(componentId, selectIndex);
@@ -145,6 +145,7 @@ class ListComponent {
145
145
  }
146
146
  focus() {
147
147
  this.element.focus();
148
+ this.element.setFront();
148
149
  this.element.style.border.fg = 'white';
149
150
  this.element.style.selected = {
150
151
  bg: 'grey',
@@ -132,11 +132,16 @@ class GenerateCLIDocCommand extends BaseCommand_1.default {
132
132
  }
133
133
  writeCommand(docFile, command, indent = '') {
134
134
  const name = command.getFullCommand();
135
- const desc = core_1.utils.upperCaseFirstCharacter(command.getDescription());
135
+ const desc = core_1.utils.upperCaseFirstCharacter(command.getDescription().trim());
136
+ const cmdDoc = command.getDocumenation().trim();
136
137
  // write command title
137
138
  this.writeTextWithNewLine(docFile, `\n###${indent} memlab ${name}\n`);
138
139
  // write description
139
140
  this.writeTextWithNewLine(docFile, `${desc}\n`);
141
+ // write detailed command documentation
142
+ if (cmdDoc.length > 0) {
143
+ this.writeTextWithNewLine(docFile, `${cmdDoc}\n`);
144
+ }
140
145
  // get example
141
146
  const examples = command.getExamples();
142
147
  let example = '';
@@ -48,10 +48,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
48
48
  const chalk_1 = __importDefault(require("chalk"));
49
49
  const string_width_1 = __importDefault(require("string-width"));
50
50
  const core_1 = require("@memlab/core");
51
+ const heap_analysis_1 = require("@memlab/heap-analysis");
51
52
  const CommandOrder_1 = __importDefault(require("./lib/CommandOrder"));
52
53
  const BaseCommand_1 = __importStar(require("../../BaseCommand"));
53
54
  const UniversalOptions_1 = __importDefault(require("../../options/lib/UniversalOptions"));
54
- const heap_analysis_1 = require("@memlab/heap-analysis");
55
+ const CLIUtils_1 = require("../../lib/CLIUtils");
55
56
  class HelperCommand extends BaseCommand_1.default {
56
57
  constructor() {
57
58
  super(...arguments);
@@ -137,7 +138,7 @@ class HelperCommand extends BaseCommand_1.default {
137
138
  if (options.length === 0) {
138
139
  return '';
139
140
  }
140
- const width = Math.min(70, process.stdout.columns);
141
+ const width = Math.min(CLIUtils_1.READABLE_CMD_FLAG_WIDTH, process.stdout.columns);
141
142
  let summary = '';
142
143
  let curLine = chalk_1.default.bold(`${indent}Options:`);
143
144
  for (const option of options) {
@@ -177,25 +178,21 @@ class HelperCommand extends BaseCommand_1.default {
177
178
  }
178
179
  formatOptionText(optionText, indent, headerLength) {
179
180
  const header = chalk_1.default.green(optionText.header);
180
- const prefix = core_1.utils.repeat(' ', headerLength - optionText.header.length);
181
+ const prefix = (0, CLIUtils_1.getBlankSpaceString)(headerLength - optionText.header.length);
181
182
  const headerString = `${indent}${prefix}${header} `;
182
183
  const headerStringWidth = (0, string_width_1.default)(headerString);
183
- const maxWidth = Math.min(process.stdout.columns, 150);
184
- const descMaxWidth = maxWidth - headerStringWidth;
185
- let descString = optionText.desc.substring(0, descMaxWidth);
186
- let descStringRemain = optionText.desc.substring(descMaxWidth);
187
- while (descStringRemain.length > 0) {
188
- descString += '\n';
189
- descString += core_1.utils.repeat(' ', headerStringWidth);
190
- descString += descStringRemain.substring(0, descMaxWidth);
191
- descStringRemain = descStringRemain.substring(descMaxWidth);
192
- }
193
- return `${headerString}${descString}`;
184
+ const maxWidth = Math.min(process.stdout.columns, CLIUtils_1.READABLE_TEXT_WIDTH);
185
+ const descString = (0, CLIUtils_1.alignTextInBlock)(optionText.desc, {
186
+ leftIndent: headerStringWidth,
187
+ lineLength: maxWidth,
188
+ });
189
+ return `${headerString}${descString.substring(headerStringWidth)}`;
194
190
  }
195
191
  printCommand(command, extraIndent = '', printOptions = false) {
196
192
  const indent = ' ' + extraIndent;
197
193
  const name = command.getFullCommand();
198
- const desc = core_1.utils.upperCaseFirstCharacter(command.getDescription());
194
+ const desc = core_1.utils.upperCaseFirstCharacter(command.getDescription().trim());
195
+ const cmdDoc = command.getDocumenation().trim();
199
196
  // get example
200
197
  const examples = command.getExamples();
201
198
  let example = '';
@@ -205,6 +202,12 @@ class HelperCommand extends BaseCommand_1.default {
205
202
  const cmd = chalk_1.default.green(`memlab ${name}${example}`);
206
203
  let msg = `${indent}${cmd}`;
207
204
  msg += `\n${indent}${desc}`;
205
+ if (cmdDoc.length > 0) {
206
+ const cmdDocBlock = (0, CLIUtils_1.alignTextInBlock)(cmdDoc, {
207
+ leftIndent: indent.length + 2,
208
+ });
209
+ msg += `\n\n${chalk_1.default.grey(cmdDocBlock)}`;
210
+ }
208
211
  core_1.info.topLevel(msg);
209
212
  // print options info
210
213
  if (printOptions) {
@@ -0,0 +1,22 @@
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 { ParsedArgs } from 'minimist';
11
+ import type { AnyRecord } from '@memlab/core';
12
+ export declare type BlockTextOption = {
13
+ leftIndent?: number;
14
+ lineLength?: number;
15
+ };
16
+ export declare const READABLE_CMD_FLAG_WIDTH = 70;
17
+ export declare const READABLE_TEXT_WIDTH = 150;
18
+ export declare function filterAndGetUndefinedArgs(cliArgs: ParsedArgs): AnyRecord;
19
+ export declare function argsToString(args: AnyRecord): string;
20
+ export declare function getBlankSpaceString(length: number): string;
21
+ export declare function alignTextInBlock(text: string, options: BlockTextOption): string;
22
+ //# sourceMappingURL=CLIUtils.d.ts.map
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.alignTextInBlock = exports.getBlankSpaceString = exports.argsToString = exports.filterAndGetUndefinedArgs = exports.READABLE_TEXT_WIDTH = exports.READABLE_CMD_FLAG_WIDTH = void 0;
7
+ const string_width_1 = __importDefault(require("string-width"));
8
+ const core_1 = require("@memlab/core");
9
+ const OptionConstant_1 = __importDefault(require("../options/lib/OptionConstant"));
10
+ const breakableSymbolOnRight = new Set([
11
+ ' ',
12
+ '\t',
13
+ ',',
14
+ '.',
15
+ ':',
16
+ ';',
17
+ '!',
18
+ '?',
19
+ ')',
20
+ ']',
21
+ '}',
22
+ '>',
23
+ ]);
24
+ const breakableSymbolOnLeft = new Set([' ', '\t', '(', '[', '{', '<']);
25
+ exports.READABLE_CMD_FLAG_WIDTH = 70;
26
+ exports.READABLE_TEXT_WIDTH = 150;
27
+ function filterAndGetUndefinedArgs(cliArgs) {
28
+ const ret = Object.create(null);
29
+ const memlabFBOptionNames = new Set(Object.values(Object.assign({}, OptionConstant_1.default.optionNames)));
30
+ for (const optionName of Object.keys(cliArgs)) {
31
+ if (optionName === '_') {
32
+ continue;
33
+ }
34
+ if (memlabFBOptionNames.has(optionName)) {
35
+ continue;
36
+ }
37
+ ret[optionName] = cliArgs[optionName];
38
+ }
39
+ return ret;
40
+ }
41
+ exports.filterAndGetUndefinedArgs = filterAndGetUndefinedArgs;
42
+ function argsToString(args) {
43
+ let ret = '';
44
+ for (const optionName of Object.keys(args)) {
45
+ if (optionName === '_') {
46
+ continue;
47
+ }
48
+ const value = args[optionName];
49
+ if (value === true) {
50
+ ret += `--${optionName} `;
51
+ }
52
+ else if (Array.isArray(value)) {
53
+ value.forEach(v => {
54
+ ret += `--${optionName}=${v} `;
55
+ });
56
+ }
57
+ else {
58
+ ret += `--${optionName}=${value} `;
59
+ }
60
+ }
61
+ return ret.trim();
62
+ }
63
+ exports.argsToString = argsToString;
64
+ function getBlankSpaceString(length) {
65
+ let ret = '';
66
+ for (let i = 0; i < length; ++i) {
67
+ ret += ' ';
68
+ }
69
+ return ret;
70
+ }
71
+ exports.getBlankSpaceString = getBlankSpaceString;
72
+ function alignTextInBlock(text, options) {
73
+ var _a, _b;
74
+ const indent = (_a = options.leftIndent) !== null && _a !== void 0 ? _a : 0;
75
+ const maxLineWidth = Math.min(exports.READABLE_TEXT_WIDTH, (_b = options.lineLength) !== null && _b !== void 0 ? _b : process.stdout.columns);
76
+ if (indent < 0 || maxLineWidth <= 0 || indent >= maxLineWidth) {
77
+ throw core_1.utils.haltOrThrow('invalid indent or maximum line width');
78
+ }
79
+ const indentString = getBlankSpaceString(indent);
80
+ const inputLines = text.split('\n');
81
+ const outputLines = [];
82
+ while (inputLines.length > 0) {
83
+ const line = inputLines.shift();
84
+ // if the current line can fit in cmd row
85
+ if ((0, string_width_1.default)(indentString + line) <= maxLineWidth) {
86
+ outputLines.push(indentString + line);
87
+ continue;
88
+ }
89
+ // otherwise split the current line
90
+ const intendedSplitPoint = maxLineWidth - indent;
91
+ const splitLines = splitIntoReadableSubstrings(line, intendedSplitPoint);
92
+ const [firstLine, restLine] = splitLines;
93
+ outputLines.push(indentString + firstLine);
94
+ inputLines.unshift(restLine);
95
+ }
96
+ return outputLines.join('\n');
97
+ }
98
+ exports.alignTextInBlock = alignTextInBlock;
99
+ function splitIntoReadableSubstrings(text, intendedSplitPoint) {
100
+ if (intendedSplitPoint >= text.length) {
101
+ return [text];
102
+ }
103
+ let splitPoint = intendedSplitPoint;
104
+ while (splitPoint > 0) {
105
+ const ch = text[splitPoint];
106
+ if (breakableSymbolOnLeft.has(ch)) {
107
+ break;
108
+ }
109
+ if (splitPoint - 1 > 0 &&
110
+ breakableSymbolOnRight.has(text[splitPoint - 1])) {
111
+ break;
112
+ }
113
+ --splitPoint;
114
+ }
115
+ if (splitPoint <= 0) {
116
+ splitPoint = intendedSplitPoint;
117
+ }
118
+ // if the second line starts with a ' ',
119
+ // skip the empty space
120
+ const firstLine = text.substring(0, splitPoint);
121
+ let secondLine = text.substring(splitPoint);
122
+ if (secondLine.startsWith(' ')) {
123
+ secondLine = secondLine.substring(1);
124
+ }
125
+ if (secondLine.length === 0) {
126
+ return [firstLine];
127
+ }
128
+ return [firstLine, secondLine];
129
+ }
@@ -13,6 +13,7 @@ import { BaseOption } from '@memlab/core';
13
13
  export default class MLClusteringOption extends BaseOption {
14
14
  getOptionName(): string;
15
15
  getDescription(): string;
16
+ static hasOptionSet(args: ParsedArgs): boolean;
16
17
  parse(config: MemLabConfig, args: ParsedArgs): Promise<void>;
17
18
  }
18
19
  //# sourceMappingURL=MLClusteringOption.d.ts.map
@@ -30,6 +30,10 @@ class MLClusteringOption extends core_1.BaseOption {
30
30
  getDescription() {
31
31
  return 'use machine learning algorithms for clustering leak traces (by default, traces are clustered by heuristics)';
32
32
  }
33
+ static hasOptionSet(args) {
34
+ const name = OptionConstant_1.default.optionNames.ML_CLUSTERING;
35
+ return args[name] != null;
36
+ }
33
37
  parse(config, args) {
34
38
  return __awaiter(this, void 0, void 0, function* () {
35
39
  const name = this.getOptionName();
@@ -8,13 +8,15 @@
8
8
  * @oncall web_perf_infra
9
9
  */
10
10
  import type { ParsedArgs } from 'minimist';
11
- import type { AnyRecord, MemLabConfig } from '@memlab/core';
11
+ import type { AnyRecord, MemLabConfig, Optional } from '@memlab/core';
12
12
  import { BaseOption } from '@memlab/core';
13
13
  export default class NumberOfRunsOption extends BaseOption {
14
- static DEFAULT_NUM_RUNS: number;
14
+ private defaultRunNumber;
15
+ constructor(runNumber?: number);
15
16
  getOptionName(): string;
16
17
  getDescription(): string;
17
18
  getExampleValues(): string[];
19
+ static getParsedOption(configFromOptions: Optional<AnyRecord>): number;
18
20
  parse(config: MemLabConfig, args: ParsedArgs): Promise<AnyRecord>;
19
21
  }
20
22
  //# sourceMappingURL=NumberOfRunsOption.d.ts.map
@@ -23,7 +23,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  const core_1 = require("@memlab/core");
25
25
  const OptionConstant_1 = __importDefault(require("./lib/OptionConstant"));
26
+ const DEFAULT_NUM_RUNS = 10;
26
27
  class NumberOfRunsOption extends core_1.BaseOption {
28
+ constructor(runNumber = DEFAULT_NUM_RUNS) {
29
+ super();
30
+ this.defaultRunNumber = DEFAULT_NUM_RUNS;
31
+ this.defaultRunNumber = runNumber;
32
+ }
27
33
  getOptionName() {
28
34
  return OptionConstant_1.default.optionNames.RUN_NUM;
29
35
  }
@@ -33,16 +39,18 @@ class NumberOfRunsOption extends core_1.BaseOption {
33
39
  getExampleValues() {
34
40
  return ['5'];
35
41
  }
42
+ static getParsedOption(configFromOptions) {
43
+ const { numOfRuns } = configFromOptions !== null && configFromOptions !== void 0 ? configFromOptions : {};
44
+ const n = parseInt(`${numOfRuns}`, 10);
45
+ return isNaN(n) ? DEFAULT_NUM_RUNS : n;
46
+ }
36
47
  parse(config, args) {
37
48
  return __awaiter(this, void 0, void 0, function* () {
38
49
  const ret = Object.create(null);
39
50
  const name = this.getOptionName();
40
- ret.numOfRuns = args[name]
41
- ? args[name] | 0
42
- : NumberOfRunsOption.DEFAULT_NUM_RUNS;
51
+ ret.numOfRuns = args[name] != null ? args[name] | 0 : this.defaultRunNumber;
43
52
  return ret;
44
53
  });
45
54
  }
46
55
  }
47
56
  exports.default = NumberOfRunsOption;
48
- NumberOfRunsOption.DEFAULT_NUM_RUNS = 10;
@@ -13,8 +13,9 @@ import { BaseOption } from '@memlab/core';
13
13
  export default class SetControlWorkDirOption extends BaseOption {
14
14
  getOptionName(): string;
15
15
  getDescription(): string;
16
+ protected extractAndCheckWorkDirs(args: ParsedArgs): string[];
16
17
  parse(config: MemLabConfig, args: ParsedArgs): Promise<{
17
- controlWorkDir?: string;
18
+ controlWorkDirs?: string[];
18
19
  }>;
19
20
  }
20
21
  //# sourceMappingURL=SetControlWorkDirOption.d.ts.map
@@ -21,6 +21,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
21
21
  return (mod && mod.__esModule) ? mod : { "default": mod };
22
22
  };
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
+ const fs_1 = __importDefault(require("fs"));
24
25
  const core_1 = require("@memlab/core");
25
26
  const OptionConstant_1 = __importDefault(require("../lib/OptionConstant"));
26
27
  class SetControlWorkDirOption extends core_1.BaseOption {
@@ -30,14 +31,32 @@ class SetControlWorkDirOption extends core_1.BaseOption {
30
31
  getDescription() {
31
32
  return 'set the working directory of the control run';
32
33
  }
34
+ extractAndCheckWorkDirs(args) {
35
+ let dirs = [];
36
+ const name = this.getOptionName();
37
+ const flagValue = args[name];
38
+ if (!flagValue) {
39
+ return dirs;
40
+ }
41
+ if (Array.isArray(flagValue)) {
42
+ dirs = flagValue;
43
+ }
44
+ else {
45
+ dirs = [flagValue];
46
+ }
47
+ for (const dir of dirs) {
48
+ if (fs_1.default.existsSync(dir)) {
49
+ core_1.fileManager.createDefaultVisitOrderMetaFile({
50
+ workDir: dir,
51
+ });
52
+ }
53
+ }
54
+ return dirs;
55
+ }
33
56
  parse(config, args) {
34
57
  return __awaiter(this, void 0, void 0, function* () {
35
- const name = this.getOptionName();
36
- const ret = {};
37
- if (args[name]) {
38
- ret.controlWorkDir = args[name];
39
- }
40
- return ret;
58
+ const dirs = this.extractAndCheckWorkDirs(args);
59
+ return { controlWorkDirs: dirs };
41
60
  });
42
61
  }
43
62
  }
@@ -21,6 +21,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
21
21
  return (mod && mod.__esModule) ? mod : { "default": mod };
22
22
  };
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
+ const fs_1 = __importDefault(require("fs"));
24
25
  const core_1 = require("@memlab/core");
25
26
  const OptionConstant_1 = __importDefault(require("../lib/OptionConstant"));
26
27
  class SetTreatmentWorkDirOption extends core_1.BaseOption {
@@ -36,6 +37,11 @@ class SetTreatmentWorkDirOption extends core_1.BaseOption {
36
37
  const ret = {};
37
38
  if (args[name]) {
38
39
  ret.treatmentWorkDir = args[name];
40
+ if (fs_1.default.existsSync(ret.treatmentWorkDir)) {
41
+ core_1.fileManager.createDefaultVisitOrderMetaFile({
42
+ workDir: ret.treatmentWorkDir,
43
+ });
44
+ }
39
45
  }
40
46
  return ret;
41
47
  });
@@ -23,6 +23,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  const core_1 = require("@memlab/core");
25
25
  const OptionConstant_1 = __importDefault(require("../lib/OptionConstant"));
26
+ const OversizeThresholdOption_1 = __importDefault(require("./OversizeThresholdOption"));
26
27
  class TraceAllObjectsOption extends core_1.BaseOption {
27
28
  getOptionName() {
28
29
  return OptionConstant_1.default.optionNames.TRACE_ALL_OBJECTS;
@@ -32,8 +33,13 @@ class TraceAllObjectsOption extends core_1.BaseOption {
32
33
  }
33
34
  parse(config, args) {
34
35
  return __awaiter(this, void 0, void 0, function* () {
35
- if (args[this.getOptionName()]) {
36
- config.oversizeObjectAsLeak = true;
36
+ if (!args[this.getOptionName()]) {
37
+ return;
38
+ }
39
+ config.oversizeObjectAsLeak = true;
40
+ const overSizeOptionName = new OversizeThresholdOption_1.default().getOptionName();
41
+ // over size option will set the oversize threshold
42
+ if (!args[overSizeOptionName]) {
37
43
  config.oversizeThreshold = 0;
38
44
  }
39
45
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memlab/cli",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
4
4
  "license": "MIT",
5
5
  "description": "command line interface for memlab",
6
6
  "author": "Liang Gong <lgong@fb.com>",