ic-mops 1.4.0 → 1.5.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Mops CLI Changelog
2
2
 
3
+ ## 1.5.1
4
+ - Collapsible output of `mops bench` in a CI environment
5
+ - Fix regression in `mops bench` without `dfx.json` file (by @rvanasa)
6
+
7
+ ## 1.5.0
8
+ - Compile benchmarks with `--release` flag by default
9
+ - Respect `profile` field in `dfx.json` for benchmarks
10
+
3
11
  ## 1.4.0
4
12
  - Update `mops bench` command output:
5
13
  - Print only final results if benchmarks run in a CI environment or there is no vertical space to progressively print the results
package/bundle/cli.tgz CHANGED
Binary file
package/commands/bench.ts CHANGED
@@ -12,7 +12,7 @@ import {filesize} from 'filesize';
12
12
  import terminalSize from 'terminal-size';
13
13
  import {SemVer} from 'semver';
14
14
 
15
- import {getRootDir, readConfig} from '../mops.js';
15
+ import {getRootDir, readConfig, readDfxJson} from '../mops.js';
16
16
  import {parallel} from '../parallel.js';
17
17
  import {absToRel} from './test/utils.js';
18
18
  import {getMocVersion} from '../helpers/get-moc-version.js';
@@ -49,11 +49,12 @@ type BenchOptions = {
49
49
  compare : boolean,
50
50
  verbose : boolean,
51
51
  silent : boolean,
52
-
52
+ profile : 'Debug' | 'Release',
53
53
  };
54
54
 
55
55
  export async function bench(filter = '', optionsArg : Partial<BenchOptions> = {}) : Promise<Benchmarks> {
56
56
  let config = readConfig();
57
+ let dfxJson = readDfxJson();
57
58
 
58
59
  let defaultOptions : BenchOptions = {
59
60
  replica: config.toolchain?.['pocket-ic'] ? 'pocket-ic' : 'dfx',
@@ -66,6 +67,7 @@ export async function bench(filter = '', optionsArg : Partial<BenchOptions> = {}
66
67
  compare: false,
67
68
  verbose: false,
68
69
  silent: false,
70
+ profile: dfxJson?.profile || 'Release',
69
71
  };
70
72
 
71
73
  let options : BenchOptions = {...defaultOptions, ...optionsArg};
@@ -123,7 +125,7 @@ export async function bench(filter = '', optionsArg : Partial<BenchOptions> = {}
123
125
  fs.rmSync(benchDir, {recursive: true, force: true});
124
126
  fs.mkdirSync(benchDir, {recursive: true});
125
127
 
126
- if (!options.silent) {
128
+ if (!options.silent && !process.env.CI) {
127
129
  console.log('Benchmark files:');
128
130
  for (let file of files) {
129
131
  console.log(chalk.gray(`• ${absToRel(file)}`));
@@ -181,14 +183,75 @@ export async function bench(filter = '', optionsArg : Partial<BenchOptions> = {}
181
183
  return benchResults;
182
184
  }
183
185
 
186
+ function computeDiff(
187
+ results : Map<string, BenchResult>,
188
+ prevResults : Map<string, BenchResult> | undefined,
189
+ rows : string[],
190
+ cols : string[],
191
+ metric : keyof BenchResult,
192
+ ) : number {
193
+ let diff = 0;
194
+ let count = 0;
195
+
196
+ for (let [_rowIndex, row] of rows.entries()) {
197
+ for (let [_colIndex, col] of cols.entries()) {
198
+ let res = results.get(`${row}:${col}`);
199
+ if (res && prevResults) {
200
+ let prevRes = prevResults.get(`${row}:${col}`);
201
+ if (prevRes) {
202
+ let denom = Number(prevRes[metric]);
203
+ let numerator = Number(res[metric]) - denom;
204
+ let percent = 0;
205
+ if (denom !== 0) {
206
+ percent = (numerator / denom) * 100;
207
+ }
208
+ if (!Number.isFinite(percent)) {
209
+ percent = 0;
210
+ }
211
+ diff += percent;
212
+ count++;
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ return count > 0 ? diff / count : 0;
219
+ }
220
+
221
+ function computeDiffAll(
222
+ currentResults : Map<string, BenchResult>,
223
+ prevResults : Map<string, BenchResult> | undefined,
224
+ rows : string[],
225
+ cols : string[],
226
+ ) : number {
227
+ let metrics : (keyof BenchResult)[] = ['instructions', 'rts_heap_size', 'rts_logical_stable_memory_size', 'rts_reclaimed'];
228
+ let diff = 0;
229
+
230
+ for (let metric of metrics) {
231
+ diff += computeDiff(currentResults, prevResults, rows, cols, metric);
232
+ }
233
+
234
+ return diff;
235
+ }
236
+
184
237
  function getMocArgs(options : BenchOptions) : string {
185
238
  let args = '';
239
+
186
240
  if (options.forceGc) {
187
241
  args += ' --force-gc';
188
242
  }
243
+
189
244
  if (options.gc) {
190
245
  args += ` --${options.gc}-gc`;
191
246
  }
247
+
248
+ if (options.profile === 'Debug') {
249
+ args += ' --debug';
250
+ }
251
+ else if (options.profile === 'Release') {
252
+ args += ' --release';
253
+ }
254
+
192
255
  return args;
193
256
  }
194
257
 
@@ -261,6 +324,19 @@ async function runBenchFile(file : string, options : BenchOptions, replica : Ben
261
324
  return filesize(n, {standard: 'iec', round: 2});
262
325
  };
263
326
 
327
+ let colorizePercent = (percent : number, wrapInParens = false) : string => {
328
+ let sign = percent > 0 ? '+' : '';
329
+ let percentText = percent == 0 ? '0%' : sign + percent.toFixed(2) + '%';
330
+ let color : keyof typeof chalk = percent == 0 ? 'gray' : (percent > 0 ? 'red' : 'green');
331
+ let parens = wrapInParens ? ['(', ')'] : ['', ''];
332
+ if (process.env.CI) {
333
+ return `$${parens[0]}{\\color{${color}}${percentText.replace('%', '\\\\%')}}${parens[1]}$`;
334
+ }
335
+ else {
336
+ return `${parens[0]}${chalk[color](percentText)}${parens[1]}`;
337
+ }
338
+ };
339
+
264
340
  let getTable = (prop : keyof BenchResult) : string => {
265
341
  let resArr = [['', ...schema.cols]];
266
342
  let allZero = true;
@@ -284,15 +360,7 @@ async function runBenchFile(file : string, options : BenchOptions, replica : Ben
284
360
  if (Object.is(percent, NaN)) {
285
361
  percent = 0;
286
362
  }
287
- let sign = percent > 0 ? '+' : '';
288
- let percentText = percent == 0 ? '0%' : sign + percent.toFixed(2) + '%';
289
- let color : keyof typeof chalk = percent == 0 ? 'gray' : (percent > 0 ? 'red' : 'green');
290
- if (process.env.CI) {
291
- diff = ` $({\\color{${color}}${percentText.replace('%', '\\\\%')}})$`;
292
- }
293
- else {
294
- diff = ` (${chalk[color](percentText)})`;
295
- }
363
+ diff = ' ' + colorizePercent(percent, true);
296
364
  }
297
365
  else {
298
366
  diff = chalk.yellow(' (no previous results)');
@@ -333,14 +401,33 @@ async function runBenchFile(file : string, options : BenchOptions, replica : Ben
333
401
  let logUpdate = createLogUpdate(process.stdout, {showCursor: true});
334
402
 
335
403
  let getOutput = () => {
336
- return `
337
- \n${process.env.CI ? `## ${schema.name}` : chalk.bold(schema.name)}
338
- ${schema.description ? '\n' + (process.env.CI ? `_${schema.description}_` : chalk.gray(schema.description)) : ''}
339
- \n\n${chalk.blue('Instructions')}\n\n${getTable('instructions')}
340
- \n\n${chalk.blue('Heap')}\n\n${getTable('rts_heap_size')}
341
- \n\n${chalk.blue('Garbage Collection')}\n\n${getTable('rts_reclaimed')}
342
- ${getTable('rts_logical_stable_memory_size') ? `\n\n${chalk.blue('Stable Memory')}\n\n${getTable('rts_logical_stable_memory_size')}` : ''}
343
- `;
404
+ if (process.env.CI) {
405
+ return [
406
+ '\n<details>',
407
+ `\n<summary>${absToRel(file)} ${colorizePercent(computeDiffAll(results, prevResults, schema.rows, schema.cols), true).replace('\\\\%', '\\%')}</summary>`,
408
+ `\n${process.env.CI ? `### ${schema.name}` : chalk.bold(schema.name)}`,
409
+ `${schema.description ? '\n' + (process.env.CI ? `_${schema.description}_` : chalk.gray(schema.description)) : ''}`,
410
+ `\n\nInstructions: ${colorizePercent(computeDiff(results, prevResults, schema.rows, schema.cols, 'instructions'), false)}`,
411
+ `Heap: ${colorizePercent(computeDiff(results, prevResults, schema.rows, schema.cols, 'rts_heap_size'), false)}`,
412
+ `Stable Memory: ${colorizePercent(computeDiff(results, prevResults, schema.rows, schema.cols, 'rts_logical_stable_memory_size'), false)}`,
413
+ `Garbage Collection: ${colorizePercent(computeDiff(results, prevResults, schema.rows, schema.cols, 'rts_reclaimed'), false)}`,
414
+ `\n\n**Instructions**\n\n${getTable('instructions')}`,
415
+ `\n\n**Heap**\n\n${getTable('rts_heap_size')}`,
416
+ `\n\n**Garbage Collection**\n\n${getTable('rts_reclaimed')}`,
417
+ `${getTable('rts_logical_stable_memory_size') ? `\n\n**Stable Memory**\n\n${getTable('rts_logical_stable_memory_size')}` : ''}`,
418
+ '\n</details>',
419
+ ].join('\n');
420
+ }
421
+ else {
422
+ return `
423
+ \n${chalk.bold(schema.name)}
424
+ ${schema.description ? '\n' + chalk.gray(schema.description) : ''}
425
+ \n\n${chalk.blue('Instructions')}\n\n${getTable('instructions')}
426
+ \n\n${chalk.blue('Heap')}\n\n${getTable('rts_heap_size')}
427
+ \n\n${chalk.blue('Garbage Collection')}\n\n${getTable('rts_reclaimed')}
428
+ ${getTable('rts_logical_stable_memory_size') ? `\n\n${chalk.blue('Stable Memory')}\n\n${getTable('rts_logical_stable_memory_size')}` : ''}
429
+ `;
430
+ }
344
431
  };
345
432
 
346
433
  let canUpdateLog = !process.env.CI && !options.silent && terminalSize().rows > getOutput().split('\n').length;
@@ -433,4 +520,4 @@ async function runBenchFile(file : string, options : BenchOptions, replica : Ben
433
520
  ['rts_reclaimed', reclaimedCells],
434
521
  ],
435
522
  };
436
- }
523
+ }
@@ -5,6 +5,7 @@ import {getRootDir} from '../../mops.js';
5
5
  type DfxConfig = {
6
6
  $schema : string;
7
7
  version : number;
8
+ profile : 'Debug' | 'Release';
8
9
  canisters : {
9
10
  [key : string] : {
10
11
  type : 'motoko' | 'assets';
@@ -41,7 +42,7 @@ type DfxConfig = {
41
42
  };
42
43
  };
43
44
 
44
- function readDfxJson() : DfxConfig | Record<string, never> {
45
+ export function readDfxJson() : DfxConfig | Record<string, never> {
45
46
  let dfxJsonPath = path.resolve(getRootDir(), 'dfx.json');
46
47
  if (!existsSync(dfxJsonPath)) {
47
48
  return {};
@@ -11,6 +11,7 @@ type BenchOptions = {
11
11
  compare: boolean;
12
12
  verbose: boolean;
13
13
  silent: boolean;
14
+ profile: 'Debug' | 'Release';
14
15
  };
15
16
  export declare function bench(filter?: string, optionsArg?: Partial<BenchOptions>): Promise<Benchmarks>;
16
17
  export {};
@@ -11,7 +11,7 @@ import stringWidth from 'string-width';
11
11
  import { filesize } from 'filesize';
12
12
  import terminalSize from 'terminal-size';
13
13
  import { SemVer } from 'semver';
14
- import { getRootDir, readConfig } from '../mops.js';
14
+ import { getRootDir, readConfig, readDfxJson } from '../mops.js';
15
15
  import { parallel } from '../parallel.js';
16
16
  import { absToRel } from './test/utils.js';
17
17
  import { getMocVersion } from '../helpers/get-moc-version.js';
@@ -31,6 +31,7 @@ let globConfig = {
31
31
  };
32
32
  export async function bench(filter = '', optionsArg = {}) {
33
33
  let config = readConfig();
34
+ let dfxJson = readDfxJson();
34
35
  let defaultOptions = {
35
36
  replica: config.toolchain?.['pocket-ic'] ? 'pocket-ic' : 'dfx',
36
37
  replicaVersion: '',
@@ -42,6 +43,7 @@ export async function bench(filter = '', optionsArg = {}) {
42
43
  compare: false,
43
44
  verbose: false,
44
45
  silent: false,
46
+ profile: dfxJson?.profile || 'Release',
45
47
  };
46
48
  let options = { ...defaultOptions, ...optionsArg };
47
49
  let replicaType = options.replica ?? (config.toolchain?.['pocket-ic'] ? 'pocket-ic' : 'dfx');
@@ -88,7 +90,7 @@ export async function bench(filter = '', optionsArg = {}) {
88
90
  let benchDir = `${getRootDir()}/.mops/.bench/`;
89
91
  fs.rmSync(benchDir, { recursive: true, force: true });
90
92
  fs.mkdirSync(benchDir, { recursive: true });
91
- if (!options.silent) {
93
+ if (!options.silent && !process.env.CI) {
92
94
  console.log('Benchmark files:');
93
95
  for (let file of files) {
94
96
  console.log(chalk.gray(`• ${absToRel(file)}`));
@@ -137,6 +139,40 @@ export async function bench(filter = '', optionsArg = {}) {
137
139
  fs.rmSync(benchDir, { recursive: true, force: true });
138
140
  return benchResults;
139
141
  }
142
+ function computeDiff(results, prevResults, rows, cols, metric) {
143
+ let diff = 0;
144
+ let count = 0;
145
+ for (let [_rowIndex, row] of rows.entries()) {
146
+ for (let [_colIndex, col] of cols.entries()) {
147
+ let res = results.get(`${row}:${col}`);
148
+ if (res && prevResults) {
149
+ let prevRes = prevResults.get(`${row}:${col}`);
150
+ if (prevRes) {
151
+ let denom = Number(prevRes[metric]);
152
+ let numerator = Number(res[metric]) - denom;
153
+ let percent = 0;
154
+ if (denom !== 0) {
155
+ percent = (numerator / denom) * 100;
156
+ }
157
+ if (!Number.isFinite(percent)) {
158
+ percent = 0;
159
+ }
160
+ diff += percent;
161
+ count++;
162
+ }
163
+ }
164
+ }
165
+ }
166
+ return count > 0 ? diff / count : 0;
167
+ }
168
+ function computeDiffAll(currentResults, prevResults, rows, cols) {
169
+ let metrics = ['instructions', 'rts_heap_size', 'rts_logical_stable_memory_size', 'rts_reclaimed'];
170
+ let diff = 0;
171
+ for (let metric of metrics) {
172
+ diff += computeDiff(currentResults, prevResults, rows, cols, metric);
173
+ }
174
+ return diff;
175
+ }
140
176
  function getMocArgs(options) {
141
177
  let args = '';
142
178
  if (options.forceGc) {
@@ -145,6 +181,12 @@ function getMocArgs(options) {
145
181
  if (options.gc) {
146
182
  args += ` --${options.gc}-gc`;
147
183
  }
184
+ if (options.profile === 'Debug') {
185
+ args += ' --debug';
186
+ }
187
+ else if (options.profile === 'Release') {
188
+ args += ' --release';
189
+ }
148
190
  return args;
149
191
  }
150
192
  async function deployBenchFile(file, options, replica) {
@@ -203,6 +245,18 @@ async function runBenchFile(file, options, replica) {
203
245
  let formatSize = (n) => {
204
246
  return filesize(n, { standard: 'iec', round: 2 });
205
247
  };
248
+ let colorizePercent = (percent, wrapInParens = false) => {
249
+ let sign = percent > 0 ? '+' : '';
250
+ let percentText = percent == 0 ? '0%' : sign + percent.toFixed(2) + '%';
251
+ let color = percent == 0 ? 'gray' : (percent > 0 ? 'red' : 'green');
252
+ let parens = wrapInParens ? ['(', ')'] : ['', ''];
253
+ if (process.env.CI) {
254
+ return `$${parens[0]}{\\color{${color}}${percentText.replace('%', '\\\\%')}}${parens[1]}$`;
255
+ }
256
+ else {
257
+ return `${parens[0]}${chalk[color](percentText)}${parens[1]}`;
258
+ }
259
+ };
206
260
  let getTable = (prop) => {
207
261
  let resArr = [['', ...schema.cols]];
208
262
  let allZero = true;
@@ -223,15 +277,7 @@ async function runBenchFile(file, options, replica) {
223
277
  if (Object.is(percent, NaN)) {
224
278
  percent = 0;
225
279
  }
226
- let sign = percent > 0 ? '+' : '';
227
- let percentText = percent == 0 ? '0%' : sign + percent.toFixed(2) + '%';
228
- let color = percent == 0 ? 'gray' : (percent > 0 ? 'red' : 'green');
229
- if (process.env.CI) {
230
- diff = ` $({\\color{${color}}${percentText.replace('%', '\\\\%')}})$`;
231
- }
232
- else {
233
- diff = ` (${chalk[color](percentText)})`;
234
- }
280
+ diff = ' ' + colorizePercent(percent, true);
235
281
  }
236
282
  else {
237
283
  diff = chalk.yellow(' (no previous results)');
@@ -267,14 +313,33 @@ async function runBenchFile(file, options, replica) {
267
313
  };
268
314
  let logUpdate = createLogUpdate(process.stdout, { showCursor: true });
269
315
  let getOutput = () => {
270
- return `
271
- \n${process.env.CI ? `## ${schema.name}` : chalk.bold(schema.name)}
272
- ${schema.description ? '\n' + (process.env.CI ? `_${schema.description}_` : chalk.gray(schema.description)) : ''}
273
- \n\n${chalk.blue('Instructions')}\n\n${getTable('instructions')}
274
- \n\n${chalk.blue('Heap')}\n\n${getTable('rts_heap_size')}
275
- \n\n${chalk.blue('Garbage Collection')}\n\n${getTable('rts_reclaimed')}
276
- ${getTable('rts_logical_stable_memory_size') ? `\n\n${chalk.blue('Stable Memory')}\n\n${getTable('rts_logical_stable_memory_size')}` : ''}
277
- `;
316
+ if (process.env.CI) {
317
+ return [
318
+ '\n<details>',
319
+ `\n<summary>${absToRel(file)} ${colorizePercent(computeDiffAll(results, prevResults, schema.rows, schema.cols), true).replace('\\\\%', '\\%')}</summary>`,
320
+ `\n${process.env.CI ? `### ${schema.name}` : chalk.bold(schema.name)}`,
321
+ `${schema.description ? '\n' + (process.env.CI ? `_${schema.description}_` : chalk.gray(schema.description)) : ''}`,
322
+ `\n\nInstructions: ${colorizePercent(computeDiff(results, prevResults, schema.rows, schema.cols, 'instructions'), false)}`,
323
+ `Heap: ${colorizePercent(computeDiff(results, prevResults, schema.rows, schema.cols, 'rts_heap_size'), false)}`,
324
+ `Stable Memory: ${colorizePercent(computeDiff(results, prevResults, schema.rows, schema.cols, 'rts_logical_stable_memory_size'), false)}`,
325
+ `Garbage Collection: ${colorizePercent(computeDiff(results, prevResults, schema.rows, schema.cols, 'rts_reclaimed'), false)}`,
326
+ `\n\n**Instructions**\n\n${getTable('instructions')}`,
327
+ `\n\n**Heap**\n\n${getTable('rts_heap_size')}`,
328
+ `\n\n**Garbage Collection**\n\n${getTable('rts_reclaimed')}`,
329
+ `${getTable('rts_logical_stable_memory_size') ? `\n\n**Stable Memory**\n\n${getTable('rts_logical_stable_memory_size')}` : ''}`,
330
+ '\n</details>',
331
+ ].join('\n');
332
+ }
333
+ else {
334
+ return `
335
+ \n${chalk.bold(schema.name)}
336
+ ${schema.description ? '\n' + chalk.gray(schema.description) : ''}
337
+ \n\n${chalk.blue('Instructions')}\n\n${getTable('instructions')}
338
+ \n\n${chalk.blue('Heap')}\n\n${getTable('rts_heap_size')}
339
+ \n\n${chalk.blue('Garbage Collection')}\n\n${getTable('rts_reclaimed')}
340
+ ${getTable('rts_logical_stable_memory_size') ? `\n\n${chalk.blue('Stable Memory')}\n\n${getTable('rts_logical_stable_memory_size')}` : ''}
341
+ `;
342
+ }
278
343
  };
279
344
  let canUpdateLog = !process.env.CI && !options.silent && terminalSize().rows > getOutput().split('\n').length;
280
345
  let log = () => {
@@ -1,2 +1,43 @@
1
+ type DfxConfig = {
2
+ $schema: string;
3
+ version: number;
4
+ profile: 'Debug' | 'Release';
5
+ canisters: {
6
+ [key: string]: {
7
+ type: 'motoko' | 'assets';
8
+ main?: string;
9
+ specified_id?: string;
10
+ declarations?: {
11
+ output: string;
12
+ node_compatibility: boolean;
13
+ };
14
+ build?: string[];
15
+ frontend?: {
16
+ entrypoint: string;
17
+ };
18
+ source?: string[];
19
+ remote?: {
20
+ id: {
21
+ ic: string;
22
+ staging: string;
23
+ };
24
+ };
25
+ };
26
+ };
27
+ defaults: {
28
+ build: {
29
+ packtool: string;
30
+ };
31
+ };
32
+ dfx: string;
33
+ networks: {
34
+ [key: string]: {
35
+ type: string;
36
+ providers: string[];
37
+ };
38
+ };
39
+ };
40
+ export declare function readDfxJson(): DfxConfig | Record<string, never>;
1
41
  export declare function getMotokoCanisters(): Record<string, string>;
2
42
  export declare function getMotokoCanistersWithDeclarations(): Record<string, string>;
43
+ export {};
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { getRootDir } from '../../mops.js';
4
- function readDfxJson() {
4
+ export function readDfxJson() {
5
5
  let dfxJsonPath = path.resolve(getRootDir(), 'dfx.json');
6
6
  if (!existsSync(dfxJsonPath)) {
7
7
  return {};
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "bin/mops.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "dist/bin/mops.js",