@fedify/cli 2.3.0-dev.1347 → 2.3.0-dev.1361
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/dist/bench/action.js +29 -189
- package/dist/bench/command.js +13 -43
- package/dist/bench/load/clock.js +2 -20
- package/dist/bench/load/generator.js +9 -42
- package/dist/bench/metrics/stats-client.js +3 -65
- package/dist/bench/mod.js +2 -9
- package/dist/bench/render/markdown.js +0 -1
- package/dist/bench/render/text.js +0 -1
- package/dist/bench/result/build.js +10 -133
- package/dist/bench/result/expect/evaluate.js +1 -1
- package/dist/bench/result/schema.js +3 -353
- package/dist/bench/safety/gate.js +2 -4
- package/dist/bench/scenario/normalize.js +2 -1
- package/dist/bench/scenario/schema.js +9 -50
- package/dist/bench/scenario/validate.js +2 -2
- package/dist/bench/scenarios/inbox.js +12 -4
- package/dist/bench/scenarios/registry.js +1 -19
- package/dist/bench/scenarios/runner.js +1 -21
- package/dist/bench/scenarios/webfinger.js +1 -1
- package/dist/cache.js +1 -1
- package/dist/config.js +1 -1
- package/dist/deno.js +1 -1
- package/dist/docloader.js +1 -1
- package/dist/generate-vocab/action.js +1 -1
- package/dist/generate-vocab/command.js +3 -5
- package/dist/generate-vocab/mod.js +4 -0
- package/dist/imagerenderer.js +2 -2
- package/dist/inbox/command.js +4 -6
- package/dist/inbox.js +4 -4
- package/dist/init/mod.js +3 -0
- package/dist/log.js +2 -2
- package/dist/lookup.js +123 -12
- package/dist/mod.js +23 -2
- package/dist/nodeinfo.js +9 -11
- package/dist/options.js +1 -1
- package/dist/relay/command.js +4 -6
- package/dist/relay.js +2 -2
- package/dist/runner.js +46 -69
- package/dist/tempserver.js +1 -1
- package/dist/tunnel.js +4 -6
- package/dist/utils.js +4 -5
- package/dist/webfinger/action.js +1 -1
- package/dist/webfinger/command.js +4 -6
- package/dist/webfinger/lib.js +1 -1
- package/dist/webfinger/mod.js +4 -0
- package/package.json +12 -13
- package/dist/bench/compare/schema.js +0 -16
- package/dist/bench/compare.js +0 -667
- package/dist/bench/scenarios/actor.js +0 -38
- package/dist/bench/scenarios/failure.js +0 -363
- package/dist/bench/scenarios/fanout.js +0 -261
- package/dist/bench/scenarios/mixed.js +0 -244
- package/dist/bench/scenarios/object-discovery.js +0 -211
- package/dist/bench/scenarios/object.js +0 -54
- package/dist/bench/scenarios/read.js +0 -108
- package/dist/commands.js +0 -110
- package/dist/lookup/command.js +0 -121
package/dist/bench/mod.js
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
+
import "./action.js";
|
|
2
3
|
import "./command.js";
|
|
3
|
-
|
|
4
|
-
import { runBenchCompare } from "./compare.js";
|
|
5
|
-
//#region src/bench/mod.ts
|
|
6
|
-
/** Runs a parsed benchmark command. */
|
|
7
|
-
function runBench(command) {
|
|
8
|
-
return command.mode === "compare" ? runBenchCompare(command) : runBench$1(command);
|
|
9
|
-
}
|
|
10
|
-
//#endregion
|
|
11
|
-
export { runBench };
|
|
4
|
+
export {};
|
|
@@ -32,7 +32,6 @@ function renderScenario(scenario) {
|
|
|
32
32
|
lines.push(`| Requests | ${formatNumber(r.total)} |`);
|
|
33
33
|
lines.push(`| Success rate | ${formatPercent(r.successRate)} |`);
|
|
34
34
|
lines.push(`| Throughput | ${formatNumber(scenario.throughputPerSec)}/s |`);
|
|
35
|
-
if (scenario.deliveryThroughputPerSec != null) lines.push(`| Delivery throughput | ${formatNumber(scenario.deliveryThroughputPerSec)}/s |`);
|
|
36
35
|
const l = scenario.client.latencyMs;
|
|
37
36
|
lines.push(`| Latency p50 | ${formatNumber(l.p50)}ms |`);
|
|
38
37
|
lines.push(`| Latency p95 | ${formatNumber(l.p95)}ms |`);
|
|
@@ -28,7 +28,6 @@ function renderScenario(scenario) {
|
|
|
28
28
|
const r = scenario.requests;
|
|
29
29
|
lines.push(` Requests: ${formatNumber(r.total)} (ok ${formatNumber(r.ok)}, failed ${formatNumber(r.failed)}, success ${formatPercent(r.successRate)})`);
|
|
30
30
|
lines.push(` Throughput: ${formatNumber(scenario.throughputPerSec)} req/s`);
|
|
31
|
-
if (scenario.deliveryThroughputPerSec != null) lines.push(` Delivery throughput: ${formatNumber(scenario.deliveryThroughputPerSec)} deliveries/s`);
|
|
32
31
|
const l = scenario.client.latencyMs;
|
|
33
32
|
lines.push(` Client latency (ms): p50 ${formatNumber(l.p50)} p95 ${formatNumber(l.p95)} p99 ${formatNumber(l.p99)} mean ${formatNumber(l.mean)} max ${formatNumber(l.max)}`);
|
|
34
33
|
if (scenario.server?.signatureVerificationMs != null) lines.push(` Server signature verification (ms): ${describePartial(scenario.server.signatureVerificationMs.overall)}`);
|
|
@@ -3,8 +3,8 @@ import { version } from "../../deno.js";
|
|
|
3
3
|
import { evaluateExpect } from "./expect/evaluate.js";
|
|
4
4
|
import { REPORT_SCHEMA_ID } from "./schema.js";
|
|
5
5
|
import process from "node:process";
|
|
6
|
-
import { cpus } from "node:os";
|
|
7
6
|
import { createHash } from "node:crypto";
|
|
7
|
+
import { cpus } from "node:os";
|
|
8
8
|
//#region src/bench/result/build.ts
|
|
9
9
|
/**
|
|
10
10
|
* Assembly of the canonical benchmark report from measured scenario data.
|
|
@@ -23,25 +23,19 @@ import { createHash } from "node:crypto";
|
|
|
23
23
|
* @returns The assembled scenario result.
|
|
24
24
|
*/
|
|
25
25
|
function buildScenarioResult(scenario, measurement) {
|
|
26
|
-
const
|
|
27
|
-
if (measurements.length < 1) throw new RangeError("At least one scenario measurement is required.");
|
|
28
|
-
const aggregate = measurements.length === 1 ? measurements[0] : aggregateMeasurements(measurements);
|
|
29
|
-
const { results, passed } = evaluateExpect(scenario.expect, aggregate);
|
|
26
|
+
const { results, passed } = evaluateExpect(scenario.expect, measurement);
|
|
30
27
|
return {
|
|
31
28
|
name: scenario.name,
|
|
32
29
|
type: scenario.type,
|
|
33
30
|
load: loadSummary(scenario),
|
|
34
|
-
requests:
|
|
35
|
-
throughputPerSec:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
errors: aggregate.errors,
|
|
31
|
+
requests: measurement.requests,
|
|
32
|
+
throughputPerSec: measurement.throughputPerSec,
|
|
33
|
+
client: measurement.client,
|
|
34
|
+
server: measurement.server,
|
|
35
|
+
errors: measurement.errors,
|
|
40
36
|
expectations: results,
|
|
41
|
-
passed: passed &&
|
|
42
|
-
|
|
43
|
-
...measurements.length > 1 ? { runs: measurements.map((m, index) => runResult(index + 1, m)) } : {},
|
|
44
|
-
...aggregate.histogram ? { histogram: aggregate.histogram } : {}
|
|
37
|
+
passed: passed && measurement.requests.total > 0,
|
|
38
|
+
...measurement.histogram ? { histogram: measurement.histogram } : {}
|
|
45
39
|
};
|
|
46
40
|
}
|
|
47
41
|
/**
|
|
@@ -53,7 +47,7 @@ function buildScenarioResult(scenario, measurement) {
|
|
|
53
47
|
function buildReport(input) {
|
|
54
48
|
return {
|
|
55
49
|
$schema: REPORT_SCHEMA_ID,
|
|
56
|
-
schemaVersion:
|
|
50
|
+
schemaVersion: 1,
|
|
57
51
|
tool: {
|
|
58
52
|
name: "@fedify/cli",
|
|
59
53
|
version
|
|
@@ -67,123 +61,6 @@ function buildReport(input) {
|
|
|
67
61
|
scenarios: input.scenarios
|
|
68
62
|
};
|
|
69
63
|
}
|
|
70
|
-
function aggregateMeasurements(measurements) {
|
|
71
|
-
const errors = sumErrorBuckets(measurements.flatMap((m) => m.errors));
|
|
72
|
-
const total = sum(measurements.map((m) => m.requests.total));
|
|
73
|
-
const ok = sum(measurements.map((m) => m.requests.ok));
|
|
74
|
-
const failed = sum(measurements.map((m) => m.requests.failed));
|
|
75
|
-
const delivery = medianPresent(measurements.map((m) => m.deliveryThroughputPerSec));
|
|
76
|
-
return {
|
|
77
|
-
requests: {
|
|
78
|
-
total,
|
|
79
|
-
ok,
|
|
80
|
-
failed,
|
|
81
|
-
successRate: Math.min(...measurements.map((m) => m.requests.successRate))
|
|
82
|
-
},
|
|
83
|
-
throughputPerSec: median(measurements.map((m) => m.throughputPerSec)),
|
|
84
|
-
...delivery == null ? {} : { deliveryThroughputPerSec: delivery },
|
|
85
|
-
client: { latencyMs: {
|
|
86
|
-
p50: median(measurements.map((m) => m.client.latencyMs.p50)),
|
|
87
|
-
p95: median(measurements.map((m) => m.client.latencyMs.p95)),
|
|
88
|
-
p99: median(measurements.map((m) => m.client.latencyMs.p99)),
|
|
89
|
-
mean: median(measurements.map((m) => m.client.latencyMs.mean)),
|
|
90
|
-
max: median(measurements.map((m) => m.client.latencyMs.max))
|
|
91
|
-
} },
|
|
92
|
-
server: aggregateServer(measurements.map((m) => m.server)),
|
|
93
|
-
errors
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
function runResult(run, measurement) {
|
|
97
|
-
return {
|
|
98
|
-
run,
|
|
99
|
-
requests: measurement.requests,
|
|
100
|
-
throughputPerSec: measurement.throughputPerSec,
|
|
101
|
-
...measurement.deliveryThroughputPerSec == null ? {} : { deliveryThroughputPerSec: measurement.deliveryThroughputPerSec },
|
|
102
|
-
client: measurement.client,
|
|
103
|
-
server: measurement.server,
|
|
104
|
-
errors: measurement.errors,
|
|
105
|
-
...measurement.histogram ? { histogram: measurement.histogram } : {}
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
function aggregateServer(servers) {
|
|
109
|
-
const present = servers.filter((s) => s != null);
|
|
110
|
-
if (present.length !== servers.length) return null;
|
|
111
|
-
const signature = aggregateSignatureVerification(present);
|
|
112
|
-
const queue = aggregateQueue(present);
|
|
113
|
-
return {
|
|
114
|
-
...signature == null ? {} : { signatureVerificationMs: signature },
|
|
115
|
-
...queue == null ? {} : { queue }
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
function aggregateSignatureVerification(servers) {
|
|
119
|
-
const values = servers.map((s) => s.signatureVerificationMs).filter((s) => s != null);
|
|
120
|
-
if (values.length !== servers.length) return null;
|
|
121
|
-
const standards = /* @__PURE__ */ new Set();
|
|
122
|
-
for (const value of values) for (const key of Object.keys(value.byStandard ?? {})) standards.add(key);
|
|
123
|
-
const byStandard = {};
|
|
124
|
-
for (const standard of standards) byStandard[standard] = aggregatePartial(values.map((v) => v.byStandard?.[standard]), "present");
|
|
125
|
-
return {
|
|
126
|
-
overall: aggregatePartial(values.map((v) => v.overall)),
|
|
127
|
-
...Object.keys(byStandard).length < 1 ? {} : { byStandard }
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
function aggregateQueue(servers) {
|
|
131
|
-
const values = servers.map((s) => s.queue).filter((q) => q != null);
|
|
132
|
-
if (values.length !== servers.length) return null;
|
|
133
|
-
const drainMs = aggregatePartial(values.map((v) => v.drainMs));
|
|
134
|
-
const depths = values.map((v) => v.depthMax);
|
|
135
|
-
return {
|
|
136
|
-
...hasPartial(drainMs) ? { drainMs } : {},
|
|
137
|
-
...depths.every(isNumber) ? { depthMax: Math.max(...depths) } : {}
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
function aggregatePartial(values, mode = "complete") {
|
|
141
|
-
return {
|
|
142
|
-
...partialField(values, "p50", mode),
|
|
143
|
-
...partialField(values, "p95", mode),
|
|
144
|
-
...partialField(values, "p99", mode)
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
function partialField(values, key, mode) {
|
|
148
|
-
const fieldValues = values.map((v) => v?.[key]);
|
|
149
|
-
if (mode === "present") {
|
|
150
|
-
const present = fieldValues.filter(isNumber);
|
|
151
|
-
return present.length < 1 ? {} : { [key]: median(present) };
|
|
152
|
-
}
|
|
153
|
-
return fieldValues.every(isNumber) ? { [key]: median(fieldValues) } : {};
|
|
154
|
-
}
|
|
155
|
-
function hasPartial(value) {
|
|
156
|
-
return value.p50 != null || value.p95 != null || value.p99 != null;
|
|
157
|
-
}
|
|
158
|
-
function sumErrorBuckets(errors) {
|
|
159
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
160
|
-
for (const error of errors) {
|
|
161
|
-
const key = `${error.kind}|${error.status ?? ""}|${error.reason}`;
|
|
162
|
-
const previous = buckets.get(key);
|
|
163
|
-
buckets.set(key, {
|
|
164
|
-
...error,
|
|
165
|
-
count: (previous?.count ?? 0) + error.count
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
return [...buckets.values()].sort((a, b) => b.count - a.count);
|
|
169
|
-
}
|
|
170
|
-
function medianPresent(values) {
|
|
171
|
-
const present = values.filter(isNumber);
|
|
172
|
-
return present.length < 1 ? null : median(present);
|
|
173
|
-
}
|
|
174
|
-
function median(values) {
|
|
175
|
-
if (values.length < 1) return 0;
|
|
176
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
177
|
-
const middle = Math.floor(sorted.length / 2);
|
|
178
|
-
if (sorted.length % 2 === 1) return sorted[middle];
|
|
179
|
-
return (sorted[middle - 1] + sorted[middle]) / 2;
|
|
180
|
-
}
|
|
181
|
-
function sum(values) {
|
|
182
|
-
return values.reduce((a, b) => a + b, 0);
|
|
183
|
-
}
|
|
184
|
-
function isNumber(value) {
|
|
185
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
186
|
-
}
|
|
187
64
|
/** Detects the current runtime environment for reproducibility metadata. */
|
|
188
65
|
function detectEnvironment() {
|
|
189
66
|
const g = globalThis;
|
|
@@ -78,7 +78,7 @@ function lookupValue(metrics, metric) {
|
|
|
78
78
|
switch (metric) {
|
|
79
79
|
case "successRate": return metrics.requests.successRate;
|
|
80
80
|
case "throughputPerSec": return metrics.throughputPerSec;
|
|
81
|
-
case "deliveryThroughput": return
|
|
81
|
+
case "deliveryThroughput": return null;
|
|
82
82
|
case "errors.total": return sumErrors(metrics.errors);
|
|
83
83
|
case "errors.4xx": return sumErrors(metrics.errors, {
|
|
84
84
|
min: 400,
|
|
@@ -4,362 +4,12 @@ import "@js-temporal/polyfill";
|
|
|
4
4
|
* The embedded JSON Schema (draft 2020-12) for benchmark report output.
|
|
5
5
|
*
|
|
6
6
|
* Like the scenario schema, this object is the runtime copy and is published,
|
|
7
|
-
* byte-for-byte, as
|
|
8
|
-
* sync. The matching TypeScript types live in {@link ./model.ts}.
|
|
7
|
+
* byte-for-byte, as *schema/bench/report-v1.json*; a drift guard keeps the two
|
|
8
|
+
* in sync. The matching TypeScript types live in {@link ./model.ts}.
|
|
9
9
|
* @since 2.3.0
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
12
|
/** The hosted URL that serves the report schema. */
|
|
13
|
-
const REPORT_SCHEMA_ID = "https://json-schema.fedify.dev/bench/report-
|
|
14
|
-
/** The hosted URL for the version 2 report schema. */
|
|
15
|
-
const REPORT_SCHEMA_V2_ID = "https://json-schema.fedify.dev/bench/report-v2.json";
|
|
16
|
-
/** The benchmark report JSON Schema (draft 2020-12). */
|
|
17
|
-
const reportSchemaV1 = {
|
|
18
|
-
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
19
|
-
$id: "https://json-schema.fedify.dev/bench/report-v1.json",
|
|
20
|
-
title: "Fedify benchmark report",
|
|
21
|
-
type: "object",
|
|
22
|
-
additionalProperties: false,
|
|
23
|
-
required: [
|
|
24
|
-
"schemaVersion",
|
|
25
|
-
"tool",
|
|
26
|
-
"environment",
|
|
27
|
-
"target",
|
|
28
|
-
"startedAt",
|
|
29
|
-
"finishedAt",
|
|
30
|
-
"suite",
|
|
31
|
-
"passed",
|
|
32
|
-
"scenarios"
|
|
33
|
-
],
|
|
34
|
-
properties: {
|
|
35
|
-
$schema: { type: "string" },
|
|
36
|
-
schemaVersion: { const: 1 },
|
|
37
|
-
tool: {
|
|
38
|
-
type: "object",
|
|
39
|
-
additionalProperties: false,
|
|
40
|
-
required: ["name", "version"],
|
|
41
|
-
properties: {
|
|
42
|
-
name: { type: "string" },
|
|
43
|
-
version: { type: "string" }
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
environment: {
|
|
47
|
-
type: "object",
|
|
48
|
-
additionalProperties: false,
|
|
49
|
-
required: [
|
|
50
|
-
"runtime",
|
|
51
|
-
"runtimeVersion",
|
|
52
|
-
"os",
|
|
53
|
-
"cpuCount"
|
|
54
|
-
],
|
|
55
|
-
properties: {
|
|
56
|
-
runtime: { type: "string" },
|
|
57
|
-
runtimeVersion: { type: "string" },
|
|
58
|
-
os: { type: "string" },
|
|
59
|
-
cpuCount: {
|
|
60
|
-
type: "integer",
|
|
61
|
-
minimum: 0
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
target: {
|
|
66
|
-
type: "object",
|
|
67
|
-
additionalProperties: false,
|
|
68
|
-
required: ["url", "statsAvailable"],
|
|
69
|
-
properties: {
|
|
70
|
-
url: { type: "string" },
|
|
71
|
-
fedifyVersion: { type: ["string", "null"] },
|
|
72
|
-
statsAvailable: { type: "boolean" }
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
startedAt: { type: "string" },
|
|
76
|
-
finishedAt: { type: "string" },
|
|
77
|
-
suite: {
|
|
78
|
-
type: "object",
|
|
79
|
-
additionalProperties: false,
|
|
80
|
-
required: ["configHash"],
|
|
81
|
-
properties: {
|
|
82
|
-
name: { type: "string" },
|
|
83
|
-
configHash: { type: "string" }
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
passed: { type: "boolean" },
|
|
87
|
-
scenarios: {
|
|
88
|
-
type: "array",
|
|
89
|
-
items: { $ref: "#/$defs/scenarioResult" }
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
$defs: {
|
|
93
|
-
latencyMs: {
|
|
94
|
-
type: "object",
|
|
95
|
-
additionalProperties: false,
|
|
96
|
-
required: [
|
|
97
|
-
"p50",
|
|
98
|
-
"p95",
|
|
99
|
-
"p99",
|
|
100
|
-
"mean",
|
|
101
|
-
"max"
|
|
102
|
-
],
|
|
103
|
-
properties: {
|
|
104
|
-
p50: { type: "number" },
|
|
105
|
-
p95: { type: "number" },
|
|
106
|
-
p99: { type: "number" },
|
|
107
|
-
mean: { type: "number" },
|
|
108
|
-
max: { type: "number" }
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
partialLatencyMs: {
|
|
112
|
-
type: "object",
|
|
113
|
-
additionalProperties: false,
|
|
114
|
-
properties: {
|
|
115
|
-
p50: { type: "number" },
|
|
116
|
-
p95: { type: "number" },
|
|
117
|
-
p99: { type: "number" }
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
loadSummary: {
|
|
121
|
-
type: "object",
|
|
122
|
-
additionalProperties: false,
|
|
123
|
-
required: [
|
|
124
|
-
"model",
|
|
125
|
-
"durationMs",
|
|
126
|
-
"warmupMs"
|
|
127
|
-
],
|
|
128
|
-
properties: {
|
|
129
|
-
model: { enum: ["open", "closed"] },
|
|
130
|
-
ratePerSec: { type: "number" },
|
|
131
|
-
arrival: { type: "string" },
|
|
132
|
-
concurrency: { type: "integer" },
|
|
133
|
-
durationMs: { type: "number" },
|
|
134
|
-
warmupMs: { type: "number" },
|
|
135
|
-
maxInFlight: { type: "integer" }
|
|
136
|
-
},
|
|
137
|
-
oneOf: [{
|
|
138
|
-
properties: { model: { const: "open" } },
|
|
139
|
-
required: ["ratePerSec", "arrival"],
|
|
140
|
-
not: { required: ["concurrency"] }
|
|
141
|
-
}, {
|
|
142
|
-
properties: { model: { const: "closed" } },
|
|
143
|
-
required: ["concurrency"],
|
|
144
|
-
not: { anyOf: [{ required: ["ratePerSec"] }, { required: ["arrival"] }] }
|
|
145
|
-
}]
|
|
146
|
-
},
|
|
147
|
-
requestSummary: {
|
|
148
|
-
type: "object",
|
|
149
|
-
additionalProperties: false,
|
|
150
|
-
required: [
|
|
151
|
-
"total",
|
|
152
|
-
"ok",
|
|
153
|
-
"failed",
|
|
154
|
-
"successRate"
|
|
155
|
-
],
|
|
156
|
-
properties: {
|
|
157
|
-
total: {
|
|
158
|
-
type: "integer",
|
|
159
|
-
minimum: 0
|
|
160
|
-
},
|
|
161
|
-
ok: {
|
|
162
|
-
type: "integer",
|
|
163
|
-
minimum: 0
|
|
164
|
-
},
|
|
165
|
-
failed: {
|
|
166
|
-
type: "integer",
|
|
167
|
-
minimum: 0
|
|
168
|
-
},
|
|
169
|
-
successRate: {
|
|
170
|
-
type: "number",
|
|
171
|
-
minimum: 0,
|
|
172
|
-
maximum: 1
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
clientMetrics: {
|
|
177
|
-
type: "object",
|
|
178
|
-
additionalProperties: false,
|
|
179
|
-
required: ["latencyMs"],
|
|
180
|
-
properties: { latencyMs: { $ref: "#/$defs/latencyMs" } }
|
|
181
|
-
},
|
|
182
|
-
serverMetrics: {
|
|
183
|
-
type: "object",
|
|
184
|
-
additionalProperties: false,
|
|
185
|
-
properties: {
|
|
186
|
-
signatureVerificationMs: {
|
|
187
|
-
type: "object",
|
|
188
|
-
additionalProperties: false,
|
|
189
|
-
required: ["overall"],
|
|
190
|
-
properties: {
|
|
191
|
-
overall: { $ref: "#/$defs/partialLatencyMs" },
|
|
192
|
-
byStandard: {
|
|
193
|
-
type: "object",
|
|
194
|
-
additionalProperties: { $ref: "#/$defs/partialLatencyMs" }
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
queue: {
|
|
199
|
-
type: "object",
|
|
200
|
-
additionalProperties: false,
|
|
201
|
-
properties: {
|
|
202
|
-
drainMs: { $ref: "#/$defs/partialLatencyMs" },
|
|
203
|
-
depthMax: { type: "number" }
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
errorBucket: {
|
|
209
|
-
type: "object",
|
|
210
|
-
additionalProperties: false,
|
|
211
|
-
required: [
|
|
212
|
-
"kind",
|
|
213
|
-
"reason",
|
|
214
|
-
"count"
|
|
215
|
-
],
|
|
216
|
-
properties: {
|
|
217
|
-
kind: { type: "string" },
|
|
218
|
-
status: { type: "integer" },
|
|
219
|
-
reason: { type: "string" },
|
|
220
|
-
count: {
|
|
221
|
-
type: "integer",
|
|
222
|
-
minimum: 0
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
expectResult: {
|
|
227
|
-
type: "object",
|
|
228
|
-
additionalProperties: false,
|
|
229
|
-
required: [
|
|
230
|
-
"metric",
|
|
231
|
-
"op",
|
|
232
|
-
"threshold",
|
|
233
|
-
"unit",
|
|
234
|
-
"actual",
|
|
235
|
-
"severity",
|
|
236
|
-
"pass"
|
|
237
|
-
],
|
|
238
|
-
properties: {
|
|
239
|
-
metric: { type: "string" },
|
|
240
|
-
op: { enum: [
|
|
241
|
-
"lt",
|
|
242
|
-
"lte",
|
|
243
|
-
"gt",
|
|
244
|
-
"gte",
|
|
245
|
-
"eq"
|
|
246
|
-
] },
|
|
247
|
-
threshold: { type: "number" },
|
|
248
|
-
unit: { type: ["string", "null"] },
|
|
249
|
-
actual: { type: ["number", "null"] },
|
|
250
|
-
severity: { enum: ["warn", "fail"] },
|
|
251
|
-
pass: { type: "boolean" }
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
|
-
scenarioResult: {
|
|
255
|
-
type: "object",
|
|
256
|
-
additionalProperties: false,
|
|
257
|
-
required: [
|
|
258
|
-
"name",
|
|
259
|
-
"type",
|
|
260
|
-
"load",
|
|
261
|
-
"requests",
|
|
262
|
-
"throughputPerSec",
|
|
263
|
-
"client",
|
|
264
|
-
"server",
|
|
265
|
-
"errors",
|
|
266
|
-
"expectations",
|
|
267
|
-
"passed"
|
|
268
|
-
],
|
|
269
|
-
properties: {
|
|
270
|
-
name: { type: "string" },
|
|
271
|
-
type: { enum: [
|
|
272
|
-
"inbox",
|
|
273
|
-
"webfinger",
|
|
274
|
-
"actor",
|
|
275
|
-
"object",
|
|
276
|
-
"fanout",
|
|
277
|
-
"collection",
|
|
278
|
-
"failure",
|
|
279
|
-
"mixed"
|
|
280
|
-
] },
|
|
281
|
-
load: { $ref: "#/$defs/loadSummary" },
|
|
282
|
-
requests: { $ref: "#/$defs/requestSummary" },
|
|
283
|
-
throughputPerSec: { type: "number" },
|
|
284
|
-
client: { $ref: "#/$defs/clientMetrics" },
|
|
285
|
-
server: { anyOf: [{ $ref: "#/$defs/serverMetrics" }, { type: "null" }] },
|
|
286
|
-
errors: {
|
|
287
|
-
type: "array",
|
|
288
|
-
items: { $ref: "#/$defs/errorBucket" }
|
|
289
|
-
},
|
|
290
|
-
expectations: {
|
|
291
|
-
type: "array",
|
|
292
|
-
items: { $ref: "#/$defs/expectResult" }
|
|
293
|
-
},
|
|
294
|
-
passed: { type: "boolean" },
|
|
295
|
-
histogram: { $ref: "#/$defs/serializedHistogram" }
|
|
296
|
-
}
|
|
297
|
-
},
|
|
298
|
-
serializedHistogram: {
|
|
299
|
-
type: "object",
|
|
300
|
-
additionalProperties: false,
|
|
301
|
-
required: [
|
|
302
|
-
"version",
|
|
303
|
-
"subBucketCount",
|
|
304
|
-
"count",
|
|
305
|
-
"zeroCount",
|
|
306
|
-
"min",
|
|
307
|
-
"max",
|
|
308
|
-
"sum",
|
|
309
|
-
"indices",
|
|
310
|
-
"counts"
|
|
311
|
-
],
|
|
312
|
-
properties: {
|
|
313
|
-
version: { const: 1 },
|
|
314
|
-
subBucketCount: {
|
|
315
|
-
type: "integer",
|
|
316
|
-
minimum: 1
|
|
317
|
-
},
|
|
318
|
-
count: {
|
|
319
|
-
type: "integer",
|
|
320
|
-
minimum: 0
|
|
321
|
-
},
|
|
322
|
-
zeroCount: {
|
|
323
|
-
type: "integer",
|
|
324
|
-
minimum: 0
|
|
325
|
-
},
|
|
326
|
-
min: { type: "number" },
|
|
327
|
-
max: { type: "number" },
|
|
328
|
-
sum: { type: "number" },
|
|
329
|
-
indices: {
|
|
330
|
-
type: "array",
|
|
331
|
-
items: { type: "integer" }
|
|
332
|
-
},
|
|
333
|
-
counts: {
|
|
334
|
-
type: "array",
|
|
335
|
-
items: {
|
|
336
|
-
type: "integer",
|
|
337
|
-
minimum: 0
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
/** The benchmark report JSON Schema (draft 2020-12). */
|
|
345
|
-
const reportSchemaV2 = {
|
|
346
|
-
...reportSchemaV1,
|
|
347
|
-
$id: REPORT_SCHEMA_V2_ID,
|
|
348
|
-
properties: {
|
|
349
|
-
...reportSchemaV1.properties,
|
|
350
|
-
schemaVersion: { const: 2 }
|
|
351
|
-
},
|
|
352
|
-
$defs: {
|
|
353
|
-
...reportSchemaV1.$defs,
|
|
354
|
-
scenarioResult: {
|
|
355
|
-
...reportSchemaV1.$defs.scenarioResult,
|
|
356
|
-
properties: {
|
|
357
|
-
...reportSchemaV1.$defs.scenarioResult.properties,
|
|
358
|
-
deliveryThroughputPerSec: { type: "number" }
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
};
|
|
363
|
-
({ ...reportSchemaV2 }), { ...reportSchemaV2.properties }, { ...reportSchemaV2.$defs }, { ...reportSchemaV2.$defs.scenarioResult }, [...reportSchemaV2.$defs.scenarioResult.required], { ...reportSchemaV2.$defs.scenarioResult.properties };
|
|
13
|
+
const REPORT_SCHEMA_ID = "https://json-schema.fedify.dev/bench/report-v1.json";
|
|
364
14
|
//#endregion
|
|
365
15
|
export { REPORT_SCHEMA_ID };
|
|
@@ -20,9 +20,8 @@ function assertTargetAllowed(context) {
|
|
|
20
20
|
*
|
|
21
21
|
* The override is only meaningful for a public target that does not advertise
|
|
22
22
|
* benchmark mode. In that caution tier, the operator must name the target on
|
|
23
|
-
* the command line for this run and must explicitly set load
|
|
24
|
-
*
|
|
25
|
-
* benchmark.
|
|
23
|
+
* the command line for this run and must explicitly set load and duration, so
|
|
24
|
+
* the built-in defaults cannot accidentally create a long public benchmark.
|
|
26
25
|
* @param context The unsafe override decision inputs.
|
|
27
26
|
* @throws {UnsafeTargetError} If the unsafe override is too broad.
|
|
28
27
|
*/
|
|
@@ -32,7 +31,6 @@ function assertUnsafeOverrideAllowed(context) {
|
|
|
32
31
|
for (const scenario of context.scenarios) {
|
|
33
32
|
if (!scenario.explicitLoad) throw new UnsafeTargetError(`Scenario "${scenario.name}" uses the built-in benchmark load default. Set rate or concurrency explicitly before using --allow-unsafe-target against a public target.`);
|
|
34
33
|
if (!scenario.explicitDuration) throw new UnsafeTargetError(`Scenario "${scenario.name}" uses the built-in benchmark duration default. Set duration explicitly before using --allow-unsafe-target against a public target.`);
|
|
35
|
-
if (!scenario.explicitRuns) throw new UnsafeTargetError(`Scenario "${scenario.name}" uses the built-in benchmark runs default. Set runs explicitly before using --allow-unsafe-target against a public target.`);
|
|
36
34
|
}
|
|
37
35
|
}
|
|
38
36
|
/**
|
|
@@ -19,7 +19,7 @@ const DEFAULT_DURATION_MS = 6e4;
|
|
|
19
19
|
const DEFAULT_WARMUP_MS = 0;
|
|
20
20
|
const DEFAULT_RATE_PER_SEC = 50;
|
|
21
21
|
const DEFAULT_SIGNING = "pipeline";
|
|
22
|
-
const DEFAULT_RUNS =
|
|
22
|
+
const DEFAULT_RUNS = 1;
|
|
23
23
|
/** An error raised when a suite cannot be normalized. */
|
|
24
24
|
var SuiteNormalizeError = class extends Error {};
|
|
25
25
|
/**
|
|
@@ -57,6 +57,7 @@ function resolveScenario(scenario, suite) {
|
|
|
57
57
|
const warmupMs = resolveDuration(scenario.warmup ?? defaults.warmup, DEFAULT_WARMUP_MS);
|
|
58
58
|
if (warmupMs >= durationMs) throw new SuiteNormalizeError(`Scenario "${scenario.name}": warmup (${warmupMs}ms) must be shorter than duration (${durationMs}ms); otherwise no requests are measured.`);
|
|
59
59
|
const runs = scenario.runs ?? defaults.runs ?? DEFAULT_RUNS;
|
|
60
|
+
if (runs > 1) throw new SuiteNormalizeError(`Scenario "${scenario.name}": multiple runs (runs > 1) are not yet implemented in fedify bench; set runs to 1.`);
|
|
60
61
|
return {
|
|
61
62
|
name: scenario.name,
|
|
62
63
|
type: scenario.type,
|