@fedify/cli 2.3.0-dev.1299 → 2.3.0-dev.1344
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 +189 -29
- package/dist/bench/command.js +43 -13
- package/dist/bench/compare/schema.js +16 -0
- package/dist/bench/compare.js +667 -0
- package/dist/bench/load/clock.js +20 -2
- package/dist/bench/load/generator.js +42 -9
- package/dist/bench/metrics/stats-client.js +65 -3
- package/dist/bench/mod.js +9 -2
- package/dist/bench/render/markdown.js +1 -0
- package/dist/bench/render/text.js +1 -0
- package/dist/bench/result/build.js +133 -10
- package/dist/bench/result/expect/evaluate.js +1 -1
- package/dist/bench/result/schema.js +353 -3
- package/dist/bench/safety/gate.js +4 -2
- package/dist/bench/scenario/normalize.js +1 -2
- package/dist/bench/scenario/schema.js +50 -9
- package/dist/bench/scenario/validate.js +2 -2
- package/dist/bench/scenarios/actor.js +38 -0
- package/dist/bench/scenarios/failure.js +363 -0
- package/dist/bench/scenarios/fanout.js +261 -0
- package/dist/bench/scenarios/inbox.js +4 -12
- package/dist/bench/scenarios/mixed.js +244 -0
- package/dist/bench/scenarios/object-discovery.js +211 -0
- package/dist/bench/scenarios/object.js +54 -0
- package/dist/bench/scenarios/read.js +108 -0
- package/dist/bench/scenarios/registry.js +19 -1
- package/dist/bench/scenarios/runner.js +21 -1
- package/dist/bench/scenarios/webfinger.js +1 -1
- package/dist/cache.js +1 -1
- package/dist/commands.js +110 -0
- 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 +5 -3
- package/dist/imagerenderer.js +2 -2
- package/dist/inbox/command.js +6 -4
- package/dist/inbox.js +4 -4
- package/dist/log.js +2 -2
- package/dist/lookup/command.js +121 -0
- package/dist/lookup.js +12 -123
- package/dist/mod.js +2 -23
- package/dist/nodeinfo.js +11 -9
- package/dist/options.js +1 -1
- package/dist/relay/command.js +6 -4
- package/dist/relay.js +2 -2
- package/dist/runner.js +69 -46
- package/dist/tempserver.js +1 -1
- package/dist/tunnel.js +6 -4
- package/dist/utils.js +5 -4
- package/dist/webfinger/action.js +1 -1
- package/dist/webfinger/command.js +6 -4
- package/dist/webfinger/lib.js +1 -1
- package/package.json +13 -12
- package/dist/generate-vocab/mod.js +0 -4
- package/dist/init/mod.js +0 -3
- package/dist/webfinger/mod.js +0 -4
|
@@ -4,12 +4,362 @@ 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 *schema/bench
|
|
8
|
-
*
|
|
7
|
+
* byte-for-byte, as files under *schema/bench/*; a drift guard keeps the two in
|
|
8
|
+
* 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-
|
|
13
|
+
const REPORT_SCHEMA_ID = "https://json-schema.fedify.dev/bench/report-v3.json";
|
|
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 };
|
|
14
364
|
//#endregion
|
|
15
365
|
export { REPORT_SCHEMA_ID };
|
|
@@ -20,8 +20,9 @@ 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
|
-
* the built-in defaults cannot accidentally create a long public
|
|
23
|
+
* the command line for this run and must explicitly set load, duration, and
|
|
24
|
+
* runs, so the built-in defaults cannot accidentally create a long public
|
|
25
|
+
* benchmark.
|
|
25
26
|
* @param context The unsafe override decision inputs.
|
|
26
27
|
* @throws {UnsafeTargetError} If the unsafe override is too broad.
|
|
27
28
|
*/
|
|
@@ -31,6 +32,7 @@ function assertUnsafeOverrideAllowed(context) {
|
|
|
31
32
|
for (const scenario of context.scenarios) {
|
|
32
33
|
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.`);
|
|
33
34
|
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.`);
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
/**
|
|
@@ -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 = 3;
|
|
23
23
|
/** An error raised when a suite cannot be normalized. */
|
|
24
24
|
var SuiteNormalizeError = class extends Error {};
|
|
25
25
|
/**
|
|
@@ -57,7 +57,6 @@ 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.`);
|
|
61
60
|
return {
|
|
62
61
|
name: scenario.name,
|
|
63
62
|
type: scenario.type,
|
|
@@ -3,14 +3,14 @@ import "@js-temporal/polyfill";
|
|
|
3
3
|
/**
|
|
4
4
|
* The embedded JSON Schema (draft 2020-12) for benchmark scenario suite files.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* byte-for-byte,
|
|
8
|
-
*
|
|
6
|
+
* These objects are the runtime copies used by the validator; they are
|
|
7
|
+
* published, byte-for-byte, under *schema/bench/* and a drift guard keeps them
|
|
8
|
+
* in sync. The matching TypeScript types live in {@link ./types.ts}.
|
|
9
9
|
*
|
|
10
10
|
* The schema expresses every scenario type discussed for `fedify bench`
|
|
11
11
|
* (`inbox`, `webfinger`, `actor`, `object`, `fanout`, `collection`, `failure`,
|
|
12
|
-
* `mixed`)
|
|
13
|
-
*
|
|
12
|
+
* `mixed`). All but `collection` have runners in this version. Three
|
|
13
|
+
* cross-field rules are enforced here rather than in code:
|
|
14
14
|
*
|
|
15
15
|
* - exactly one HTTP request signature scheme per actor group
|
|
16
16
|
* (`contains` + `minContains`/`maxContains`);
|
|
@@ -20,8 +20,10 @@ import "@js-temporal/polyfill";
|
|
|
20
20
|
* @since 2.3.0
|
|
21
21
|
* @module
|
|
22
22
|
*/
|
|
23
|
-
/** The hosted URL that serves the scenario schema. */
|
|
24
|
-
const SCENARIO_SCHEMA_ID = "https://json-schema.fedify.dev/bench/scenario-
|
|
23
|
+
/** The hosted URL that serves the current scenario schema. */
|
|
24
|
+
const SCENARIO_SCHEMA_ID = "https://json-schema.fedify.dev/bench/scenario-v2.json";
|
|
25
|
+
/** The hosted URL that serves the version 1 scenario schema. */
|
|
26
|
+
const SCENARIO_SCHEMA_ID_V1 = "https://json-schema.fedify.dev/bench/scenario-v1.json";
|
|
25
27
|
const READ_METRICS = [
|
|
26
28
|
"successRate",
|
|
27
29
|
"throughputPerSec",
|
|
@@ -51,10 +53,11 @@ const FANOUT_METRICS = [
|
|
|
51
53
|
"queueDrain.p99"
|
|
52
54
|
];
|
|
53
55
|
const MIXED_METRICS = [...new Set([...INBOX_METRICS, ...FANOUT_METRICS])];
|
|
56
|
+
const MIXED_V2_METRICS = [...READ_METRICS, "deliveryThroughput"];
|
|
54
57
|
/** The benchmark scenario suite JSON Schema (draft 2020-12). */
|
|
55
58
|
const scenarioSchemaV1 = {
|
|
56
59
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
57
|
-
$id:
|
|
60
|
+
$id: SCENARIO_SCHEMA_ID_V1,
|
|
58
61
|
title: "Fedify benchmark scenario suite",
|
|
59
62
|
type: "object",
|
|
60
63
|
required: ["version", "scenarios"],
|
|
@@ -354,5 +357,43 @@ const scenarioSchemaV1 = {
|
|
|
354
357
|
}
|
|
355
358
|
}
|
|
356
359
|
};
|
|
360
|
+
/** The current benchmark scenario suite JSON Schema (draft 2020-12). */
|
|
361
|
+
const scenarioSchemaV2 = {
|
|
362
|
+
...scenarioSchemaV1,
|
|
363
|
+
$id: SCENARIO_SCHEMA_ID,
|
|
364
|
+
$defs: {
|
|
365
|
+
...scenarioSchemaV1.$defs,
|
|
366
|
+
scenario: {
|
|
367
|
+
...scenarioSchemaV1.$defs.scenario,
|
|
368
|
+
properties: {
|
|
369
|
+
...scenarioSchemaV1.$defs.scenario.properties,
|
|
370
|
+
sinkBase: { type: "string" }
|
|
371
|
+
},
|
|
372
|
+
allOf: scenarioSchemaV1.$defs.scenario.allOf.map((condition) => condition.if.properties.type.const === "failure" ? {
|
|
373
|
+
if: condition.if,
|
|
374
|
+
then: { properties: condition.then.properties }
|
|
375
|
+
} : condition.if.properties.type.const === "actor" || condition.if.properties.type.const === "object" ? {
|
|
376
|
+
if: condition.if,
|
|
377
|
+
then: {
|
|
378
|
+
required: condition.then.required,
|
|
379
|
+
allOf: [{
|
|
380
|
+
if: {
|
|
381
|
+
required: ["authenticated"],
|
|
382
|
+
properties: { authenticated: { const: true } }
|
|
383
|
+
},
|
|
384
|
+
then: { properties: { expect: { propertyNames: { enum: INBOX_METRICS } } } },
|
|
385
|
+
else: { properties: { expect: { propertyNames: { enum: READ_METRICS } } } }
|
|
386
|
+
}]
|
|
387
|
+
}
|
|
388
|
+
} : condition.if.properties.type.const === "mixed" ? {
|
|
389
|
+
if: condition.if,
|
|
390
|
+
then: {
|
|
391
|
+
required: condition.then.required,
|
|
392
|
+
properties: { expect: { propertyNames: { enum: MIXED_V2_METRICS } } }
|
|
393
|
+
}
|
|
394
|
+
} : condition)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
};
|
|
357
398
|
//#endregion
|
|
358
|
-
export {
|
|
399
|
+
export { scenarioSchemaV2 };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
|
-
import {
|
|
2
|
+
import { scenarioSchemaV2 } from "./schema.js";
|
|
3
3
|
import { SuiteValidationError } from "./errors.js";
|
|
4
4
|
import { Validator } from "@cfworker/json-schema";
|
|
5
5
|
//#region src/bench/scenario/validate.ts
|
|
@@ -10,7 +10,7 @@ import { Validator } from "@cfworker/json-schema";
|
|
|
10
10
|
*/
|
|
11
11
|
let validator;
|
|
12
12
|
function getValidator() {
|
|
13
|
-
validator ??= new Validator(
|
|
13
|
+
validator ??= new Validator(scenarioSchemaV2, "2020-12");
|
|
14
14
|
return validator;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import "@js-temporal/polyfill";
|
|
2
|
+
import { convertUrlIfHandle } from "../../webfinger/lib.js";
|
|
3
|
+
import { actorUrlsFromRecipients } from "./object-discovery.js";
|
|
4
|
+
import { isBareHttpUrl } from "./runner.js";
|
|
5
|
+
import { runReadLoad } from "./read.js";
|
|
6
|
+
//#region src/bench/scenarios/actor.ts
|
|
7
|
+
/**
|
|
8
|
+
* The `actor` scenario runner.
|
|
9
|
+
* @since 2.3.0
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
/** The `actor` scenario runner. */
|
|
13
|
+
const actorRunner = {
|
|
14
|
+
validate(scenario) {
|
|
15
|
+
if (scenario.recipients.length < 1) throw new Error("The actor scenario requires a recipient.");
|
|
16
|
+
for (const recipient of scenario.recipients) {
|
|
17
|
+
let url;
|
|
18
|
+
try {
|
|
19
|
+
url = convertUrlIfHandle(recipient);
|
|
20
|
+
} catch {
|
|
21
|
+
throw new Error(`Scenario "${scenario.name}": invalid actor recipient ${JSON.stringify(recipient)}.`);
|
|
22
|
+
}
|
|
23
|
+
if (url.protocol !== "acct:" && !isBareHttpUrl(url)) throw new Error(`Scenario "${scenario.name}": actor recipient must be an acct: handle or a bare http(s) URL with a host and no credentials; got ${JSON.stringify(url.href)}.`);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
async run(context) {
|
|
27
|
+
this.validate?.(context.scenario);
|
|
28
|
+
return await runReadLoad(context, {
|
|
29
|
+
urls: await actorUrlsFromRecipients(context.scenario.recipients, {
|
|
30
|
+
target: context.target,
|
|
31
|
+
fetch: context.fetch
|
|
32
|
+
}),
|
|
33
|
+
authenticated: context.scenario.authenticated
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { actorRunner };
|