@nite-framework/nite-zk-profiler 0.1.3 → 0.2.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/README.md +104 -6
- package/dist/budget.d.ts +36 -10
- package/dist/budget.js +94 -37
- package/dist/cache.d.ts +16 -0
- package/dist/cache.js +54 -0
- package/dist/cli.d.ts +10 -3
- package/dist/cli.js +211 -50
- package/dist/colors.d.ts +40 -0
- package/dist/colors.js +69 -0
- package/dist/compile.d.ts +1 -1
- package/dist/compile.js +17 -8
- package/dist/deep.d.ts +24 -0
- package/dist/deep.js +60 -0
- package/dist/diff.d.ts +30 -0
- package/dist/diff.js +64 -0
- package/dist/estimate.d.ts +38 -0
- package/dist/estimate.js +85 -0
- package/dist/measure.d.ts +14 -5
- package/dist/measure.js +72 -17
- package/dist/progress.d.ts +19 -0
- package/dist/progress.js +51 -0
- package/dist/report.d.ts +20 -6
- package/dist/report.js +163 -39
- package/dist/version.d.ts +9 -0
- package/dist/version.js +19 -0
- package/package.json +1 -1
package/dist/report.js
CHANGED
|
@@ -1,74 +1,198 @@
|
|
|
1
|
+
import { bold, costColor, dim, gray, green, red, visibleLength, yellow } from "./colors.js";
|
|
2
|
+
import { formatBytes, formatMs } from "./deep.js";
|
|
3
|
+
import { estimateProvingMs, formatDuration } from "./estimate.js";
|
|
4
|
+
/** Pad accounting for ANSI escapes, which do not occupy screen columns. */
|
|
1
5
|
function pad(text, width) {
|
|
2
|
-
return text.
|
|
6
|
+
return text + " ".repeat(Math.max(0, width - visibleLength(text)));
|
|
3
7
|
}
|
|
4
8
|
function padLeft(text, width) {
|
|
5
|
-
return
|
|
9
|
+
return " ".repeat(Math.max(0, width - visibleLength(text))) + text;
|
|
6
10
|
}
|
|
7
|
-
function
|
|
8
|
-
|
|
11
|
+
function table(costs, opts, indent) {
|
|
12
|
+
const { deep, calibration, showEstimate } = opts;
|
|
13
|
+
const cells = costs.map((c) => {
|
|
14
|
+
const paint = costColor(c.relativeCost);
|
|
15
|
+
const d = deep?.get(c.circuit);
|
|
16
|
+
return {
|
|
17
|
+
circuit: c.circuit,
|
|
18
|
+
rows: String(c.rows),
|
|
19
|
+
k: paint(String(c.k)),
|
|
20
|
+
capacity: String(c.capacity),
|
|
21
|
+
cost: paint(`${c.relativeCost}x`),
|
|
22
|
+
prove: showEstimate ? `~${formatDuration(estimateProvingMs(c.k, calibration).ms)}` : "",
|
|
23
|
+
setup: d ? formatMs(d.setupMs) : "",
|
|
24
|
+
key: d ? formatBytes(d.proverKeyBytes) : "",
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
const width = (header, get) => Math.max(header.length, ...cells.map((c) => visibleLength(get(c))));
|
|
28
|
+
const w = {
|
|
29
|
+
name: width("circuit", (c) => c.circuit),
|
|
30
|
+
rows: width("rows", (c) => c.rows),
|
|
31
|
+
k: width("k", (c) => c.k),
|
|
32
|
+
cap: width("capacity", (c) => c.capacity),
|
|
33
|
+
cost: width("cost", (c) => c.cost),
|
|
34
|
+
prove: showEstimate ? width("est. prove", (c) => c.prove) : 0,
|
|
35
|
+
setup: deep ? width("setup", (c) => c.setup) : 0,
|
|
36
|
+
key: deep ? width("prover key", (c) => c.key) : 0,
|
|
37
|
+
};
|
|
38
|
+
const head = () => {
|
|
39
|
+
let h = `${indent}${pad("circuit", w.name)} ${padLeft("rows", w.rows)} ${padLeft("k", w.k)} ` +
|
|
40
|
+
`${padLeft("capacity", w.cap)} ${padLeft("cost", w.cost)}`;
|
|
41
|
+
if (showEstimate)
|
|
42
|
+
h += ` ${padLeft("est. prove", w.prove)}`;
|
|
43
|
+
if (deep)
|
|
44
|
+
h += ` ${padLeft("setup", w.setup)} ${padLeft("prover key", w.key)}`;
|
|
45
|
+
return dim(h);
|
|
46
|
+
};
|
|
47
|
+
const lines = [head()];
|
|
48
|
+
for (const c of cells) {
|
|
49
|
+
let line = `${indent}${pad(c.circuit, w.name)} ${padLeft(c.rows, w.rows)} ${padLeft(c.k, w.k)} ` +
|
|
50
|
+
`${padLeft(c.capacity, w.cap)} ${padLeft(c.cost, w.cost)}`;
|
|
51
|
+
if (showEstimate)
|
|
52
|
+
line += ` ${padLeft(c.prove, w.prove)}`;
|
|
53
|
+
if (deep)
|
|
54
|
+
line += ` ${padLeft(c.setup, w.setup)} ${padLeft(c.key, w.key)}`;
|
|
55
|
+
lines.push(line);
|
|
56
|
+
}
|
|
57
|
+
return lines;
|
|
9
58
|
}
|
|
10
|
-
/**
|
|
11
|
-
export function formatProfile(
|
|
12
|
-
const nameWidth = Math.max(7, ...costs.map((c) => c.circuit.length));
|
|
13
|
-
const rowsWidth = Math.max(4, ...costs.map((c) => String(c.rows).length));
|
|
14
|
-
const capWidth = Math.max(8, ...costs.map((c) => String(c.capacity).length));
|
|
15
|
-
const costWidth = Math.max(4, ...costs.map((c) => costLabel(c.relativeCost).length));
|
|
59
|
+
/** Per circuit cost table, one block per contract. */
|
|
60
|
+
export function formatProfile(contracts, opts) {
|
|
16
61
|
const lines = [""];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
62
|
+
const many = contracts.length > 1;
|
|
63
|
+
for (const { source, costs } of contracts) {
|
|
64
|
+
if (many) {
|
|
65
|
+
lines.push(bold(` ${source}`));
|
|
66
|
+
lines.push(...table(costs, opts, " "));
|
|
67
|
+
lines.push("");
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
lines.push(...table(costs, opts, " "));
|
|
71
|
+
lines.push("");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const all = contracts.flatMap((c) => c.costs);
|
|
75
|
+
const circuits = all.length;
|
|
76
|
+
const suffix = opts.cached ? " (cached)" : "";
|
|
77
|
+
const contractNote = many ? `${contracts.length} contracts, ` : "";
|
|
78
|
+
lines.push(gray(` ${contractNote}${circuits} circuit${circuits === 1 ? "" : "s"}, ` +
|
|
79
|
+
`toolchain ${opts.toolchain.version}, ${opts.toolchain.zkirVersion}, ` +
|
|
80
|
+
`${(opts.elapsedMs / 1000).toFixed(1)}s${suffix}`));
|
|
81
|
+
if (all.length > 0) {
|
|
82
|
+
const worst = all.reduce((a, b) => (b.k > a.k ? b : a), all[0]);
|
|
83
|
+
const cheapest = Math.min(...all.map((c) => c.k));
|
|
84
|
+
if (worst.k > cheapest) {
|
|
85
|
+
lines.push(gray(` ${bold(worst.circuit)} dominates at k=${worst.k}, ` +
|
|
86
|
+
`${2 ** (worst.k - cheapest)}x the cheapest circuit here.`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (opts.showEstimate) {
|
|
90
|
+
lines.push(gray(opts.calibration
|
|
91
|
+
? ` est. prove is modelled as 2^k, calibrated from an observed ${formatDuration(opts.calibration.observedMs)} proof at k=${opts.calibration.observedK}.`
|
|
92
|
+
: " est. prove is modelled as 2^k on an uncalibrated default. Run `nite-zk calibrate` to anchor it."));
|
|
23
93
|
}
|
|
24
|
-
const plural = costs.length === 1 ? "circuit" : "circuits";
|
|
25
|
-
lines.push("");
|
|
26
|
-
lines.push(` ${costs.length} ${plural}, toolchain ${toolchain.version}, ` +
|
|
27
|
-
`${toolchain.zkirVersion}, ${(elapsedMs / 1000).toFixed(1)}s`);
|
|
28
94
|
lines.push("");
|
|
29
95
|
return lines.join("\n");
|
|
30
96
|
}
|
|
31
97
|
const STATUS_NOTE = {
|
|
32
|
-
under: (r) => `under by ${(r.maxK ?? 0) - (r.k ?? 0)}
|
|
33
|
-
at: () => "at budget",
|
|
98
|
+
under: (r) => green(`under by ${(r.maxK ?? 0) - (r.k ?? 0)}`),
|
|
99
|
+
at: () => green("at budget"),
|
|
34
100
|
over: (r) => {
|
|
35
101
|
const by = (r.k ?? 0) - (r.maxK ?? 0);
|
|
36
|
-
return `over by ${by}, about ${2 ** by}x
|
|
102
|
+
return red(`over by ${by}, about ${2 ** by}x`);
|
|
37
103
|
},
|
|
38
|
-
undeclared: () => "not in budget",
|
|
39
|
-
stale: () => "no longer in contract",
|
|
104
|
+
undeclared: () => yellow("not in budget"),
|
|
105
|
+
stale: () => dim("no longer in contract"),
|
|
40
106
|
};
|
|
41
|
-
/**
|
|
107
|
+
/** Budget comparison, grouped by contract when there is more than one. */
|
|
42
108
|
export function formatCheck(result) {
|
|
43
|
-
const
|
|
109
|
+
const contracts = [...new Set(result.rows.map((r) => r.contract))];
|
|
110
|
+
const many = contracts.length > 1;
|
|
44
111
|
const lines = [""];
|
|
45
|
-
for (const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
112
|
+
for (const contract of contracts) {
|
|
113
|
+
const rows = result.rows.filter((r) => r.contract === contract);
|
|
114
|
+
const indent = many ? " " : " ";
|
|
115
|
+
if (many)
|
|
116
|
+
lines.push(bold(` ${contract}`));
|
|
117
|
+
const wName = Math.max(7, ...rows.map((r) => r.circuit.length));
|
|
118
|
+
for (const row of rows) {
|
|
119
|
+
const k = row.k === undefined ? " -" : padLeft(String(row.k), 3);
|
|
120
|
+
const maxK = row.maxK === undefined ? " -" : padLeft(String(row.maxK), 3);
|
|
121
|
+
const paint = row.status === "over" ? red : (t) => t;
|
|
122
|
+
lines.push(`${indent}${pad(paint(row.circuit), wName)} k ${k} ${dim("budget")} ${maxK} ` +
|
|
123
|
+
STATUS_NOTE[row.status](row));
|
|
124
|
+
}
|
|
125
|
+
if (many)
|
|
126
|
+
lines.push("");
|
|
49
127
|
}
|
|
50
128
|
const over = result.rows.filter((r) => r.status === "over").length;
|
|
51
129
|
const undeclared = result.rows.filter((r) => r.status === "undeclared").length;
|
|
52
|
-
|
|
130
|
+
if (!many)
|
|
131
|
+
lines.push("");
|
|
53
132
|
if (over > 0) {
|
|
54
|
-
lines.push(` FAIL: ${over} circuit${over === 1 ? "" : "s"} over budget`);
|
|
133
|
+
lines.push(red(bold(` FAIL: ${over} circuit${over === 1 ? "" : "s"} over budget`)));
|
|
55
134
|
}
|
|
56
135
|
else if (result.failed) {
|
|
57
|
-
lines.push(` FAIL: ${undeclared} circuit${undeclared === 1 ? "" : "s"} not declared
|
|
136
|
+
lines.push(red(bold(` FAIL: ${undeclared} circuit${undeclared === 1 ? "" : "s"} not declared (--strict)`)));
|
|
58
137
|
}
|
|
59
138
|
else {
|
|
60
|
-
lines.push(" OK: every circuit within budget");
|
|
139
|
+
lines.push(green(bold(" OK: every circuit within budget")));
|
|
61
140
|
}
|
|
62
141
|
lines.push("");
|
|
63
142
|
return lines.join("\n");
|
|
64
143
|
}
|
|
65
|
-
|
|
144
|
+
/** Comparison against a git ref. */
|
|
145
|
+
export function formatDiff(result) {
|
|
146
|
+
const lines = [""];
|
|
147
|
+
const wName = Math.max(7, ...result.rows.map((r) => r.circuit.length));
|
|
148
|
+
for (const row of result.rows) {
|
|
149
|
+
const before = row.before === undefined ? dim(" -") : padLeft(String(row.before), 3);
|
|
150
|
+
const after = row.after === undefined ? dim(" -") : padLeft(String(row.after), 3);
|
|
151
|
+
let note;
|
|
152
|
+
if (row.before === undefined)
|
|
153
|
+
note = yellow("new circuit");
|
|
154
|
+
else if (row.after === undefined)
|
|
155
|
+
note = dim("removed");
|
|
156
|
+
else if (row.after > row.before)
|
|
157
|
+
note = red(`+${row.after - row.before}, about ${2 ** (row.after - row.before)}x more expensive`);
|
|
158
|
+
else if (row.after < row.before)
|
|
159
|
+
note = green(`${row.after - row.before}, about ${2 ** (row.before - row.after)}x cheaper`);
|
|
160
|
+
else
|
|
161
|
+
note = dim("unchanged");
|
|
162
|
+
lines.push(` ${pad(row.circuit, wName)} k ${before} ${dim("->")} ${after} ${note}`);
|
|
163
|
+
}
|
|
164
|
+
const worse = result.rows.filter((r) => r.before !== undefined && r.after !== undefined && r.after > r.before).length;
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push(worse > 0
|
|
167
|
+
? red(bold(` ${worse} circuit${worse === 1 ? "" : "s"} more expensive than ${result.ref}`))
|
|
168
|
+
: green(bold(` nothing more expensive than ${result.ref}`)));
|
|
169
|
+
lines.push("");
|
|
170
|
+
return lines.join("\n");
|
|
171
|
+
}
|
|
172
|
+
export function profileJson(contracts, opts) {
|
|
66
173
|
return JSON.stringify({
|
|
67
|
-
toolchain: toolchain.version,
|
|
68
|
-
zkir: toolchain.zkirVersion,
|
|
69
|
-
|
|
174
|
+
toolchain: opts.toolchain.version,
|
|
175
|
+
zkir: opts.toolchain.zkirVersion,
|
|
176
|
+
contracts: contracts.map(({ source, costs }) => ({
|
|
177
|
+
source,
|
|
178
|
+
circuits: costs.map((c) => {
|
|
179
|
+
const d = opts.deep?.get(c.circuit);
|
|
180
|
+
const est = opts.showEstimate
|
|
181
|
+
? {
|
|
182
|
+
estimatedProvingMs: Math.round(estimateProvingMs(c.k, opts.calibration).ms),
|
|
183
|
+
estimateCalibrated: opts.calibration !== undefined,
|
|
184
|
+
}
|
|
185
|
+
: {};
|
|
186
|
+
return d
|
|
187
|
+
? { ...c, ...est, setupMs: Math.round(d.setupMs), proverKeyBytes: d.proverKeyBytes }
|
|
188
|
+
: { ...c, ...est };
|
|
189
|
+
}),
|
|
190
|
+
})),
|
|
70
191
|
}, null, 2);
|
|
71
192
|
}
|
|
72
193
|
export function checkJson(result) {
|
|
73
194
|
return JSON.stringify(result, null, 2);
|
|
74
195
|
}
|
|
196
|
+
export function diffJson(result) {
|
|
197
|
+
return JSON.stringify(result, null, 2);
|
|
198
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read from package.json rather than hardcoded, because `npm version` updates
|
|
3
|
+
* the manifest and would silently leave a literal behind, so `--version` would
|
|
4
|
+
* report the wrong release.
|
|
5
|
+
*
|
|
6
|
+
* Resolves from this module, which sits one level below the manifest in both
|
|
7
|
+
* `src/` and the published `dist/`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function toolVersion(): string;
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Read from package.json rather than hardcoded, because `npm version` updates
|
|
5
|
+
* the manifest and would silently leave a literal behind, so `--version` would
|
|
6
|
+
* report the wrong release.
|
|
7
|
+
*
|
|
8
|
+
* Resolves from this module, which sits one level below the manifest in both
|
|
9
|
+
* `src/` and the published `dist/`.
|
|
10
|
+
*/
|
|
11
|
+
export function toolVersion() {
|
|
12
|
+
try {
|
|
13
|
+
const manifest = join(import.meta.dirname, "..", "package.json");
|
|
14
|
+
return JSON.parse(readFileSync(manifest, "utf8")).version ?? "unknown";
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return "unknown";
|
|
18
|
+
}
|
|
19
|
+
}
|