@ton/sandbox 0.30.0 → 0.31.0
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 +10 -1
- package/README.md +104 -0
- package/dist/blockchain/Blockchain.js +13 -12
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/jest/BenchmarkCommand.d.ts +10 -0
- package/dist/jest/BenchmarkCommand.js +14 -0
- package/dist/jest/BenchmarkEnvironment.d.ts +19 -0
- package/dist/jest/BenchmarkEnvironment.js +46 -0
- package/dist/jest/BenchmarkReporter.d.ts +45 -0
- package/dist/jest/BenchmarkReporter.js +207 -0
- package/dist/metric/ContractDatabase.d.ts +21 -0
- package/dist/metric/ContractDatabase.js +111 -0
- package/dist/metric/collectMetric.d.ts +106 -0
- package/dist/metric/collectMetric.js +217 -0
- package/dist/metric/defaultColor.d.ts +2 -0
- package/dist/metric/defaultColor.js +45 -0
- package/dist/metric/deltaResult.d.ts +39 -0
- package/dist/metric/deltaResult.js +210 -0
- package/dist/metric/gasReportTable.d.ts +2 -0
- package/dist/metric/gasReportTable.js +112 -0
- package/dist/metric/index.d.ts +6 -0
- package/dist/metric/index.js +22 -0
- package/dist/metric/readSnapshots.d.ts +2 -0
- package/dist/metric/readSnapshots.js +33 -0
- package/dist/utils/readJsonl.d.ts +1 -0
- package/dist/utils/readJsonl.js +25 -0
- package/jest-environment.js +1 -0
- package/jest-reporter.js +1 -0
- package/package.json +18 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.31.0] - 2025-05-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added methods to collect metrics of contracts
|
|
13
|
+
- Added `@ton/sandbox/jest-environment` and `@ton/sandbox/jest-reporter` to write metric snapshots from test run results
|
|
14
|
+
- Added contract method ABI auto-mapping mechanism for detailed benchmark metrics
|
|
15
|
+
- Added methods to generate delta reports from metrics of contracts
|
|
16
|
+
|
|
8
17
|
## [0.30.0] - 2025-05-12
|
|
9
18
|
|
|
10
19
|
### Changed
|
|
@@ -383,4 +392,4 @@ type LogsVerbosity = {
|
|
|
383
392
|
|
|
384
393
|
### Removed
|
|
385
394
|
|
|
386
|
-
- Changed `blockchain.pushMessage`, `blockchain.processQueue`, `blockchain.runQueue` to be private
|
|
395
|
+
- Changed `blockchain.pushMessage`, `blockchain.processQueue`, `blockchain.runQueue` to be private
|
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ The key difference of this package from [ton-contract-executor](https://github.c
|
|
|
15
15
|
* [Cross contract tests](#cross-contract-tests)
|
|
16
16
|
* [Testing key points](#testing-key-points)
|
|
17
17
|
* [Test examples](#test-examples)
|
|
18
|
+
* [Benchmark contracts](#benchmark-contracts)
|
|
18
19
|
* [Sandbox pitfalls](#sandbox-pitfalls)
|
|
19
20
|
* [Viewing logs](#viewing-logs)
|
|
20
21
|
* [Setting smart contract state directly](#setting-smart-contract-state-directly)
|
|
@@ -290,6 +291,109 @@ Learn more from examples:
|
|
|
290
291
|
* [FunC Test Examples](https://docs.ton.org/develop/smart-contracts/examples#examples-of-tests-for-smart-contracts)
|
|
291
292
|
* [Tact Test Examples](docs/tact-testing-examples.md)
|
|
292
293
|
|
|
294
|
+
## Benchmark contracts
|
|
295
|
+
|
|
296
|
+
The `@ton/sandbox` package provides `@ton/sandbox/jest-environment` and `@ton/sandbox/jest-reporter` built-in support for benchmarking smart contract behavior during tests, including tracking gas usage, cell size, opcode execution, and action phases.
|
|
297
|
+
This is especially useful for performance analysis, gas optimization, and regression checks on contract logic.
|
|
298
|
+
|
|
299
|
+
> ℹ️ See also: [Collect metric API](docs/collect-metric-api.md) for low-level control and manual snapshots.
|
|
300
|
+
|
|
301
|
+
### Features
|
|
302
|
+
|
|
303
|
+
* Automatic metric collection from all transactions triggered during tests
|
|
304
|
+
* Snapshot reporting with contract-level filters
|
|
305
|
+
* Integration with [blueprint](https://github.com/ton-org/blueprint#benchmark-contracts)
|
|
306
|
+
|
|
307
|
+
### Setup in `jest.config.ts`
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import type { Config } from 'jest';
|
|
311
|
+
|
|
312
|
+
const config: Config = {
|
|
313
|
+
preset: 'ts-jest',
|
|
314
|
+
testEnvironment: '@ton/sandbox/jest-environment',
|
|
315
|
+
globalSetup: './jest.setup.ts',
|
|
316
|
+
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
|
|
317
|
+
reporters: [
|
|
318
|
+
'default',
|
|
319
|
+
['@ton/sandbox/jest-reporter', {
|
|
320
|
+
// options
|
|
321
|
+
snapshotDir: '.snapshot', // output folder for benchmark reports, default: '.snapshot'
|
|
322
|
+
contractDatabase: 'contract.abi.json', // path or json a map of known contracts, see Collect metric API, default: 'contract.abi.json'
|
|
323
|
+
reportName: 'gas-report', // report name, default: 'gas-report'
|
|
324
|
+
depthCompare: 2, // comparison depth, default: 2
|
|
325
|
+
removeRawResult: true, // remove raw metric file, default: true
|
|
326
|
+
contractExcludes: [ // exclude specific contracts from snapshot, default: []
|
|
327
|
+
'TreasuryContract',
|
|
328
|
+
],
|
|
329
|
+
}],
|
|
330
|
+
],
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export default config;
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### How to run benchmarks
|
|
337
|
+
|
|
338
|
+
To collect and save snapshot metrics:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
BENCH_NEW="some" npx jest
|
|
342
|
+
# or
|
|
343
|
+
npx blueprint snapshot --label "some"
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
This will:
|
|
347
|
+
|
|
348
|
+
* Run your tests
|
|
349
|
+
* Collect contract execution metrics
|
|
350
|
+
* Save a snapshot in `.snapshot/<timestamp>.json`
|
|
351
|
+
|
|
352
|
+
To compare with a previous snapshot:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
BENCH_DIFF=true npx jest
|
|
356
|
+
# or
|
|
357
|
+
npx blueprint test --gas-report
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Or setup in `gas-report.config.ts`
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
import config from './jest.config';
|
|
364
|
+
|
|
365
|
+
config.testNamePattern = '^DescribeName .* - test name$'
|
|
366
|
+
config.testEnvironment = '@ton/sandbox/jest-environment'
|
|
367
|
+
config.reporters = [
|
|
368
|
+
['@ton/sandbox/jest-reporter', {
|
|
369
|
+
contractDatabase: 'abi.json',
|
|
370
|
+
contractExcludes: [
|
|
371
|
+
'TreasuryContract',
|
|
372
|
+
],
|
|
373
|
+
}],
|
|
374
|
+
]
|
|
375
|
+
export default config;
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**Collect metric and get report:**
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
npx blueprint snapshot --label "some label" -- --config gas-report.config.ts
|
|
382
|
+
npx blueprint test --gas-report -- --config gas-report.config.ts
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Output structure
|
|
386
|
+
|
|
387
|
+
By default, the reporter generates:
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
.project-root/
|
|
391
|
+
├── .snapshot/
|
|
392
|
+
│ └── 4200000000000.json // timestamped snapshot file
|
|
393
|
+
├── .sandbox-metric-raw.jsonl // raw metric log (auto-deleted by default)
|
|
394
|
+
├── contract.abi.json // map of known contracts, see Collect metric API
|
|
395
|
+
└── gas-report.json // aggregate report in json format
|
|
396
|
+
```
|
|
293
397
|
|
|
294
398
|
## Sandbox pitfalls
|
|
295
399
|
|
|
@@ -13,6 +13,7 @@ const AsyncLock_1 = require("../utils/AsyncLock");
|
|
|
13
13
|
const message_1 = require("../utils/message");
|
|
14
14
|
const slimConfig_1 = require("../config/slimConfig");
|
|
15
15
|
const testTreasurySubwalletId_1 = require("../utils/testTreasurySubwalletId");
|
|
16
|
+
const collectMetric_1 = require("../metric/collectMetric");
|
|
16
17
|
const CREATE_WALLETS_PREFIX = 'CREATE_WALLETS';
|
|
17
18
|
function createWalletsSeed(idx) {
|
|
18
19
|
return `${CREATE_WALLETS_PREFIX}${idx}`;
|
|
@@ -452,25 +453,25 @@ class Blockchain {
|
|
|
452
453
|
}
|
|
453
454
|
const value = target[prop];
|
|
454
455
|
if (typeof prop === 'string' && typeof value === 'function') {
|
|
456
|
+
const ctx = {
|
|
457
|
+
contract,
|
|
458
|
+
methodName: prop,
|
|
459
|
+
};
|
|
455
460
|
if (prop.startsWith('get')) {
|
|
456
461
|
return (...args) => value.apply(target, [provider, ...args]);
|
|
457
462
|
}
|
|
458
463
|
else if (prop.startsWith('send')) {
|
|
459
464
|
return async (...args) => {
|
|
460
|
-
|
|
465
|
+
let ret = value.apply(target, [provider, ...args]);
|
|
461
466
|
if (ret instanceof Promise) {
|
|
462
|
-
|
|
463
|
-
return {
|
|
464
|
-
...await blkch.runQueue(),
|
|
465
|
-
result: r,
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
else {
|
|
469
|
-
return {
|
|
470
|
-
...await blkch.runQueue(),
|
|
471
|
-
result: ret,
|
|
472
|
-
};
|
|
467
|
+
ret = await ret;
|
|
473
468
|
}
|
|
469
|
+
const out = {
|
|
470
|
+
...await blkch.runQueue(),
|
|
471
|
+
result: ret,
|
|
472
|
+
};
|
|
473
|
+
await (0, collectMetric_1.collectMetric)(blkch, ctx, out);
|
|
474
|
+
return out;
|
|
474
475
|
};
|
|
475
476
|
}
|
|
476
477
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,3 +11,4 @@ export { prettyLogTransaction, prettyLogTransactions, } from './utils/prettyLogT
|
|
|
11
11
|
export { printTransactionFees, } from './utils/printTransactionFees';
|
|
12
12
|
export { internal, } from './utils/message';
|
|
13
13
|
export { ExtraCurrency, } from './utils/ec';
|
|
14
|
+
export * from './metric';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
2
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
17
|
exports.internal = exports.printTransactionFees = exports.prettyLogTransactions = exports.prettyLogTransaction = exports.TreasuryContract = exports.Executor = exports.EmulationError = exports.TimeError = exports.GetMethodError = exports.createShardAccount = exports.createEmptyShardAccount = exports.SmartContract = exports.wrapTonClient4ForRemote = exports.RemoteBlockchainStorage = exports.LocalBlockchainStorage = exports.BlockchainSender = exports.BlockchainContractProvider = exports.toSandboxContract = exports.Blockchain = exports.defaultConfigSeqno = exports.defaultConfig = void 0;
|
|
4
18
|
var defaultConfig_1 = require("./config/defaultConfig");
|
|
@@ -33,3 +47,4 @@ var printTransactionFees_1 = require("./utils/printTransactionFees");
|
|
|
33
47
|
Object.defineProperty(exports, "printTransactionFees", { enumerable: true, get: function () { return printTransactionFees_1.printTransactionFees; } });
|
|
34
48
|
var message_1 = require("./utils/message");
|
|
35
49
|
Object.defineProperty(exports, "internal", { enumerable: true, get: function () { return message_1.internal; } });
|
|
50
|
+
__exportStar(require("./metric"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface BenchmarkCommandOption {
|
|
2
|
+
label: string;
|
|
3
|
+
doDiff: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare class BenchmarkCommand {
|
|
6
|
+
readonly label?: string;
|
|
7
|
+
readonly doDiff: boolean;
|
|
8
|
+
constructor(option?: Partial<BenchmarkCommandOption>);
|
|
9
|
+
get doBenchmark(): boolean;
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BenchmarkCommand = void 0;
|
|
4
|
+
class BenchmarkCommand {
|
|
5
|
+
constructor(option) {
|
|
6
|
+
option = option || {};
|
|
7
|
+
this.label = option?.label ?? process.env?.BENCH_NEW;
|
|
8
|
+
this.doDiff = option?.doDiff ?? process.env?.BENCH_DIFF === 'true' ?? false;
|
|
9
|
+
}
|
|
10
|
+
get doBenchmark() {
|
|
11
|
+
return this.doDiff || typeof this.label !== 'undefined';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.BenchmarkCommand = BenchmarkCommand;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { EnvironmentContext } from '@jest/environment';
|
|
2
|
+
import NodeEnvironment from 'jest-environment-node';
|
|
3
|
+
import { Config } from '@jest/types';
|
|
4
|
+
import { BenchmarkCommand, BenchmarkCommandOption } from './BenchmarkCommand';
|
|
5
|
+
export declare const sandboxMetricRawFile = ".sandbox-metric-raw.jsonl";
|
|
6
|
+
export type BenchmarkEnvironmentConfig = {
|
|
7
|
+
projectConfig: Config.ProjectConfig & {
|
|
8
|
+
testEnvironmentCommand?: Partial<BenchmarkCommandOption>;
|
|
9
|
+
};
|
|
10
|
+
globalConfig: Config.GlobalConfig;
|
|
11
|
+
};
|
|
12
|
+
export default class BenchmarkEnvironment extends NodeEnvironment {
|
|
13
|
+
protected command: BenchmarkCommand;
|
|
14
|
+
protected rootDir: string;
|
|
15
|
+
constructor(config: BenchmarkEnvironmentConfig, context: EnvironmentContext);
|
|
16
|
+
setup(): Promise<void>;
|
|
17
|
+
get resultFile(): string;
|
|
18
|
+
teardown(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
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.sandboxMetricRawFile = void 0;
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const jest_environment_node_1 = __importDefault(require("jest-environment-node"));
|
|
10
|
+
const BenchmarkCommand_1 = require("./BenchmarkCommand");
|
|
11
|
+
const metric_1 = require("../metric");
|
|
12
|
+
exports.sandboxMetricRawFile = '.sandbox-metric-raw.jsonl';
|
|
13
|
+
class BenchmarkEnvironment extends jest_environment_node_1.default {
|
|
14
|
+
constructor(config, context) {
|
|
15
|
+
super(config, context);
|
|
16
|
+
this.command = new BenchmarkCommand_1.BenchmarkCommand(config.projectConfig.testEnvironmentCommand);
|
|
17
|
+
this.rootDir = config.globalConfig.rootDir;
|
|
18
|
+
}
|
|
19
|
+
async setup() {
|
|
20
|
+
if (this.command.doBenchmark) {
|
|
21
|
+
(0, metric_1.createMetricStore)(this.global);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
get resultFile() {
|
|
25
|
+
return (0, path_1.join)(this.rootDir, exports.sandboxMetricRawFile);
|
|
26
|
+
}
|
|
27
|
+
async teardown() {
|
|
28
|
+
if (this.command.doBenchmark) {
|
|
29
|
+
const store = (0, metric_1.getMetricStore)(this.global) || [];
|
|
30
|
+
const fileName = this.resultFile;
|
|
31
|
+
const folder = (0, path_1.dirname)(fileName);
|
|
32
|
+
if (!(0, node_fs_1.existsSync)(folder)) {
|
|
33
|
+
(0, node_fs_1.mkdirSync)(folder, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
if (!(0, node_fs_1.existsSync)(fileName)) {
|
|
36
|
+
(0, node_fs_1.writeFileSync)(fileName, '');
|
|
37
|
+
}
|
|
38
|
+
for (const item of store) {
|
|
39
|
+
(0, node_fs_1.appendFileSync)(fileName, JSON.stringify(item) + '\n');
|
|
40
|
+
}
|
|
41
|
+
(0, metric_1.resetMetricStore)(this.global);
|
|
42
|
+
}
|
|
43
|
+
await super.teardown();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.default = BenchmarkEnvironment;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { BaseReporter } from '@jest/reporters';
|
|
2
|
+
import type { Config } from '@jest/types';
|
|
3
|
+
import { ContractABI } from '@ton/core';
|
|
4
|
+
import { BenchmarkCommand } from './BenchmarkCommand';
|
|
5
|
+
import { Metric, CodeHash, SnapshotMetric, ContractDatabase } from '../metric';
|
|
6
|
+
export declare const defaultSnapshotDir = ".snapshot";
|
|
7
|
+
export declare const defaultReportName = "gas-report";
|
|
8
|
+
export declare const defaultContractDatabaseName = "contract.abi.json";
|
|
9
|
+
export declare const defaultDepthCompare = 2;
|
|
10
|
+
export declare const minComparisonDepth = 1;
|
|
11
|
+
type ReportMode = 'gas' | 'average';
|
|
12
|
+
export interface Options {
|
|
13
|
+
reportMode?: ReportMode;
|
|
14
|
+
reportName?: string;
|
|
15
|
+
snapshotDir?: string;
|
|
16
|
+
depthCompare?: number;
|
|
17
|
+
contractExcludes?: string[];
|
|
18
|
+
removeRawResult?: boolean;
|
|
19
|
+
contractDatabase?: Record<CodeHash, ContractABI> | string;
|
|
20
|
+
}
|
|
21
|
+
export default class BenchmarkReporter extends BaseReporter {
|
|
22
|
+
protected rootDirPath: string;
|
|
23
|
+
protected options: Options;
|
|
24
|
+
protected command: BenchmarkCommand;
|
|
25
|
+
contractDatabase: ContractDatabase;
|
|
26
|
+
constructor(globalConfig: Config.GlobalConfig, options?: Options);
|
|
27
|
+
get reportMode(): ReportMode;
|
|
28
|
+
get reportName(): string;
|
|
29
|
+
get snapshotDir(): string;
|
|
30
|
+
get snapshotDirPath(): string;
|
|
31
|
+
get depthCompare(): number;
|
|
32
|
+
get snapshotFiles(): Promise<import("../metric").SnapshotMetricList>;
|
|
33
|
+
get snapshots(): Promise<SnapshotMetric[]>;
|
|
34
|
+
get snapshotCurrent(): Promise<SnapshotMetric>;
|
|
35
|
+
get removeRawResult(): true;
|
|
36
|
+
get contractExcludes(): string[];
|
|
37
|
+
get sandboxMetricRawFile(): string;
|
|
38
|
+
get metricStore(): Promise<Metric[]>;
|
|
39
|
+
readContractDatabase(): ContractDatabase;
|
|
40
|
+
saveContractDatabase(): void;
|
|
41
|
+
onRunComplete(): Promise<void>;
|
|
42
|
+
gasReportReport(data: SnapshotMetric[], benchmarkDepth: number): string[];
|
|
43
|
+
saveSnapshot(label: string): Promise<string>;
|
|
44
|
+
}
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.minComparisonDepth = exports.defaultDepthCompare = exports.defaultContractDatabaseName = exports.defaultReportName = exports.defaultSnapshotDir = void 0;
|
|
27
|
+
const path_1 = require("path");
|
|
28
|
+
const fs_1 = require("fs");
|
|
29
|
+
const chalk_1 = __importStar(require("chalk"));
|
|
30
|
+
const reporters_1 = require("@jest/reporters");
|
|
31
|
+
const BenchmarkCommand_1 = require("./BenchmarkCommand");
|
|
32
|
+
const BenchmarkEnvironment_1 = require("./BenchmarkEnvironment");
|
|
33
|
+
const readJsonl_1 = require("../utils/readJsonl");
|
|
34
|
+
const metric_1 = require("../metric");
|
|
35
|
+
exports.defaultSnapshotDir = '.snapshot';
|
|
36
|
+
exports.defaultReportName = 'gas-report';
|
|
37
|
+
exports.defaultContractDatabaseName = 'contract.abi.json';
|
|
38
|
+
exports.defaultDepthCompare = 2;
|
|
39
|
+
exports.minComparisonDepth = 1;
|
|
40
|
+
const PASS_TEXT = 'PASS';
|
|
41
|
+
const PASS = chalk_1.supportsColor ? chalk_1.default.reset.inverse.bold.green(` ${PASS_TEXT} `) : PASS_TEXT;
|
|
42
|
+
const SKIP_TEXT = 'SKIP';
|
|
43
|
+
const SKIP = chalk_1.supportsColor ? chalk_1.default.reset.inverse.bold.yellow(` ${SKIP_TEXT} `) : SKIP_TEXT;
|
|
44
|
+
class BenchmarkReporter extends reporters_1.BaseReporter {
|
|
45
|
+
constructor(globalConfig, options = {}) {
|
|
46
|
+
super();
|
|
47
|
+
this.rootDirPath = globalConfig.rootDir;
|
|
48
|
+
this.options = options;
|
|
49
|
+
this.command = new BenchmarkCommand_1.BenchmarkCommand();
|
|
50
|
+
if (this.depthCompare < exports.minComparisonDepth) {
|
|
51
|
+
throw new Error(`The minimum depth compare must be greater than or equal to ${exports.minComparisonDepth}`);
|
|
52
|
+
}
|
|
53
|
+
this.contractDatabase = this.readContractDatabase();
|
|
54
|
+
}
|
|
55
|
+
get reportMode() {
|
|
56
|
+
return this.options.reportMode || 'gas';
|
|
57
|
+
}
|
|
58
|
+
get reportName() {
|
|
59
|
+
return this.options.reportName || exports.defaultReportName;
|
|
60
|
+
}
|
|
61
|
+
get snapshotDir() {
|
|
62
|
+
return this.options.snapshotDir || exports.defaultSnapshotDir;
|
|
63
|
+
}
|
|
64
|
+
get snapshotDirPath() {
|
|
65
|
+
const dirPath = (0, path_1.join)(this.rootDirPath, this.snapshotDir);
|
|
66
|
+
try {
|
|
67
|
+
if (!(0, fs_1.existsSync)(dirPath)) {
|
|
68
|
+
(0, fs_1.mkdirSync)(dirPath, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (_) {
|
|
72
|
+
throw new Error(`Can not create directory: ${dirPath}`);
|
|
73
|
+
}
|
|
74
|
+
return dirPath;
|
|
75
|
+
}
|
|
76
|
+
get depthCompare() {
|
|
77
|
+
return this.options.depthCompare || exports.defaultDepthCompare;
|
|
78
|
+
}
|
|
79
|
+
get snapshotFiles() {
|
|
80
|
+
return (0, metric_1.readSnapshots)(this.snapshotDirPath);
|
|
81
|
+
}
|
|
82
|
+
get snapshots() {
|
|
83
|
+
return this.snapshotFiles.then((list) => Object.values(list).map((item) => item.content));
|
|
84
|
+
}
|
|
85
|
+
get snapshotCurrent() {
|
|
86
|
+
return this.metricStore.then((store) => (0, metric_1.makeSnapshotMetric)(store, {
|
|
87
|
+
contractDatabase: this.contractDatabase,
|
|
88
|
+
contractExcludes: this.contractExcludes,
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
get removeRawResult() {
|
|
92
|
+
return this.options.removeRawResult || true;
|
|
93
|
+
}
|
|
94
|
+
get contractExcludes() {
|
|
95
|
+
return this.options.contractExcludes || [];
|
|
96
|
+
}
|
|
97
|
+
get sandboxMetricRawFile() {
|
|
98
|
+
return (0, path_1.join)(this.rootDirPath, BenchmarkEnvironment_1.sandboxMetricRawFile);
|
|
99
|
+
}
|
|
100
|
+
get metricStore() {
|
|
101
|
+
if ((0, fs_1.existsSync)(this.sandboxMetricRawFile)) {
|
|
102
|
+
return (0, readJsonl_1.readJsonl)(this.sandboxMetricRawFile);
|
|
103
|
+
}
|
|
104
|
+
return new Promise((resolve) => resolve([]));
|
|
105
|
+
}
|
|
106
|
+
readContractDatabase() {
|
|
107
|
+
let data = {};
|
|
108
|
+
const filePath = this.options.contractDatabase || exports.defaultContractDatabaseName;
|
|
109
|
+
if (typeof filePath === 'string') {
|
|
110
|
+
try {
|
|
111
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(this.rootDirPath, filePath))) {
|
|
112
|
+
data = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(this.rootDirPath, filePath), 'utf-8'));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
throw new Error(`Could not parse contract database: ${filePath}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return metric_1.ContractDatabase.from(data);
|
|
120
|
+
}
|
|
121
|
+
saveContractDatabase() {
|
|
122
|
+
const contractDatabase = this.options.contractDatabase;
|
|
123
|
+
let filePath = typeof contractDatabase === 'string' ? contractDatabase : exports.defaultContractDatabaseName;
|
|
124
|
+
try {
|
|
125
|
+
const content = JSON.stringify(this.contractDatabase.data, null, 2) + '\n';
|
|
126
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(this.rootDirPath, filePath), content, {
|
|
127
|
+
encoding: 'utf8',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (_) {
|
|
131
|
+
throw new Error(`Can not write: ${filePath}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async onRunComplete() {
|
|
135
|
+
const log = [];
|
|
136
|
+
let status = SKIP;
|
|
137
|
+
if (this.command.doBenchmark) {
|
|
138
|
+
const list = await this.snapshots;
|
|
139
|
+
const snapshots = [await this.snapshotCurrent, ...list];
|
|
140
|
+
let doDiff = this.command.doDiff;
|
|
141
|
+
const depthCompare = Math.min(snapshots.length, this.depthCompare);
|
|
142
|
+
if (doDiff) {
|
|
143
|
+
log.push(`Comparison metric mode: ${this.reportMode} depth: ${depthCompare}`);
|
|
144
|
+
switch (this.reportMode) {
|
|
145
|
+
case 'gas':
|
|
146
|
+
log.push(...this.gasReportReport(snapshots, depthCompare));
|
|
147
|
+
status = PASS;
|
|
148
|
+
break;
|
|
149
|
+
default:
|
|
150
|
+
throw new Error(`Report mode ${this.reportMode} not supported`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else if (this.command.label) {
|
|
154
|
+
log.push(`Collect metric mode: "${this.reportMode}"`);
|
|
155
|
+
const file = await this.saveSnapshot(this.command.label);
|
|
156
|
+
log.push(`Report write in '${file}'`);
|
|
157
|
+
status = PASS;
|
|
158
|
+
}
|
|
159
|
+
if (this.removeRawResult) {
|
|
160
|
+
(0, fs_1.unlinkSync)(this.sandboxMetricRawFile);
|
|
161
|
+
}
|
|
162
|
+
this.saveContractDatabase();
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
log.push(`Reporter mode: ${this.reportMode}`);
|
|
166
|
+
}
|
|
167
|
+
this.log(`${status} ${log.join('\n')}`);
|
|
168
|
+
}
|
|
169
|
+
gasReportReport(data, benchmarkDepth) {
|
|
170
|
+
const log = [];
|
|
171
|
+
const reportFile = `${this.reportName}.json`;
|
|
172
|
+
const report = (0, metric_1.makeGasReport)(data);
|
|
173
|
+
try {
|
|
174
|
+
const reportFilePath = (0, path_1.join)(this.rootDirPath, reportFile);
|
|
175
|
+
(0, fs_1.writeFileSync)(reportFilePath, JSON.stringify(report, null, 2) + '\n', {
|
|
176
|
+
encoding: 'utf8',
|
|
177
|
+
});
|
|
178
|
+
log.push(`Gas report write in '${reportFile}'`);
|
|
179
|
+
}
|
|
180
|
+
catch (_) {
|
|
181
|
+
throw new Error(`Can not write: ${reportFile}`);
|
|
182
|
+
}
|
|
183
|
+
const list = report.sort((0, metric_1.sortByCreatedAt)(true)).slice(0, benchmarkDepth);
|
|
184
|
+
log.push((0, metric_1.gasReportTable)(list, metric_1.defaultColor));
|
|
185
|
+
return log;
|
|
186
|
+
}
|
|
187
|
+
async saveSnapshot(label) {
|
|
188
|
+
const snapshot = await this.snapshotCurrent;
|
|
189
|
+
snapshot.label = label;
|
|
190
|
+
const list = await this.snapshotFiles;
|
|
191
|
+
let snapshotFile = `${snapshot.createdAt.getTime()}.json`;
|
|
192
|
+
if (list[snapshot.label]) {
|
|
193
|
+
snapshotFile = list[snapshot.label].name;
|
|
194
|
+
}
|
|
195
|
+
const snapshotFilePath = (0, path_1.join)(this.snapshotDirPath, snapshotFile);
|
|
196
|
+
try {
|
|
197
|
+
(0, fs_1.writeFileSync)(snapshotFilePath, JSON.stringify(snapshot, null, 2) + '\n', {
|
|
198
|
+
encoding: 'utf8',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
catch (_) {
|
|
202
|
+
throw new Error(`Can not write: ${(0, path_1.join)(this.snapshotDir, snapshotFile)}`);
|
|
203
|
+
}
|
|
204
|
+
return (0, path_1.join)(this.snapshotDir, snapshotFile);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
exports.default = BenchmarkReporter;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ContractABI } from '@ton/core';
|
|
2
|
+
import { CodeHash, Metric, OpCode } from './collectMetric';
|
|
3
|
+
type Condition = {
|
|
4
|
+
codeHash: CodeHash;
|
|
5
|
+
opCode: OpCode;
|
|
6
|
+
receiver: 'internal' | 'external-in' | 'external-out';
|
|
7
|
+
};
|
|
8
|
+
export type ContractDataKey = CodeHash | string;
|
|
9
|
+
export type ContractData = Record<ContractDataKey, ContractABI | ContractDataKey>;
|
|
10
|
+
export declare class ContractDatabase {
|
|
11
|
+
protected list: Map<ContractDataKey, ContractABI>;
|
|
12
|
+
protected match: Map<ContractDataKey, ContractDataKey>;
|
|
13
|
+
constructor(abiList: Map<ContractDataKey, ContractABI>, codeHashMatch: Map<ContractDataKey, ContractDataKey>);
|
|
14
|
+
static from(data: ContractData): ContractDatabase;
|
|
15
|
+
get data(): ContractData;
|
|
16
|
+
origin(needle: ContractDataKey): string;
|
|
17
|
+
get(needle: ContractDataKey): ContractABI | undefined;
|
|
18
|
+
extract(metric: Metric): void;
|
|
19
|
+
by(where: Partial<Condition>): Partial<Metric>;
|
|
20
|
+
}
|
|
21
|
+
export {};
|