@ton/sandbox 0.29.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 +16 -1
- package/README.md +104 -0
- package/dist/blockchain/Blockchain.js +13 -12
- package/dist/config/defaultConfig.d.ts +2 -2
- package/dist/config/defaultConfig.js +2 -2
- package/dist/config/slimConfig.d.ts +2 -2
- package/dist/config/slimConfig.js +2 -2
- 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
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectMetric = exports.OpCodeReserved = exports.calcComputePhase = exports.calcStateSize = exports.calcCellSize = exports.calcDictSize = exports.calcMessageSize = exports.resetMetricStore = exports.createMetricStore = exports.getMetricStore = exports.makeSnapshotMetric = exports.sortByCreatedAt = exports.isCodeHash = exports.isAddressFriendly = void 0;
|
|
4
|
+
const core_1 = require("@ton/core");
|
|
5
|
+
const ContractDatabase_1 = require("./ContractDatabase");
|
|
6
|
+
function isAddressFriendly(value) {
|
|
7
|
+
return typeof value === 'string' && core_1.Address.isFriendly(value);
|
|
8
|
+
}
|
|
9
|
+
exports.isAddressFriendly = isAddressFriendly;
|
|
10
|
+
function isCodeHash(value) {
|
|
11
|
+
return typeof value === 'string' && value.length === 66 && /^0x[0-9a-fA-F]+$/.test(value);
|
|
12
|
+
}
|
|
13
|
+
exports.isCodeHash = isCodeHash;
|
|
14
|
+
function sortByCreatedAt(reverse = false) {
|
|
15
|
+
return (a, b) => (a.createdAt.getTime() - b.createdAt.getTime()) * (reverse ? -1 : 1);
|
|
16
|
+
}
|
|
17
|
+
exports.sortByCreatedAt = sortByCreatedAt;
|
|
18
|
+
const STORE_METRIC = Symbol.for('ton-sandbox-metric-store');
|
|
19
|
+
function makeSnapshotMetric(store, config = {}) {
|
|
20
|
+
const label = config.label || 'current';
|
|
21
|
+
const contractExcludes = config.contractExcludes || new Array();
|
|
22
|
+
const contractDatabase = config.contractDatabase || ContractDatabase_1.ContractDatabase.from({});
|
|
23
|
+
const snapshot = {
|
|
24
|
+
label,
|
|
25
|
+
createdAt: new Date(),
|
|
26
|
+
items: new Array(),
|
|
27
|
+
};
|
|
28
|
+
// remove duplicates and extract ABI
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
for (const metric of store) {
|
|
31
|
+
const key = JSON.stringify(metric);
|
|
32
|
+
if (seen.has(key))
|
|
33
|
+
continue;
|
|
34
|
+
snapshot.items.push(metric);
|
|
35
|
+
seen.add(key);
|
|
36
|
+
if (metric.codeHash) {
|
|
37
|
+
contractDatabase.extract(metric);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// ABI auto-mapping
|
|
41
|
+
for (const item of snapshot.items) {
|
|
42
|
+
const find = contractDatabase.by(item);
|
|
43
|
+
if (!item.contractName && find.contractName) {
|
|
44
|
+
item.contractName = find.contractName;
|
|
45
|
+
}
|
|
46
|
+
if (!item.methodName && find.methodName) {
|
|
47
|
+
item.methodName = find.methodName;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (contractExcludes.length > 0) {
|
|
51
|
+
snapshot.items = snapshot.items.filter((it) => typeof it.contractName === 'undefined' || !contractExcludes.includes(it.contractName));
|
|
52
|
+
}
|
|
53
|
+
return snapshot;
|
|
54
|
+
}
|
|
55
|
+
exports.makeSnapshotMetric = makeSnapshotMetric;
|
|
56
|
+
function getMetricStore(context = globalThis) {
|
|
57
|
+
return context[STORE_METRIC];
|
|
58
|
+
}
|
|
59
|
+
exports.getMetricStore = getMetricStore;
|
|
60
|
+
function createMetricStore(context = globalThis) {
|
|
61
|
+
if (!Array.isArray(context[STORE_METRIC])) {
|
|
62
|
+
context[STORE_METRIC] = new Array();
|
|
63
|
+
}
|
|
64
|
+
return context[STORE_METRIC];
|
|
65
|
+
}
|
|
66
|
+
exports.createMetricStore = createMetricStore;
|
|
67
|
+
function resetMetricStore(context = globalThis) {
|
|
68
|
+
const store = getMetricStore(context);
|
|
69
|
+
if (store)
|
|
70
|
+
store.length = 0;
|
|
71
|
+
return createMetricStore(context);
|
|
72
|
+
}
|
|
73
|
+
exports.resetMetricStore = resetMetricStore;
|
|
74
|
+
function calcMessageSize(msg) {
|
|
75
|
+
if (msg) {
|
|
76
|
+
return calcCellSize((0, core_1.beginCell)().store((0, core_1.storeMessage)(msg)).endCell());
|
|
77
|
+
}
|
|
78
|
+
return { cells: 0, bits: 0 };
|
|
79
|
+
}
|
|
80
|
+
exports.calcMessageSize = calcMessageSize;
|
|
81
|
+
function calcDictSize(dict) {
|
|
82
|
+
if (dict.size > 0) {
|
|
83
|
+
return calcCellSize((0, core_1.beginCell)().storeDict(dict).endCell().asSlice().loadRef());
|
|
84
|
+
}
|
|
85
|
+
return { cells: 0, bits: 0 };
|
|
86
|
+
}
|
|
87
|
+
exports.calcDictSize = calcDictSize;
|
|
88
|
+
function calcCellSize(root, visited = new Set()) {
|
|
89
|
+
const hash = root.hash().toString('hex');
|
|
90
|
+
if (visited.has(hash)) {
|
|
91
|
+
return { cells: 0, bits: 0 };
|
|
92
|
+
}
|
|
93
|
+
visited.add(hash);
|
|
94
|
+
const out = {
|
|
95
|
+
cells: 1,
|
|
96
|
+
bits: root.bits.length,
|
|
97
|
+
};
|
|
98
|
+
for (const ref of root.refs) {
|
|
99
|
+
const childRes = calcCellSize(ref, visited);
|
|
100
|
+
out.cells += childRes.cells;
|
|
101
|
+
out.bits += childRes.bits;
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
exports.calcCellSize = calcCellSize;
|
|
106
|
+
function calcStateSize(state) {
|
|
107
|
+
const codeSize = calcCellSize(state.code);
|
|
108
|
+
const dataSize = calcCellSize(state.data);
|
|
109
|
+
return {
|
|
110
|
+
code: codeSize,
|
|
111
|
+
data: dataSize,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
exports.calcStateSize = calcStateSize;
|
|
115
|
+
function calcComputePhase(phase) {
|
|
116
|
+
if (phase.type === 'vm') {
|
|
117
|
+
return {
|
|
118
|
+
type: phase.type,
|
|
119
|
+
success: phase.success,
|
|
120
|
+
gasUsed: Number(phase.gasUsed),
|
|
121
|
+
exitCode: phase.exitCode,
|
|
122
|
+
vmSteps: phase.vmSteps,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
type: phase.type,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
exports.calcComputePhase = calcComputePhase;
|
|
130
|
+
var OpCodeReserved;
|
|
131
|
+
(function (OpCodeReserved) {
|
|
132
|
+
OpCodeReserved[OpCodeReserved["send"] = 0] = "send";
|
|
133
|
+
OpCodeReserved[OpCodeReserved["notSupported"] = 4294967295] = "notSupported";
|
|
134
|
+
OpCodeReserved[OpCodeReserved["notAllowed"] = 4294967294] = "notAllowed";
|
|
135
|
+
})(OpCodeReserved = exports.OpCodeReserved || (exports.OpCodeReserved = {}));
|
|
136
|
+
async function collectMetric(blockchain, ctx, result) {
|
|
137
|
+
const store = getMetricStore();
|
|
138
|
+
if (!Array.isArray(store)) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
let state = { data: { cells: 0, bits: 0 }, code: { cells: 0, bits: 0 } };
|
|
142
|
+
let codeHash;
|
|
143
|
+
if (ctx.contract.init && ctx.contract.init.code && ctx.contract.init.data) {
|
|
144
|
+
codeHash = `0x${ctx.contract.init.code.hash().toString('hex')}`;
|
|
145
|
+
state = calcStateSize({ code: ctx.contract.init.code, data: ctx.contract.init.data });
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const account = (await blockchain.getContract(ctx.contract.address)).accountState;
|
|
149
|
+
if (account && account.type === 'active' && account.state.code && account.state.data) {
|
|
150
|
+
codeHash = `0x${account.state.code.hash().toString('hex')}`;
|
|
151
|
+
state = calcStateSize({ code: account.state.code, data: account.state.data });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
let testName;
|
|
155
|
+
if (globalThis['expect']) {
|
|
156
|
+
testName = expect.getState().currentTestName;
|
|
157
|
+
}
|
|
158
|
+
let contractName = ctx.contract.constructor.name;
|
|
159
|
+
let methodName = ctx.methodName;
|
|
160
|
+
for (const tx of result.transactions) {
|
|
161
|
+
if (tx.description.type !== 'generic')
|
|
162
|
+
continue;
|
|
163
|
+
const receiver = tx.inMessage?.info.type;
|
|
164
|
+
const body = tx.inMessage?.body ? tx.inMessage.body.beginParse() : undefined;
|
|
165
|
+
let opCode = '0x0';
|
|
166
|
+
if (receiver === 'internal') {
|
|
167
|
+
opCode = `0x${(body && body.remainingBits >= 32 ? body.preloadUint(32) : 0).toString(16)}`;
|
|
168
|
+
}
|
|
169
|
+
if (!methodName && Object.values(OpCodeReserved).includes(Number(opCode))) {
|
|
170
|
+
methodName = OpCodeReserved[Number(opCode)];
|
|
171
|
+
}
|
|
172
|
+
const address = core_1.Address.parseRaw(`0:${tx.address.toString(16).padStart(64, '0')}`);
|
|
173
|
+
const { computePhase, actionPhase, bouncePhase, storagePhase } = tx.description;
|
|
174
|
+
const action = actionPhase
|
|
175
|
+
? {
|
|
176
|
+
success: actionPhase.success,
|
|
177
|
+
totalActions: actionPhase.totalActions,
|
|
178
|
+
skippedActions: actionPhase.skippedActions,
|
|
179
|
+
resultCode: actionPhase.resultCode,
|
|
180
|
+
totalActionFees: actionPhase.totalActions,
|
|
181
|
+
totalFwdFees: actionPhase.totalFwdFees ? Number(actionPhase.totalFwdFees) : undefined,
|
|
182
|
+
totalMessageSize: {
|
|
183
|
+
cells: Number(actionPhase.totalMessageSize.cells),
|
|
184
|
+
bits: Number(actionPhase.totalMessageSize.bits),
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
: undefined;
|
|
188
|
+
const compute = calcComputePhase(computePhase);
|
|
189
|
+
const metric = {
|
|
190
|
+
testName,
|
|
191
|
+
address: address.toString(),
|
|
192
|
+
codeHash,
|
|
193
|
+
contractName,
|
|
194
|
+
methodName,
|
|
195
|
+
receiver,
|
|
196
|
+
opCode,
|
|
197
|
+
execute: {
|
|
198
|
+
compute,
|
|
199
|
+
action,
|
|
200
|
+
},
|
|
201
|
+
message: {
|
|
202
|
+
in: calcMessageSize(tx.inMessage),
|
|
203
|
+
out: calcDictSize(tx.outMessages),
|
|
204
|
+
},
|
|
205
|
+
state,
|
|
206
|
+
};
|
|
207
|
+
store.push(metric);
|
|
208
|
+
methodName = undefined;
|
|
209
|
+
if (!address.equals(ctx.contract.address)) {
|
|
210
|
+
contractName = ctx.contract.constructor.name;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
contractName = undefined;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
exports.collectMetric = collectMetric;
|
|
@@ -0,0 +1,45 @@
|
|
|
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.defaultColor = void 0;
|
|
27
|
+
const chalk_1 = __importStar(require("chalk"));
|
|
28
|
+
function defaultColor(metric) {
|
|
29
|
+
if (!chalk_1.supportsColor) {
|
|
30
|
+
return metric.value;
|
|
31
|
+
}
|
|
32
|
+
const { green, redBright, gray, yellowBright } = chalk_1.default;
|
|
33
|
+
let color = gray;
|
|
34
|
+
if (metric.kind === 'decrease') {
|
|
35
|
+
color = green;
|
|
36
|
+
}
|
|
37
|
+
else if (metric.kind === 'increase') {
|
|
38
|
+
color = redBright;
|
|
39
|
+
}
|
|
40
|
+
else if (metric.kind === 'undefined') {
|
|
41
|
+
color = yellowBright;
|
|
42
|
+
}
|
|
43
|
+
return color(metric.value);
|
|
44
|
+
}
|
|
45
|
+
exports.defaultColor = defaultColor;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AddressFriendly, ContractMethodName, ContractName, Metric, OpCode, SnapshotMetric } from './collectMetric';
|
|
2
|
+
export type KindDelta = 'undefined' | 'init' | 'same' | 'increase' | 'decrease';
|
|
3
|
+
export type PathDelta = string;
|
|
4
|
+
export type ItemDelta = {
|
|
5
|
+
kind: KindDelta;
|
|
6
|
+
path: PathDelta;
|
|
7
|
+
before: number;
|
|
8
|
+
after: number;
|
|
9
|
+
};
|
|
10
|
+
export type ListDelta = Record<PathDelta, ItemDelta>;
|
|
11
|
+
export type DeltaMetric = {
|
|
12
|
+
kind: KindDelta;
|
|
13
|
+
value: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const undefinedDeltaMetric: () => DeltaMetric;
|
|
16
|
+
export type ColorDelta = (metric: DeltaMetric) => string;
|
|
17
|
+
export type DeltaMetrics = Record<string, DeltaMetric>;
|
|
18
|
+
export type MethodDelta = Record<ContractMethodName | OpCode, DeltaMetrics>;
|
|
19
|
+
export type ContractDelta = Record<ContractName | AddressFriendly, MethodDelta>;
|
|
20
|
+
export type DeltaResult = {
|
|
21
|
+
label: string;
|
|
22
|
+
createdAt: Date;
|
|
23
|
+
result: ContractDelta;
|
|
24
|
+
};
|
|
25
|
+
export type DeltaRow = [contract: string, method: string, ...values: DeltaMetric[]];
|
|
26
|
+
export type FlatDeltaResult = {
|
|
27
|
+
header: string[];
|
|
28
|
+
rows: DeltaRow[];
|
|
29
|
+
};
|
|
30
|
+
export declare function toFlatDeltaResult(deltas: DeltaResult[]): {
|
|
31
|
+
header: string[];
|
|
32
|
+
rows: DeltaRow[];
|
|
33
|
+
};
|
|
34
|
+
export declare function aggregatedCompareMetric(before: Metric, after: Metric, basePath?: string[]): ListDelta;
|
|
35
|
+
export declare function prepareDelta(pair: {
|
|
36
|
+
after: SnapshotMetric;
|
|
37
|
+
before?: SnapshotMetric;
|
|
38
|
+
}): DeltaResult;
|
|
39
|
+
export declare function makeGasReport(list: SnapshotMetric[]): Array<DeltaResult>;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeGasReport = exports.prepareDelta = exports.aggregatedCompareMetric = exports.toFlatDeltaResult = exports.undefinedDeltaMetric = void 0;
|
|
4
|
+
const collectMetric_1 = require("./collectMetric");
|
|
5
|
+
const undefinedDeltaMetric = () => ({
|
|
6
|
+
kind: 'undefined',
|
|
7
|
+
value: '~',
|
|
8
|
+
});
|
|
9
|
+
exports.undefinedDeltaMetric = undefinedDeltaMetric;
|
|
10
|
+
function toFlatDeltaResult(deltas) {
|
|
11
|
+
const out = {
|
|
12
|
+
header: ['Contract', 'Method'],
|
|
13
|
+
rows: [],
|
|
14
|
+
};
|
|
15
|
+
const contractSet = new Set();
|
|
16
|
+
const methodMap = new Map();
|
|
17
|
+
const metricSet = new Set();
|
|
18
|
+
for (const delta of deltas) {
|
|
19
|
+
for (const [contract, methods] of Object.entries(delta.result)) {
|
|
20
|
+
contractSet.add(contract);
|
|
21
|
+
if (!methodMap.has(contract)) {
|
|
22
|
+
methodMap.set(contract, new Set());
|
|
23
|
+
}
|
|
24
|
+
for (const [method, metrics] of Object.entries(methods)) {
|
|
25
|
+
methodMap.get(contract).add(method);
|
|
26
|
+
for (const metric of Object.keys(metrics)) {
|
|
27
|
+
metricSet.add(metric);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const metricNames = Array.from(metricSet.values());
|
|
33
|
+
out.header.push(...Array.from({ length: deltas.length }, () => metricNames).flat());
|
|
34
|
+
for (const contract of contractSet) {
|
|
35
|
+
const methods = methodMap.get(contract);
|
|
36
|
+
for (const method of methods) {
|
|
37
|
+
const metrics = [];
|
|
38
|
+
for (const delta of deltas) {
|
|
39
|
+
for (const metric of metricNames) {
|
|
40
|
+
metrics.push(delta.result?.[contract]?.[method]?.[metric] ?? (0, exports.undefinedDeltaMetric)());
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
out.rows.push([contract, method, ...metrics]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
exports.toFlatDeltaResult = toFlatDeltaResult;
|
|
49
|
+
const aggregateTarget = ['cells', 'bits', 'gasUsed'];
|
|
50
|
+
const statusTarget = ['success', 'exitCode', 'resultCode'];
|
|
51
|
+
const deltaTarget = [
|
|
52
|
+
...aggregateTarget,
|
|
53
|
+
...statusTarget,
|
|
54
|
+
'vmSteps',
|
|
55
|
+
'totalActions',
|
|
56
|
+
'skippedActions',
|
|
57
|
+
'totalActionFees',
|
|
58
|
+
'data',
|
|
59
|
+
'code',
|
|
60
|
+
'state',
|
|
61
|
+
'message',
|
|
62
|
+
'in',
|
|
63
|
+
'out',
|
|
64
|
+
'execute',
|
|
65
|
+
'compute',
|
|
66
|
+
'action',
|
|
67
|
+
];
|
|
68
|
+
/**
|
|
69
|
+
* Recursively collects the sum of all numeric fields named `needle` within an arbitrary data structure
|
|
70
|
+
*/
|
|
71
|
+
function sumDeep(source, needle) {
|
|
72
|
+
if (source === null || typeof source !== 'object') {
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
if (Array.isArray(source)) {
|
|
76
|
+
return source.map((item) => sumDeep(item, needle)).reduce((total, current) => total + current, 0);
|
|
77
|
+
}
|
|
78
|
+
let total = 0;
|
|
79
|
+
for (const [key, value] of Object.entries(source)) {
|
|
80
|
+
if (key === needle && typeof value === 'number') {
|
|
81
|
+
total += value;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
total += sumDeep(value, needle);
|
|
85
|
+
}
|
|
86
|
+
return total;
|
|
87
|
+
}
|
|
88
|
+
function aggregatedCompareMetric(before, after, basePath = []) {
|
|
89
|
+
const out = {};
|
|
90
|
+
const keys = new Set([...Object.keys(before ?? {}), ...Object.keys(after ?? {})]);
|
|
91
|
+
keys.forEach((key) => {
|
|
92
|
+
if (!deltaTarget.includes(key))
|
|
93
|
+
return;
|
|
94
|
+
const prev = before[key];
|
|
95
|
+
const next = after[key];
|
|
96
|
+
const path = [...basePath, key];
|
|
97
|
+
if (prev === undefined || next === undefined)
|
|
98
|
+
return;
|
|
99
|
+
if (typeof prev === 'object' && typeof next === 'object') {
|
|
100
|
+
Object.assign(out, aggregatedCompareMetric(prev, next, path));
|
|
101
|
+
aggregateTarget.forEach((aggKey) => {
|
|
102
|
+
const beforeSum = sumDeep(prev, aggKey);
|
|
103
|
+
const afterSum = sumDeep(next, aggKey);
|
|
104
|
+
const aggPath = [...path, aggKey].join('.');
|
|
105
|
+
out[aggPath] = {
|
|
106
|
+
kind: beforeSum > afterSum ? 'decrease' : beforeSum < afterSum ? 'increase' : 'same',
|
|
107
|
+
path: aggPath,
|
|
108
|
+
before: beforeSum,
|
|
109
|
+
after: afterSum,
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (typeof prev === 'number' && typeof next === 'number') {
|
|
115
|
+
const fullPath = path.join('.');
|
|
116
|
+
out[fullPath] = {
|
|
117
|
+
kind: prev > next ? 'decrease' : prev < next ? 'increase' : 'same',
|
|
118
|
+
path: fullPath,
|
|
119
|
+
before: prev,
|
|
120
|
+
after: next,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
if (basePath.length === 0) {
|
|
125
|
+
aggregateTarget.forEach((aggKey) => {
|
|
126
|
+
const beforeSum = sumDeep(before, aggKey);
|
|
127
|
+
const afterSum = sumDeep(after, aggKey);
|
|
128
|
+
const rootPath = `root.${aggKey}`;
|
|
129
|
+
out[rootPath] = {
|
|
130
|
+
kind: beforeSum > afterSum ? 'decrease' : beforeSum < afterSum ? 'increase' : 'same',
|
|
131
|
+
path: rootPath,
|
|
132
|
+
before: beforeSum,
|
|
133
|
+
after: afterSum,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
exports.aggregatedCompareMetric = aggregatedCompareMetric;
|
|
140
|
+
function prepareItemDelta(item, calcDelta = true) {
|
|
141
|
+
const out = (0, exports.undefinedDeltaMetric)();
|
|
142
|
+
if (!item) {
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
out.kind = calcDelta ? item.kind : 'init';
|
|
146
|
+
out.value = item.after.toString();
|
|
147
|
+
if (calcDelta) {
|
|
148
|
+
let change = item.kind === 'increase' ? ' +' : ' ';
|
|
149
|
+
if (item.kind === 'same') {
|
|
150
|
+
change += 'same';
|
|
151
|
+
}
|
|
152
|
+
else if (item.before === 0) {
|
|
153
|
+
change += '100.00%';
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
change += (((item.after - item.before) / item.before) * 100).toFixed(2) + '%';
|
|
157
|
+
}
|
|
158
|
+
out.value += change;
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
function prepareDelta(pair) {
|
|
163
|
+
const result = {};
|
|
164
|
+
const out = {
|
|
165
|
+
label: pair.after.label,
|
|
166
|
+
createdAt: pair.after.createdAt,
|
|
167
|
+
result,
|
|
168
|
+
};
|
|
169
|
+
const beforeMap = new Map();
|
|
170
|
+
if (pair.before) {
|
|
171
|
+
for (const b of pair.before.items) {
|
|
172
|
+
const contractKey = b.contractName || b.address;
|
|
173
|
+
const methodKey = b.methodName || b.opCode;
|
|
174
|
+
if (!beforeMap.has(contractKey)) {
|
|
175
|
+
beforeMap.set(contractKey, new Map());
|
|
176
|
+
}
|
|
177
|
+
beforeMap.get(contractKey).set(methodKey, b);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const a of pair.after.items) {
|
|
181
|
+
const contractKey = a.contractName || a.address;
|
|
182
|
+
const methodKey = a.methodName || a.opCode;
|
|
183
|
+
const b = beforeMap.get(contractKey)?.get(methodKey); // может быть undefined
|
|
184
|
+
const calcDelta = !!b;
|
|
185
|
+
if (!result[contractKey]) {
|
|
186
|
+
result[contractKey] = {};
|
|
187
|
+
}
|
|
188
|
+
const item = {};
|
|
189
|
+
result[contractKey][methodKey] = item;
|
|
190
|
+
const delta = aggregatedCompareMetric(b || a, a);
|
|
191
|
+
item.gasUsed = prepareItemDelta(delta['execute.compute.gasUsed'], calcDelta);
|
|
192
|
+
item.cells = prepareItemDelta(delta['state.cells'], calcDelta);
|
|
193
|
+
item.bits = prepareItemDelta(delta['state.bits'], calcDelta);
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
exports.prepareDelta = prepareDelta;
|
|
198
|
+
function makeGasReport(list) {
|
|
199
|
+
list = (list || []).sort((0, collectMetric_1.sortByCreatedAt)());
|
|
200
|
+
const out = [];
|
|
201
|
+
if (list.length === 0)
|
|
202
|
+
return out;
|
|
203
|
+
for (let i = 0; i < list.length; i++) {
|
|
204
|
+
const after = list[i];
|
|
205
|
+
const before = i > 0 ? list[i - 1] : undefined;
|
|
206
|
+
out.push(prepareDelta({ after, before }));
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
210
|
+
exports.makeGasReport = makeGasReport;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.gasReportTable = void 0;
|
|
4
|
+
const table_1 = require("table");
|
|
5
|
+
const deltaResult_1 = require("./deltaResult");
|
|
6
|
+
const collectMetric_1 = require("./collectMetric");
|
|
7
|
+
const gapCellWidth = 1;
|
|
8
|
+
const maxCellWidth = 25;
|
|
9
|
+
const border = {
|
|
10
|
+
topBody: '─',
|
|
11
|
+
topJoin: '┬',
|
|
12
|
+
topLeft: '┌',
|
|
13
|
+
topRight: '┐',
|
|
14
|
+
bottomBody: '─',
|
|
15
|
+
bottomJoin: '┴',
|
|
16
|
+
bottomLeft: '└',
|
|
17
|
+
bottomRight: '┘',
|
|
18
|
+
bodyLeft: '│',
|
|
19
|
+
bodyRight: '│',
|
|
20
|
+
bodyJoin: '│',
|
|
21
|
+
joinBody: '─',
|
|
22
|
+
joinLeft: '├',
|
|
23
|
+
joinRight: '┤',
|
|
24
|
+
joinJoin: '┼',
|
|
25
|
+
};
|
|
26
|
+
function wrap(value, max) {
|
|
27
|
+
if (value.length > max) {
|
|
28
|
+
return `${value.slice(0, max - 3)}...`;
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function prepareResult(list, color) {
|
|
33
|
+
list = list.sort((0, collectMetric_1.sortByCreatedAt)(true));
|
|
34
|
+
const flat = (0, deltaResult_1.toFlatDeltaResult)(list);
|
|
35
|
+
const rows = [];
|
|
36
|
+
const widthCols = [];
|
|
37
|
+
for (const [contract, method, ...values] of flat.rows) {
|
|
38
|
+
const row = [wrap(contract, maxCellWidth), wrap(method, maxCellWidth)];
|
|
39
|
+
widthCols[0] = Math.max(widthCols[0] ?? 0, row[0].length);
|
|
40
|
+
widthCols[1] = Math.max(widthCols[1] ?? 0, row[1].length);
|
|
41
|
+
values.forEach((metric, idx) => {
|
|
42
|
+
const value = color ? color(metric) : metric.value;
|
|
43
|
+
row.push(value);
|
|
44
|
+
const colIdx = idx + 2;
|
|
45
|
+
widthCols[colIdx] = Math.max(widthCols[colIdx] ?? 0, metric.value.length);
|
|
46
|
+
});
|
|
47
|
+
rows.push(row);
|
|
48
|
+
}
|
|
49
|
+
const headers = flat.header;
|
|
50
|
+
for (let i = 0; i < headers.length; i++) {
|
|
51
|
+
widthCols[i] = Math.max(widthCols[i] ?? 0, headers[i].length + gapCellWidth);
|
|
52
|
+
}
|
|
53
|
+
const groupIndex = {};
|
|
54
|
+
let current = 0;
|
|
55
|
+
while (current < rows.length) {
|
|
56
|
+
const contract = rows[current][0];
|
|
57
|
+
let count = 1;
|
|
58
|
+
while (rows[current + count]?.[0] === contract) {
|
|
59
|
+
count++;
|
|
60
|
+
}
|
|
61
|
+
groupIndex[contract] = { index: current, size: count };
|
|
62
|
+
current += count;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
labels: list.map((s) => s.label),
|
|
66
|
+
headers,
|
|
67
|
+
widthCols,
|
|
68
|
+
rows,
|
|
69
|
+
groupIndex,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function gasReportTable(list, color) {
|
|
73
|
+
const result = prepareResult(list, color);
|
|
74
|
+
if (result.rows.length < 1) {
|
|
75
|
+
return 'No data available';
|
|
76
|
+
}
|
|
77
|
+
const columns = [];
|
|
78
|
+
for (let i = 0; i < result.headers.length; i++) {
|
|
79
|
+
columns.push({ alignment: 'center', verticalAlignment: 'middle', width: result.widthCols[i] });
|
|
80
|
+
}
|
|
81
|
+
const spanningCells = [
|
|
82
|
+
{ col: 0, row: 0, rowSpan: 2, verticalAlignment: 'middle' },
|
|
83
|
+
{ col: 1, row: 0, rowSpan: 2, verticalAlignment: 'middle' }, // Method title
|
|
84
|
+
];
|
|
85
|
+
for (const group of Object.values(result.groupIndex)) {
|
|
86
|
+
// rowSpan for Contract name
|
|
87
|
+
spanningCells.push({ col: 0, row: group.index + 2, rowSpan: group.size, verticalAlignment: 'middle' });
|
|
88
|
+
}
|
|
89
|
+
const data = [[], ['', '']];
|
|
90
|
+
data[0].push(...result.headers.slice(0, 2));
|
|
91
|
+
data[1].push(...result.headers.slice(2));
|
|
92
|
+
const metricCount = (result.headers.length - 2) / list.length;
|
|
93
|
+
let labelTitleOffset = 2;
|
|
94
|
+
for (const label of result.labels) {
|
|
95
|
+
spanningCells.push({
|
|
96
|
+
col: labelTitleOffset,
|
|
97
|
+
row: 0,
|
|
98
|
+
colSpan: metricCount,
|
|
99
|
+
verticalAlignment: 'middle',
|
|
100
|
+
});
|
|
101
|
+
labelTitleOffset += metricCount;
|
|
102
|
+
data[0].push(...[label, ...Array.from({ length: metricCount - 1 }, () => '')]);
|
|
103
|
+
}
|
|
104
|
+
data.push(...result.rows);
|
|
105
|
+
const config = {
|
|
106
|
+
columns,
|
|
107
|
+
spanningCells,
|
|
108
|
+
border,
|
|
109
|
+
};
|
|
110
|
+
return (0, table_1.table)(data, config);
|
|
111
|
+
}
|
|
112
|
+
exports.gasReportTable = gasReportTable;
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./collectMetric"), exports);
|
|
18
|
+
__exportStar(require("./ContractDatabase"), exports);
|
|
19
|
+
__exportStar(require("./defaultColor"), exports);
|
|
20
|
+
__exportStar(require("./deltaResult"), exports);
|
|
21
|
+
__exportStar(require("./gasReportTable"), exports);
|
|
22
|
+
__exportStar(require("./readSnapshots"), exports);
|