@loadstrike/loadstrike-sdk 1.0.30201 → 1.0.30401
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 +24 -0
- package/dist/cjs/cluster.js +2417 -7
- package/dist/cjs/index.js +13 -2
- package/dist/cjs/iteration-observations.js +711 -0
- package/dist/cjs/load-engine-v2.js +966 -0
- package/dist/cjs/local.js +73 -20
- package/dist/cjs/reporting.js +122 -12
- package/dist/cjs/runtime.js +2368 -129
- package/dist/cjs/sinks.js +471 -17
- package/dist/cjs/transports.js +84 -146
- package/dist/esm/cluster.js +2386 -7
- package/dist/esm/index.js +1 -0
- package/dist/esm/iteration-observations.js +698 -0
- package/dist/esm/load-engine-v2.js +942 -0
- package/dist/esm/local.js +73 -20
- package/dist/esm/reporting.js +122 -12
- package/dist/esm/runtime.js +2369 -130
- package/dist/esm/sinks.js +471 -17
- package/dist/esm/transports.js +84 -146
- package/dist/types/cluster.d.ts +379 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/iteration-observations.d.ts +225 -0
- package/dist/types/load-engine-v2.d.ts +147 -0
- package/dist/types/runtime.d.ts +214 -8
- package/dist/types/sinks.d.ts +67 -0
- package/dist/types/transports.d.ts +2 -8
- package/package.json +3 -4
package/dist/cjs/runtime.js
CHANGED
|
@@ -14,6 +14,10 @@ const correlation_js_1 = require("./correlation.js");
|
|
|
14
14
|
const transports_js_1 = require("./transports.js");
|
|
15
15
|
const reporting_js_1 = require("./reporting.js");
|
|
16
16
|
const sinks_js_1 = require("./sinks.js");
|
|
17
|
+
const load_engine_v2_js_1 = require("./load-engine-v2.js");
|
|
18
|
+
const iteration_observations_js_1 = require("./iteration-observations.js");
|
|
19
|
+
const LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS = Symbol("loadstrike.load-engine-v2.scheduler-distributions");
|
|
20
|
+
const PORTAL_RUN_TOKEN_PROVIDER = Symbol.for("loadstrike.internal.portal-run-token-provider");
|
|
17
21
|
exports.LoadStrikeNodeType = {
|
|
18
22
|
SingleNode: "SingleNode",
|
|
19
23
|
Coordinator: "Coordinator",
|
|
@@ -147,12 +151,20 @@ class LoadStrikePluginData {
|
|
|
147
151
|
}
|
|
148
152
|
exports.LoadStrikePluginData = LoadStrikePluginData;
|
|
149
153
|
class MeasurementAccumulator {
|
|
150
|
-
constructor() {
|
|
154
|
+
constructor(useHistogram = false) {
|
|
155
|
+
this.useHistogram = useHistogram;
|
|
151
156
|
this.count = 0;
|
|
152
157
|
this.allBytes = 0;
|
|
153
158
|
this.latenciesMs = [];
|
|
154
159
|
this.sizesBytes = [];
|
|
160
|
+
this.latencyLessOrEq800 = 0;
|
|
161
|
+
this.latencyMore800Less1200 = 0;
|
|
162
|
+
this.latencyMoreOrEq1200 = 0;
|
|
155
163
|
this.statusCodes = new Map();
|
|
164
|
+
if (useHistogram) {
|
|
165
|
+
this.latencyHistogram = new load_engine_v2_js_1.LoadStrikeHistogramV1();
|
|
166
|
+
this.sizeHistogram = new load_engine_v2_js_1.LoadStrikeHistogramV1();
|
|
167
|
+
}
|
|
156
168
|
}
|
|
157
169
|
get Count() {
|
|
158
170
|
return this.count;
|
|
@@ -169,8 +181,20 @@ class MeasurementAccumulator {
|
|
|
169
181
|
const key = `${statusCode}|${message}|${reply.isSuccess ? "ok" : "fail"}`;
|
|
170
182
|
this.count += 1;
|
|
171
183
|
this.allBytes += sizeBytes;
|
|
172
|
-
this.
|
|
173
|
-
|
|
184
|
+
if (this.useHistogram) {
|
|
185
|
+
this.latencyHistogram.record(normalizeLatencyMicroseconds(latencyMs));
|
|
186
|
+
this.sizeHistogram.record(normalizeHistogramInteger(sizeBytes, "Response size"));
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
this.latenciesMs.push(latencyMs);
|
|
190
|
+
this.sizesBytes.push(sizeBytes);
|
|
191
|
+
}
|
|
192
|
+
if (latencyMs <= 800)
|
|
193
|
+
this.latencyLessOrEq800 += 1;
|
|
194
|
+
else if (latencyMs < 1200)
|
|
195
|
+
this.latencyMore800Less1200 += 1;
|
|
196
|
+
else
|
|
197
|
+
this.latencyMoreOrEq1200 += 1;
|
|
174
198
|
const existing = this.statusCodes.get(key);
|
|
175
199
|
if (existing) {
|
|
176
200
|
existing.count += 1;
|
|
@@ -188,6 +212,9 @@ class MeasurementAccumulator {
|
|
|
188
212
|
* Use this when all builder inputs are ready to be materialized.
|
|
189
213
|
*/
|
|
190
214
|
build(allRequestCount, durationMs) {
|
|
215
|
+
if (this.useHistogram) {
|
|
216
|
+
return buildHistogramMeasurement(this.histogramSnapshot(), allRequestCount, durationMs);
|
|
217
|
+
}
|
|
191
218
|
const count = this.count;
|
|
192
219
|
const totalDurationMs = Math.max(durationMs, 0);
|
|
193
220
|
const latencyValues = [...this.latenciesMs];
|
|
@@ -236,14 +263,274 @@ class MeasurementAccumulator {
|
|
|
236
263
|
statusCodes
|
|
237
264
|
};
|
|
238
265
|
}
|
|
266
|
+
buildCombined(other, allRequestCount, durationMs) {
|
|
267
|
+
if (!this.useHistogram || !other.useHistogram) {
|
|
268
|
+
throw new Error("Combined measurements require Load Engine V2 histograms.");
|
|
269
|
+
}
|
|
270
|
+
const left = this.histogramSnapshot();
|
|
271
|
+
const right = other.histogramSnapshot();
|
|
272
|
+
left.latency.merge(right.latency);
|
|
273
|
+
left.size.merge(right.size);
|
|
274
|
+
for (const [key, value] of right.statusCodes) {
|
|
275
|
+
const existing = left.statusCodes.get(key);
|
|
276
|
+
if (existing)
|
|
277
|
+
existing.count += value.count;
|
|
278
|
+
else
|
|
279
|
+
left.statusCodes.set(key, { ...value });
|
|
280
|
+
}
|
|
281
|
+
return buildHistogramMeasurement({
|
|
282
|
+
count: left.count + right.count,
|
|
283
|
+
allBytes: left.allBytes + right.allBytes,
|
|
284
|
+
latency: left.latency,
|
|
285
|
+
size: left.size,
|
|
286
|
+
statusCodes: left.statusCodes,
|
|
287
|
+
lessOrEq800: left.lessOrEq800 + right.lessOrEq800,
|
|
288
|
+
more800Less1200: left.more800Less1200 + right.more800Less1200,
|
|
289
|
+
moreOrEq1200: left.moreOrEq1200 + right.moreOrEq1200
|
|
290
|
+
}, allRequestCount, durationMs);
|
|
291
|
+
}
|
|
292
|
+
histogramSnapshot() {
|
|
293
|
+
return {
|
|
294
|
+
count: this.count,
|
|
295
|
+
allBytes: this.allBytes,
|
|
296
|
+
latency: this.latencyHistogram.clone(),
|
|
297
|
+
size: this.sizeHistogram.clone(),
|
|
298
|
+
statusCodes: new Map(Array.from(this.statusCodes, ([key, value]) => [key, { ...value }])),
|
|
299
|
+
lessOrEq800: this.latencyLessOrEq800,
|
|
300
|
+
more800Less1200: this.latencyMore800Less1200,
|
|
301
|
+
moreOrEq1200: this.latencyMoreOrEq1200
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function buildHistogramMeasurement(snapshot, allRequestCount, durationMs) {
|
|
306
|
+
const count = snapshot.count;
|
|
307
|
+
const totalDurationMs = Math.max(durationMs, 0);
|
|
308
|
+
const latency = snapshot.latency;
|
|
309
|
+
const size = snapshot.size;
|
|
310
|
+
return {
|
|
311
|
+
count64: latency.count.toString(),
|
|
312
|
+
distributionMode: latency.mode === "quantized-v1" || size.mode === "quantized-v1"
|
|
313
|
+
? "quantized-v1"
|
|
314
|
+
: "exact-normalized",
|
|
315
|
+
maxRelativeError: Math.max(latency.maxRelativeError, size.maxRelativeError),
|
|
316
|
+
histogramSidecar: {
|
|
317
|
+
latency: latency.toSidecar(),
|
|
318
|
+
size: size.toSidecar(),
|
|
319
|
+
allBytes64: size.exactTotal.toString(),
|
|
320
|
+
lessOrEq80064: snapshot.lessOrEq800.toString(),
|
|
321
|
+
more800Less120064: snapshot.more800Less1200.toString(),
|
|
322
|
+
moreOrEq120064: snapshot.moreOrEq1200.toString()
|
|
323
|
+
},
|
|
324
|
+
request: {
|
|
325
|
+
count,
|
|
326
|
+
percent: allRequestCount <= 0 ? 0 : Math.round((100 * count) / allRequestCount),
|
|
327
|
+
rps: totalDurationMs <= 0 ? 0 : count / (totalDurationMs / 1000)
|
|
328
|
+
},
|
|
329
|
+
dataTransfer: {
|
|
330
|
+
allBytes: snapshot.allBytes,
|
|
331
|
+
allBytes64: size.exactTotal.toString(),
|
|
332
|
+
minBytes: Number(size.minimum),
|
|
333
|
+
maxBytes: Number(size.maximum),
|
|
334
|
+
meanBytes: Math.round(size.mean),
|
|
335
|
+
percent50: Number(size.percentile(0.5)),
|
|
336
|
+
percent75: Number(size.percentile(0.75)),
|
|
337
|
+
percent95: Number(size.percentile(0.95)),
|
|
338
|
+
percent99: Number(size.percentile(0.99)),
|
|
339
|
+
percent100: Number(size.percentile(1)),
|
|
340
|
+
stdDev: size.populationStandardDeviation
|
|
341
|
+
},
|
|
342
|
+
latency: {
|
|
343
|
+
latencyCount: {
|
|
344
|
+
lessOrEq800: snapshot.lessOrEq800,
|
|
345
|
+
more800Less1200: snapshot.more800Less1200,
|
|
346
|
+
moreOrEq1200: snapshot.moreOrEq1200
|
|
347
|
+
},
|
|
348
|
+
minMs: Number(latency.minimum) / 1000,
|
|
349
|
+
maxMs: Number(latency.maximum) / 1000,
|
|
350
|
+
meanMs: latency.mean / 1000,
|
|
351
|
+
percent50: Number(latency.percentile(0.5)) / 1000,
|
|
352
|
+
percent75: Number(latency.percentile(0.75)) / 1000,
|
|
353
|
+
percent95: Number(latency.percentile(0.95)) / 1000,
|
|
354
|
+
percent99: Number(latency.percentile(0.99)) / 1000,
|
|
355
|
+
percent100: Number(latency.percentile(1)) / 1000,
|
|
356
|
+
stdDev: latency.populationStandardDeviation / 1000
|
|
357
|
+
},
|
|
358
|
+
statusCodes: Array.from(snapshot.statusCodes.values())
|
|
359
|
+
.sort((left, right) => right.count - left.count)
|
|
360
|
+
.map((value) => ({
|
|
361
|
+
count: value.count,
|
|
362
|
+
isError: value.isError,
|
|
363
|
+
message: value.message,
|
|
364
|
+
percent: count <= 0 ? 0 : Math.round((100 * value.count) / count),
|
|
365
|
+
statusCode: value.statusCode
|
|
366
|
+
}))
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function normalizeLatencyMicroseconds(latencyMs) {
|
|
370
|
+
return normalizeHistogramInteger(Math.max(latencyMs, 0) * 1000, "Latency");
|
|
371
|
+
}
|
|
372
|
+
function normalizeRawObservationLatencyMicroseconds(latencyMs) {
|
|
373
|
+
const maximum = 9223372036854775807n;
|
|
374
|
+
if (!Number.isFinite(latencyMs) || latencyMs <= 0) {
|
|
375
|
+
return 0n;
|
|
376
|
+
}
|
|
377
|
+
const microseconds = latencyMs * 1000;
|
|
378
|
+
if (!Number.isFinite(microseconds) || microseconds >= Number(maximum)) {
|
|
379
|
+
return maximum;
|
|
380
|
+
}
|
|
381
|
+
return BigInt(Math.max(0, Math.round(microseconds)));
|
|
382
|
+
}
|
|
383
|
+
function normalizeHistogramInteger(value, name) {
|
|
384
|
+
if (!Number.isFinite(value) || value > Number(9223372036854775807n)) {
|
|
385
|
+
throw new RangeError(`${name} is outside the supported histogram range.`);
|
|
386
|
+
}
|
|
387
|
+
return BigInt(Math.max(0, Math.round(value)));
|
|
388
|
+
}
|
|
389
|
+
class LoadEngineV2Telemetry {
|
|
390
|
+
constructor(budget) {
|
|
391
|
+
this.budget = budget;
|
|
392
|
+
this.mutableSegments = [];
|
|
393
|
+
this.warnings = new Map();
|
|
394
|
+
}
|
|
395
|
+
createSegment(scenarioName, scenarioIndex, simulationIndex, kind, shardIndex, shardCount) {
|
|
396
|
+
const segment = {
|
|
397
|
+
scenarioName,
|
|
398
|
+
scenarioIndex,
|
|
399
|
+
simulationIndex,
|
|
400
|
+
kind,
|
|
401
|
+
shardIndex,
|
|
402
|
+
shardCount,
|
|
403
|
+
planned: 0n,
|
|
404
|
+
due: 0n,
|
|
405
|
+
started: 0n,
|
|
406
|
+
completed: 0n,
|
|
407
|
+
dropped: 0n,
|
|
408
|
+
unreached: 0n,
|
|
409
|
+
requestedWorkers: 0n,
|
|
410
|
+
startedWorkers: 0n,
|
|
411
|
+
unavailableWorkers: 0n,
|
|
412
|
+
dropReasons: new Map(),
|
|
413
|
+
unavailableWorkerReasons: new Map(),
|
|
414
|
+
decisionLag: new load_engine_v2_js_1.LoadStrikeHistogramV1(),
|
|
415
|
+
startLag: new load_engine_v2_js_1.LoadStrikeHistogramV1(),
|
|
416
|
+
accountingComplete: false
|
|
417
|
+
};
|
|
418
|
+
this.mutableSegments.push(segment);
|
|
419
|
+
return segment;
|
|
420
|
+
}
|
|
421
|
+
recordWarning(code, segment, count) {
|
|
422
|
+
if (count <= 0n)
|
|
423
|
+
return;
|
|
424
|
+
const key = `${code}\n${segment.scenarioIndex}\n${segment.simulationIndex}`;
|
|
425
|
+
const nowNs = BigInt(Date.now()) * 1000000n;
|
|
426
|
+
const existing = this.warnings.get(key);
|
|
427
|
+
if (existing) {
|
|
428
|
+
existing.count += count;
|
|
429
|
+
existing.lastObservedUtcNs = nowNs;
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
this.warnings.set(key, {
|
|
433
|
+
code,
|
|
434
|
+
scenarioName: segment.scenarioName,
|
|
435
|
+
scenarioIndex: segment.scenarioIndex,
|
|
436
|
+
simulationIndex: segment.simulationIndex,
|
|
437
|
+
simulationKind: segment.kind,
|
|
438
|
+
count,
|
|
439
|
+
firstObservedUtcNs: nowNs,
|
|
440
|
+
lastObservedUtcNs: nowNs
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
recordDecisionLag(segment, lagNs) {
|
|
445
|
+
segment.decisionLag.record(maxBigInt(lagNs, 0n) / 1000n);
|
|
446
|
+
}
|
|
447
|
+
recordStartLag(segment, lagNs) {
|
|
448
|
+
segment.startLag.record(maxBigInt(lagNs, 0n) / 1000n);
|
|
449
|
+
}
|
|
450
|
+
buildSchedulerDistributions() {
|
|
451
|
+
return this.mutableSegments.flatMap((segment) => {
|
|
452
|
+
const scenarioIndex64 = segment.scenarioIndex.toString();
|
|
453
|
+
const simulationIndex64 = segment.simulationIndex.toString();
|
|
454
|
+
return [
|
|
455
|
+
["scheduler-decision-lag", "decision", segment.decisionLag],
|
|
456
|
+
["scheduler-start-lag", "start", segment.startLag]
|
|
457
|
+
].map(([seriesKind, identityKind, histogram]) => ({
|
|
458
|
+
seriesKind,
|
|
459
|
+
scenarioIndex64,
|
|
460
|
+
scenarioName: segment.scenarioName,
|
|
461
|
+
identityKeyHex: (0, cluster_js_1.buildLoadEngineV2SchedulerIdentityKey)(identityKind, scenarioIndex64, simulationIndex64).toString("hex"),
|
|
462
|
+
outcome: "none",
|
|
463
|
+
unit: "microseconds",
|
|
464
|
+
histogram: histogram.toSidecar(),
|
|
465
|
+
exactTotalDecimalOrEmpty: histogram.toSidecar().exactTotal64
|
|
466
|
+
}));
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
buildWarnings() {
|
|
470
|
+
return Array.from(this.warnings.values())
|
|
471
|
+
.sort((left, right) => left.code.localeCompare(right.code)
|
|
472
|
+
|| left.scenarioName.localeCompare(right.scenarioName)
|
|
473
|
+
|| left.simulationIndex - right.simulationIndex)
|
|
474
|
+
.map((value) => ({
|
|
475
|
+
code: value.code,
|
|
476
|
+
scenarioName: value.scenarioName,
|
|
477
|
+
scenarioIndex: value.scenarioIndex,
|
|
478
|
+
simulationIndex: value.simulationIndex,
|
|
479
|
+
simulationKind: value.simulationKind,
|
|
480
|
+
count64: value.count.toString(),
|
|
481
|
+
message: value.code,
|
|
482
|
+
firstObservedUtcNs: value.firstObservedUtcNs.toString(),
|
|
483
|
+
lastObservedUtcNs: value.lastObservedUtcNs.toString()
|
|
484
|
+
}));
|
|
485
|
+
}
|
|
486
|
+
buildSegments() {
|
|
487
|
+
return this.mutableSegments.map((segment) => ({
|
|
488
|
+
scenarioName: segment.scenarioName,
|
|
489
|
+
scenarioIndex: segment.scenarioIndex,
|
|
490
|
+
simulationIndex: segment.simulationIndex,
|
|
491
|
+
kind: segment.kind,
|
|
492
|
+
shardIndex: segment.shardIndex,
|
|
493
|
+
shardCount: segment.shardCount,
|
|
494
|
+
plannedIterations64: segment.planned.toString(),
|
|
495
|
+
dueIterations64: segment.due.toString(),
|
|
496
|
+
startedIterations64: segment.started.toString(),
|
|
497
|
+
completedIterations64: segment.completed.toString(),
|
|
498
|
+
droppedIterations64: segment.dropped.toString(),
|
|
499
|
+
unreachedIterations64: segment.unreached.toString(),
|
|
500
|
+
requestedWorkerSlots64: segment.requestedWorkers.toString(),
|
|
501
|
+
startedWorkerSlots64: segment.startedWorkers.toString(),
|
|
502
|
+
unavailableWorkerSlots64: segment.unavailableWorkers.toString(),
|
|
503
|
+
dropReasons: Object.fromEntries(Array.from(segment.dropReasons, ([key, count]) => [key, count.toString()])),
|
|
504
|
+
unavailableWorkerReasons: Object.fromEntries(Array.from(segment.unavailableWorkerReasons, ([key, count]) => [key, count.toString()])),
|
|
505
|
+
deliveryPercent: segment.due === 0n ? 100 : Number(segment.started * 10000n / segment.due) / 100,
|
|
506
|
+
accountingComplete: segment.accountingComplete
|
|
507
|
+
}));
|
|
508
|
+
}
|
|
509
|
+
buildStats() {
|
|
510
|
+
return {
|
|
511
|
+
configuredMaxInFlight: this.budget.maxInFlight,
|
|
512
|
+
maxInFlightObserved: this.budget.highWater,
|
|
513
|
+
currentInFlight: this.budget.current,
|
|
514
|
+
segments: this.buildSegments()
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function incrementReason(reasons, code, count = 1n) {
|
|
519
|
+
reasons.set(code, (reasons.get(code) ?? 0n) + count);
|
|
520
|
+
}
|
|
521
|
+
function ownedV2OrdinalCount(total, shardIndex, shardCount) {
|
|
522
|
+
if (total <= BigInt(shardIndex))
|
|
523
|
+
return 0n;
|
|
524
|
+
return (total - 1n - BigInt(shardIndex)) / BigInt(shardCount) + 1n;
|
|
239
525
|
}
|
|
240
526
|
class StepStatsAccumulator {
|
|
241
|
-
constructor(scenarioName, stepName, sortIndex) {
|
|
527
|
+
constructor(scenarioName, stepName, sortIndex, useHistogram = false) {
|
|
242
528
|
this.scenarioName = scenarioName;
|
|
243
529
|
this.stepName = stepName;
|
|
244
530
|
this.sortIndex = sortIndex;
|
|
245
|
-
this.
|
|
246
|
-
this.
|
|
531
|
+
this.useHistogram = useHistogram;
|
|
532
|
+
this.ok = new MeasurementAccumulator(useHistogram);
|
|
533
|
+
this.fail = new MeasurementAccumulator(useHistogram);
|
|
247
534
|
}
|
|
248
535
|
/**
|
|
249
536
|
* Exposes the public record operation.
|
|
@@ -282,6 +569,7 @@ class StepStatsAccumulator {
|
|
|
282
569
|
minLatencyMs: minCandidates.length ? Math.min(...minCandidates) : 0,
|
|
283
570
|
maxLatencyMs: maxCandidates.length ? Math.max(...maxCandidates) : 0,
|
|
284
571
|
statusCodes,
|
|
572
|
+
allMeasurement: this.useHistogram ? this.ok.buildCombined(this.fail, requestCount, durationMs) : undefined,
|
|
285
573
|
ok,
|
|
286
574
|
fail,
|
|
287
575
|
sortIndex: this.sortIndex
|
|
@@ -289,15 +577,16 @@ class StepStatsAccumulator {
|
|
|
289
577
|
}
|
|
290
578
|
}
|
|
291
579
|
class ScenarioStatsAccumulator {
|
|
292
|
-
constructor(scenarioName, sortIndex) {
|
|
580
|
+
constructor(scenarioName, sortIndex, useHistogram = false) {
|
|
293
581
|
this.scenarioName = scenarioName;
|
|
294
582
|
this.sortIndex = sortIndex;
|
|
295
|
-
this.
|
|
296
|
-
this.fail = new MeasurementAccumulator();
|
|
583
|
+
this.useHistogram = useHistogram;
|
|
297
584
|
this.steps = new Map();
|
|
298
585
|
this.nextStepSortIndex = 0;
|
|
299
586
|
this.loadSimulationStats = { simulationName: "", value: 0 };
|
|
300
587
|
this.currentOperation = "None";
|
|
588
|
+
this.ok = new MeasurementAccumulator(useHistogram);
|
|
589
|
+
this.fail = new MeasurementAccumulator(useHistogram);
|
|
301
590
|
}
|
|
302
591
|
/**
|
|
303
592
|
* Exposes the public setLoadSimulation operation.
|
|
@@ -338,13 +627,18 @@ class ScenarioStatsAccumulator {
|
|
|
338
627
|
* Exposes the public recordStep operation.
|
|
339
628
|
* Use this when the surrounding wrapper type makes this operation the clearest way to express your intent.
|
|
340
629
|
*/
|
|
341
|
-
recordStep(stepName, reply, observedLatencyMs) {
|
|
630
|
+
recordStep(stepName, reply, observedLatencyMs, sortIndex) {
|
|
342
631
|
const existing = this.steps.get(stepName);
|
|
343
|
-
const
|
|
632
|
+
const resolvedSortIndex = sortIndex === undefined
|
|
633
|
+
? this.nextStepSortIndex + 1
|
|
634
|
+
: Math.max(Math.trunc(sortIndex), 0);
|
|
635
|
+
const step = existing ?? new StepStatsAccumulator(this.scenarioName, stepName, resolvedSortIndex, this.useHistogram);
|
|
344
636
|
if (!existing) {
|
|
637
|
+
this.nextStepSortIndex = Math.max(this.nextStepSortIndex, resolvedSortIndex);
|
|
345
638
|
this.steps.set(stepName, step);
|
|
346
639
|
}
|
|
347
640
|
step.record(reply, observedLatencyMs);
|
|
641
|
+
return step.sortIndex;
|
|
348
642
|
}
|
|
349
643
|
/**
|
|
350
644
|
* Builds the configured payload or helper object.
|
|
@@ -373,6 +667,7 @@ class ScenarioStatsAccumulator {
|
|
|
373
667
|
minLatencyMs: minCandidates.length ? Math.min(...minCandidates) : 0,
|
|
374
668
|
maxLatencyMs: maxCandidates.length ? Math.max(...maxCandidates) : 0,
|
|
375
669
|
statusCodes,
|
|
670
|
+
allMeasurement: this.useHistogram ? this.ok.buildCombined(this.fail, totalRequests, durationMs) : undefined,
|
|
376
671
|
allBytes,
|
|
377
672
|
currentOperation: this.currentOperation,
|
|
378
673
|
durationMs: Math.max(durationMs, 0),
|
|
@@ -468,7 +763,8 @@ class LoadStrikeStep {
|
|
|
468
763
|
const internal = context;
|
|
469
764
|
await internal.invokeBeforeStep(stepName);
|
|
470
765
|
let reply;
|
|
471
|
-
const
|
|
766
|
+
const startedUtcNs = (0, iteration_observations_js_1.utcNowNs)();
|
|
767
|
+
const startedAtNs = process.hrtime.bigint();
|
|
472
768
|
try {
|
|
473
769
|
reply = normalizeReply(await run());
|
|
474
770
|
}
|
|
@@ -476,8 +772,21 @@ class LoadStrikeStep {
|
|
|
476
772
|
reply = LoadStrikeResponse.fail("step_exception", resolveRuntimeErrorMessage(error, "step failed"), 0);
|
|
477
773
|
}
|
|
478
774
|
reply = attachReplyProjection(reply);
|
|
479
|
-
const
|
|
480
|
-
|
|
775
|
+
const observedLatencyNs = maxBigInt(process.hrtime.bigint() - startedAtNs, 0n);
|
|
776
|
+
const completedUtcNs = startedUtcNs + observedLatencyNs;
|
|
777
|
+
const observedLatencyMs = Number(observedLatencyNs) / 1000000;
|
|
778
|
+
const recordedSortIndex = internal.recordStep(stepName, reply, observedLatencyMs);
|
|
779
|
+
internal.recordStepObservation?.((0, iteration_observations_js_1.createIterationStepObservation)({
|
|
780
|
+
stepName,
|
|
781
|
+
sortIndex: typeof recordedSortIndex === "number" ? recordedSortIndex : 0,
|
|
782
|
+
startedUtcNs,
|
|
783
|
+
completedUtcNs,
|
|
784
|
+
observedLatencyUs64: observedLatencyNs / 1000n,
|
|
785
|
+
reportedLatencyUs64: normalizeRawObservationLatencyMicroseconds(resolveRecordedLatency(reply.customLatencyMs, observedLatencyMs)),
|
|
786
|
+
isSuccess: reply.isSuccess,
|
|
787
|
+
statusCode: normalizeStatusCode(reply.statusCode, reply.isSuccess),
|
|
788
|
+
sizeBytes64: normalizeHistogramInteger(Math.max(toNumber(reply.sizeBytes), 0), "Step response bytes")
|
|
789
|
+
}));
|
|
481
790
|
await internal.invokeAfterStep(stepName, reply);
|
|
482
791
|
return reply;
|
|
483
792
|
}
|
|
@@ -1016,10 +1325,14 @@ class LoadStrikeContext {
|
|
|
1016
1325
|
const normalizedValues = normalizeRunContextCollectionShapes(this.values);
|
|
1017
1326
|
return {
|
|
1018
1327
|
displayConsoleMetrics: normalizedValues.ConsoleMetricsEnabled,
|
|
1328
|
+
loadEngineContractVersion: normalizedValues.LoadEngineContractVersion,
|
|
1329
|
+
maxInFlight: normalizedValues.MaxInFlight,
|
|
1019
1330
|
nodeType: normalizedValues.NodeType,
|
|
1020
1331
|
localDevClusterEnabled: normalizedValues.LocalDevClusterEnabled,
|
|
1021
1332
|
agentGroup: normalizedValues.AgentGroup,
|
|
1022
1333
|
agentsCount: normalizedValues.AgentsCount,
|
|
1334
|
+
agentId: normalizedValues.AgentId,
|
|
1335
|
+
expectedAgentIds: normalizedValues.ExpectedAgentIds,
|
|
1023
1336
|
targetScenarios: normalizedValues.TargetScenarios,
|
|
1024
1337
|
agentTargetScenarios: normalizedValues.AgentTargetScenarios,
|
|
1025
1338
|
coordinatorTargetScenarios: normalizedValues.CoordinatorTargetScenarios,
|
|
@@ -1038,6 +1351,13 @@ class LoadStrikeContext {
|
|
|
1038
1351
|
reportFolderPath: normalizedValues.ReportFolderPath,
|
|
1039
1352
|
reportFormats: normalizedValues.ReportFormats,
|
|
1040
1353
|
reportingIntervalSeconds: normalizedValues.ReportingIntervalSeconds,
|
|
1354
|
+
iterationObservationFlushIntervalSeconds: normalizedValues.IterationObservationFlushIntervalSeconds,
|
|
1355
|
+
maxIterationObservationBufferBytes: normalizedValues.MaxIterationObservationBufferBytes,
|
|
1356
|
+
maxIterationObservationsPerBatch: normalizedValues.MaxIterationObservationsPerBatch,
|
|
1357
|
+
maxIterationObservationBatchBytes: normalizedValues.MaxIterationObservationBatchBytes,
|
|
1358
|
+
iterationObservationSinkQueueDepth: normalizedValues.IterationObservationSinkQueueDepth,
|
|
1359
|
+
iterationObservationSinkParallelism: normalizedValues.IterationObservationSinkParallelism,
|
|
1360
|
+
iterationObservationDrainTimeoutSeconds: normalizedValues.IterationObservationDrainTimeoutSeconds,
|
|
1041
1361
|
minimumLogLevel: normalizedValues.MinimumLogLevel,
|
|
1042
1362
|
loggerConfig: normalizedValues.LoggerConfig,
|
|
1043
1363
|
reportingSinks: normalizedValues.ReportingSinks,
|
|
@@ -1051,6 +1371,8 @@ class LoadStrikeContext {
|
|
|
1051
1371
|
workerPlugins: normalizedValues.WorkerPlugins,
|
|
1052
1372
|
customSettings: normalizedValues.CustomSettings,
|
|
1053
1373
|
globalCustomSettings: normalizedValues.GlobalCustomSettings,
|
|
1374
|
+
clusterShardIndex: normalizedValues.ClusterShardIndex,
|
|
1375
|
+
clusterShardCount: normalizedValues.ClusterShardCount,
|
|
1054
1376
|
runArgs: this.runArgs.length ? [...this.runArgs] : undefined
|
|
1055
1377
|
};
|
|
1056
1378
|
}
|
|
@@ -1112,6 +1434,19 @@ class LoadStrikeContext {
|
|
|
1112
1434
|
DisplayConsoleMetrics(enable) {
|
|
1113
1435
|
return this.mergeValues({ ConsoleMetricsEnabled: Boolean(enable) });
|
|
1114
1436
|
}
|
|
1437
|
+
useLoadEngineV2() {
|
|
1438
|
+
return this.UseLoadEngineV2();
|
|
1439
|
+
}
|
|
1440
|
+
UseLoadEngineV2() {
|
|
1441
|
+
return this.mergeValues({ LoadEngineContractVersion: 2 });
|
|
1442
|
+
}
|
|
1443
|
+
withMaxInFlight(maxInFlight) {
|
|
1444
|
+
return this.WithMaxInFlight(maxInFlight);
|
|
1445
|
+
}
|
|
1446
|
+
WithMaxInFlight(maxInFlight) {
|
|
1447
|
+
validateV2MaxInFlight(this.values.LoadEngineContractVersion, maxInFlight);
|
|
1448
|
+
return this.mergeValues({ MaxInFlight: maxInFlight });
|
|
1449
|
+
}
|
|
1115
1450
|
/**
|
|
1116
1451
|
* Toggles local development cluster mode.
|
|
1117
1452
|
* Use this when you want to simulate coordinator and agent behavior on a single machine.
|
|
@@ -1178,6 +1513,26 @@ class LoadStrikeContext {
|
|
|
1178
1513
|
WithAgentGroup(agentGroup) {
|
|
1179
1514
|
return this.mergeValues({ AgentGroup: requireNonEmpty(agentGroup, "Agent group must be provided.") });
|
|
1180
1515
|
}
|
|
1516
|
+
/** Sets the stable identity required by a remote Load Engine V2 agent. */
|
|
1517
|
+
withAgentId(agentId) {
|
|
1518
|
+
return this.WithAgentId(agentId);
|
|
1519
|
+
}
|
|
1520
|
+
/** Sets the stable identity required by a remote Load Engine V2 agent. */
|
|
1521
|
+
WithAgentId(agentId) {
|
|
1522
|
+
return this.mergeValues({ AgentId: requireNonEmpty(agentId, "Agent id must be provided.") });
|
|
1523
|
+
}
|
|
1524
|
+
/** Sets the exact remote agent identities required by a Load Engine V2 coordinator. */
|
|
1525
|
+
withExpectedAgentIds(...agentIds) {
|
|
1526
|
+
return this.WithExpectedAgentIds(...agentIds);
|
|
1527
|
+
}
|
|
1528
|
+
/** Sets the exact remote agent identities required by a Load Engine V2 coordinator. */
|
|
1529
|
+
WithExpectedAgentIds(...agentIds) {
|
|
1530
|
+
const normalized = validateScenarioNames(agentIds);
|
|
1531
|
+
if (new Set(normalized).size !== normalized.length) {
|
|
1532
|
+
throw new Error("Expected agent ids must be unique.");
|
|
1533
|
+
}
|
|
1534
|
+
return this.mergeValues({ ExpectedAgentIds: normalized });
|
|
1535
|
+
}
|
|
1181
1536
|
/**
|
|
1182
1537
|
* Sets the requested agent count.
|
|
1183
1538
|
* Use this when a coordinator should fan work out across a specific number of agents.
|
|
@@ -2022,7 +2377,7 @@ function firstWebVitalViolation(...values) {
|
|
|
2022
2377
|
return "";
|
|
2023
2378
|
}
|
|
2024
2379
|
class LoadStrikeScenario {
|
|
2025
|
-
constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail, internalLicenseFeatures = []) {
|
|
2380
|
+
constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail, internalLicenseFeatures = [], declaredStepNames = []) {
|
|
2026
2381
|
this.name = name;
|
|
2027
2382
|
this.runHandler = runHandler;
|
|
2028
2383
|
this.initHandler = initHandler;
|
|
@@ -2036,6 +2391,7 @@ class LoadStrikeScenario {
|
|
|
2036
2391
|
this.weight = weight;
|
|
2037
2392
|
this.restartIterationOnFail = restartIterationOnFail;
|
|
2038
2393
|
this.internalLicenseFeatures = normalizeStringArray(internalLicenseFeatures);
|
|
2394
|
+
this.declaredStepNames = normalizeDeclaredStepNames(declaredStepNames);
|
|
2039
2395
|
}
|
|
2040
2396
|
static create(name, runHandler) {
|
|
2041
2397
|
const scenarioName = requireNonEmpty(name, "Scenario name must be provided.");
|
|
@@ -2078,7 +2434,7 @@ class LoadStrikeScenario {
|
|
|
2078
2434
|
if (typeof handler !== "function") {
|
|
2079
2435
|
throw new TypeError("Init handler must be provided.");
|
|
2080
2436
|
}
|
|
2081
|
-
return new LoadStrikeScenario(this.name, this.runHandler, handler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
|
|
2437
|
+
return new LoadStrikeScenario(this.name, this.runHandler, handler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
|
|
2082
2438
|
}
|
|
2083
2439
|
/**
|
|
2084
2440
|
* Configures init async for this SDK object.
|
|
@@ -2095,7 +2451,7 @@ class LoadStrikeScenario {
|
|
|
2095
2451
|
if (typeof handler !== "function") {
|
|
2096
2452
|
throw new TypeError("Clean handler must be provided.");
|
|
2097
2453
|
}
|
|
2098
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, handler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
|
|
2454
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, handler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
|
|
2099
2455
|
}
|
|
2100
2456
|
/**
|
|
2101
2457
|
* Configures clean async for this SDK object.
|
|
@@ -2112,14 +2468,14 @@ class LoadStrikeScenario {
|
|
|
2112
2468
|
if (!Number.isFinite(maxFailCount)) {
|
|
2113
2469
|
throw new RangeError("maxFailCount should be a finite number.");
|
|
2114
2470
|
}
|
|
2115
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, Math.trunc(maxFailCount), this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
|
|
2471
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, Math.trunc(maxFailCount), this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
|
|
2116
2472
|
}
|
|
2117
2473
|
/**
|
|
2118
2474
|
* Configures out warm up for this SDK object.
|
|
2119
2475
|
* Use this when out warm up should be set explicitly before the run starts.
|
|
2120
2476
|
*/
|
|
2121
2477
|
withoutWarmUp() {
|
|
2122
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, true, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
|
|
2478
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, true, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
|
|
2123
2479
|
}
|
|
2124
2480
|
/**
|
|
2125
2481
|
* Configures warm up duration for this SDK object.
|
|
@@ -2129,7 +2485,7 @@ class LoadStrikeScenario {
|
|
|
2129
2485
|
if (!Number.isFinite(durationSeconds)) {
|
|
2130
2486
|
throw new RangeError("Warmup duration should be a finite number.");
|
|
2131
2487
|
}
|
|
2132
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, durationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
|
|
2488
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, durationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
|
|
2133
2489
|
}
|
|
2134
2490
|
/**
|
|
2135
2491
|
* Configures weight for this SDK object.
|
|
@@ -2139,14 +2495,14 @@ class LoadStrikeScenario {
|
|
|
2139
2495
|
if (!Number.isFinite(weight)) {
|
|
2140
2496
|
throw new RangeError("Weight should be a finite number.");
|
|
2141
2497
|
}
|
|
2142
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, Math.trunc(weight), this.restartIterationOnFail, this.internalLicenseFeatures);
|
|
2498
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, Math.trunc(weight), this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
|
|
2143
2499
|
}
|
|
2144
2500
|
/**
|
|
2145
2501
|
* Configures restart iteration on fail for this SDK object.
|
|
2146
2502
|
* Use this when restart iteration on fail should be set explicitly before the run starts.
|
|
2147
2503
|
*/
|
|
2148
2504
|
withRestartIterationOnFail(shouldRestart) {
|
|
2149
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, Boolean(shouldRestart), this.internalLicenseFeatures);
|
|
2505
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, Boolean(shouldRestart), this.internalLicenseFeatures, this.declaredStepNames);
|
|
2150
2506
|
}
|
|
2151
2507
|
/**
|
|
2152
2508
|
* Configures cross platform tracking for this SDK object.
|
|
@@ -2171,7 +2527,7 @@ class LoadStrikeScenario {
|
|
|
2171
2527
|
if (isCorrelateExistingTrafficTracking(copied) && this.loadSimulations.length > 0) {
|
|
2172
2528
|
throw new Error("CorrelateExistingTraffic uses ForDuration and cannot be combined with WithLoadSimulations.");
|
|
2173
2529
|
}
|
|
2174
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, copied, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
|
|
2530
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, copied, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
|
|
2175
2531
|
}
|
|
2176
2532
|
/**
|
|
2177
2533
|
* Configures load simulations for this SDK object.
|
|
@@ -2184,7 +2540,7 @@ class LoadStrikeScenario {
|
|
|
2184
2540
|
if (isCorrelateExistingTrafficTracking(this.trackingConfiguration)) {
|
|
2185
2541
|
throw new Error("CorrelateExistingTraffic uses ForDuration and cannot be combined with WithLoadSimulations.");
|
|
2186
2542
|
}
|
|
2187
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, simulations.map((simulation) => attachLoadSimulationProjection({ ...simulation })), this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
|
|
2543
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, simulations.map((simulation) => attachLoadSimulationProjection({ ...simulation })), this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
|
|
2188
2544
|
}
|
|
2189
2545
|
/**
|
|
2190
2546
|
* Configures thresholds for this SDK object.
|
|
@@ -2194,7 +2550,7 @@ class LoadStrikeScenario {
|
|
|
2194
2550
|
if (!thresholds.length) {
|
|
2195
2551
|
throw new Error("At least one threshold should be provided.");
|
|
2196
2552
|
}
|
|
2197
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, thresholds.map((threshold) => ({ ...threshold })), this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures);
|
|
2553
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, thresholds.map((threshold) => ({ ...threshold })), this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, this.declaredStepNames);
|
|
2198
2554
|
}
|
|
2199
2555
|
/**
|
|
2200
2556
|
* Returns simulations.
|
|
@@ -2255,6 +2611,26 @@ class LoadStrikeScenario {
|
|
|
2255
2611
|
__loadStrikeInternalLicenseFeatures() {
|
|
2256
2612
|
return [...this.internalLicenseFeatures];
|
|
2257
2613
|
}
|
|
2614
|
+
/** Freezes the named step identities that may be reported by this scenario. */
|
|
2615
|
+
withDeclaredSteps(...stepNames) {
|
|
2616
|
+
if (!stepNames.length) {
|
|
2617
|
+
throw new Error("At least one declared step name should be provided.");
|
|
2618
|
+
}
|
|
2619
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, this.internalLicenseFeatures, stepNames);
|
|
2620
|
+
}
|
|
2621
|
+
/** Returns the immutable declared-step names in declaration order. */
|
|
2622
|
+
getDeclaredSteps() {
|
|
2623
|
+
return [...this.declaredStepNames];
|
|
2624
|
+
}
|
|
2625
|
+
__loadStrikeSetTrafficMixV2Metadata(metadata) {
|
|
2626
|
+
this.trafficMixV2Metadata = cloneLoadEngineV2TrafficMixMetadata(metadata);
|
|
2627
|
+
return this;
|
|
2628
|
+
}
|
|
2629
|
+
__loadStrikeTrafficMixV2Metadata() {
|
|
2630
|
+
return this.trafficMixV2Metadata
|
|
2631
|
+
? cloneLoadEngineV2TrafficMixMetadata(this.trafficMixV2Metadata)
|
|
2632
|
+
: undefined;
|
|
2633
|
+
}
|
|
2258
2634
|
__loadStrikeScenarioSourceAnalysis() {
|
|
2259
2635
|
const source = this.runHandler.toString();
|
|
2260
2636
|
const lines = source
|
|
@@ -2277,7 +2653,7 @@ class LoadStrikeScenario {
|
|
|
2277
2653
|
}
|
|
2278
2654
|
__loadStrikeWithInternalLicenseFeatures(...features) {
|
|
2279
2655
|
const merged = Array.from(new Set([...this.internalLicenseFeatures, ...normalizeStringArray(features)]));
|
|
2280
|
-
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, merged);
|
|
2656
|
+
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, merged, this.declaredStepNames);
|
|
2281
2657
|
}
|
|
2282
2658
|
async invokeInit(context) {
|
|
2283
2659
|
if (this.initHandler) {
|
|
@@ -2298,6 +2674,9 @@ class LoadStrikeScenario {
|
|
|
2298
2674
|
return normalizeReply(result);
|
|
2299
2675
|
}
|
|
2300
2676
|
catch (error) {
|
|
2677
|
+
if (error instanceof RuntimePolicyCallbackError) {
|
|
2678
|
+
throw error;
|
|
2679
|
+
}
|
|
2301
2680
|
return LoadStrikeResponse.fail("exception", resolveRuntimeErrorMessage(error, "scenario failed"), 0);
|
|
2302
2681
|
}
|
|
2303
2682
|
}
|
|
@@ -2343,6 +2722,10 @@ class LoadStrikeScenario {
|
|
|
2343
2722
|
WithLoadSimulations(...simulations) {
|
|
2344
2723
|
return this.withLoadSimulations(...simulations);
|
|
2345
2724
|
}
|
|
2725
|
+
/** Freezes the named step identities that may be reported by this scenario. */
|
|
2726
|
+
WithDeclaredSteps(...stepNames) {
|
|
2727
|
+
return this.withDeclaredSteps(...stepNames);
|
|
2728
|
+
}
|
|
2346
2729
|
/**
|
|
2347
2730
|
* Configures max fail count for this SDK object.
|
|
2348
2731
|
* Use this when max fail count should be set explicitly before the run starts.
|
|
@@ -2473,7 +2856,7 @@ class LoadStrikeTrafficMix {
|
|
|
2473
2856
|
return this.withScenarioMix(...scenarioMix);
|
|
2474
2857
|
}
|
|
2475
2858
|
expandScenarios() {
|
|
2476
|
-
return expandTrafficMixScenarios(this);
|
|
2859
|
+
return expandTrafficMixScenarios(this, 0);
|
|
2477
2860
|
}
|
|
2478
2861
|
ExpandScenarios() {
|
|
2479
2862
|
return this.expandScenarios();
|
|
@@ -2487,10 +2870,11 @@ class LoadStrikeTrafficMix {
|
|
|
2487
2870
|
}
|
|
2488
2871
|
exports.LoadStrikeTrafficMix = LoadStrikeTrafficMix;
|
|
2489
2872
|
class LoadStrikeRunner {
|
|
2490
|
-
constructor(scenarios, options, contextConfigurators = []) {
|
|
2873
|
+
constructor(scenarios, options, contextConfigurators = [], internalOptions = {}) {
|
|
2491
2874
|
this.scenarios = scenarios;
|
|
2492
2875
|
this.options = normalizeRunnerOptionCollectionShapes(options);
|
|
2493
2876
|
this.contextConfigurators = [...contextConfigurators];
|
|
2877
|
+
this.internalOptions = internalOptions;
|
|
2494
2878
|
}
|
|
2495
2879
|
/**
|
|
2496
2880
|
* Creates a new instance of this public SDK type.
|
|
@@ -2526,7 +2910,7 @@ class LoadStrikeRunner {
|
|
|
2526
2910
|
* Use this when one total load profile should be split across weighted scenario lanes.
|
|
2527
2911
|
*/
|
|
2528
2912
|
static registerTrafficMix(trafficMix) {
|
|
2529
|
-
return LoadStrikeRunner.registerScenarios(...expandTrafficMixScenarios(trafficMix));
|
|
2913
|
+
return LoadStrikeRunner.registerScenarios(...expandTrafficMixScenarios(trafficMix, 0));
|
|
2530
2914
|
}
|
|
2531
2915
|
/**
|
|
2532
2916
|
* Registers a traffic mix on a fresh runnable context.
|
|
@@ -2542,6 +2926,12 @@ class LoadStrikeRunner {
|
|
|
2542
2926
|
static DisplayConsoleMetrics(context, enable) {
|
|
2543
2927
|
return context.DisplayConsoleMetrics(enable);
|
|
2544
2928
|
}
|
|
2929
|
+
static UseLoadEngineV2(context) {
|
|
2930
|
+
return context.UseLoadEngineV2();
|
|
2931
|
+
}
|
|
2932
|
+
static WithMaxInFlight(context, maxInFlight) {
|
|
2933
|
+
return context.WithMaxInFlight(maxInFlight);
|
|
2934
|
+
}
|
|
2545
2935
|
/**
|
|
2546
2936
|
* Toggles local development cluster mode.
|
|
2547
2937
|
* Use this when you want to simulate coordinator and agent behavior on a single machine.
|
|
@@ -2577,6 +2967,12 @@ class LoadStrikeRunner {
|
|
|
2577
2967
|
static WithAgentGroup(context, agentGroup) {
|
|
2578
2968
|
return context.WithAgentGroup(agentGroup);
|
|
2579
2969
|
}
|
|
2970
|
+
static WithAgentId(context, agentId) {
|
|
2971
|
+
return context.WithAgentId(agentId);
|
|
2972
|
+
}
|
|
2973
|
+
static WithExpectedAgentIds(context, ...agentIds) {
|
|
2974
|
+
return context.WithExpectedAgentIds(...agentIds);
|
|
2975
|
+
}
|
|
2580
2976
|
/**
|
|
2581
2977
|
* Sets the requested agent count.
|
|
2582
2978
|
* Use this when a coordinator should fan work out across a specific number of agents.
|
|
@@ -2794,7 +3190,10 @@ class LoadStrikeRunner {
|
|
|
2794
3190
|
* Use this when one total load profile should be split across weighted scenario lanes.
|
|
2795
3191
|
*/
|
|
2796
3192
|
addTrafficMix(trafficMix) {
|
|
2797
|
-
this.scenarios = [
|
|
3193
|
+
this.scenarios = [
|
|
3194
|
+
...this.scenarios,
|
|
3195
|
+
...expandTrafficMixScenarios(trafficMix, nextTrafficMixDeclarationIndex(this.scenarios))
|
|
3196
|
+
];
|
|
2798
3197
|
return this;
|
|
2799
3198
|
}
|
|
2800
3199
|
/**
|
|
@@ -2927,6 +3326,19 @@ class LoadStrikeRunner {
|
|
|
2927
3326
|
WithReportingInterval(intervalSeconds) {
|
|
2928
3327
|
return this.withReportingInterval(intervalSeconds);
|
|
2929
3328
|
}
|
|
3329
|
+
useLoadEngineV2() {
|
|
3330
|
+
return this.configure({ loadEngineContractVersion: 2 });
|
|
3331
|
+
}
|
|
3332
|
+
UseLoadEngineV2() {
|
|
3333
|
+
return this.useLoadEngineV2();
|
|
3334
|
+
}
|
|
3335
|
+
withMaxInFlight(maxInFlight) {
|
|
3336
|
+
validateV2MaxInFlight(this.options.loadEngineContractVersion, maxInFlight);
|
|
3337
|
+
return this.configure({ maxInFlight });
|
|
3338
|
+
}
|
|
3339
|
+
WithMaxInFlight(maxInFlight) {
|
|
3340
|
+
return this.withMaxInFlight(maxInFlight);
|
|
3341
|
+
}
|
|
2930
3342
|
withReportingSinks(...sinks) {
|
|
2931
3343
|
if (!sinks.length) {
|
|
2932
3344
|
throw new Error("At least one reporting sink should be provided.");
|
|
@@ -3025,7 +3437,7 @@ class LoadStrikeRunner {
|
|
|
3025
3437
|
}
|
|
3026
3438
|
async run(args = []) {
|
|
3027
3439
|
if (this.contextConfigurators.length) {
|
|
3028
|
-
return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions()).run(args);
|
|
3440
|
+
return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions(), [], this.internalOptions).run(args);
|
|
3029
3441
|
}
|
|
3030
3442
|
if (args.length) {
|
|
3031
3443
|
return this.buildContext().run(args);
|
|
@@ -3073,10 +3485,15 @@ class LoadStrikeRunner {
|
|
|
3073
3485
|
let licenseClient = null;
|
|
3074
3486
|
let licensePayload = null;
|
|
3075
3487
|
let licenseSession = null;
|
|
3488
|
+
let iterationObservationsFinalized = false;
|
|
3076
3489
|
const clusterMode = resolveClusterExecutionMode(this.options);
|
|
3077
3490
|
const selectedScenarios = clusterMode === "local-coordinator" || clusterMode === "nats-coordinator"
|
|
3078
3491
|
? await this.filterScenariosWithPolicies(this.scenarios, policies, policyErrors, runtimePolicyErrorMode)
|
|
3079
3492
|
: await this.selectScenarios(policies, policyErrors, runtimePolicyErrorMode);
|
|
3493
|
+
if (this.options.loadEngineContractVersion === 2
|
|
3494
|
+
&& (clusterMode === "nats-coordinator" || clusterMode === "nats-agent")) {
|
|
3495
|
+
validateLoadEngineV2ScenarioFeatures(selectedScenarios);
|
|
3496
|
+
}
|
|
3080
3497
|
if (clusterMode === "nats-agent") {
|
|
3081
3498
|
return this.runAgentWithNats(createdUtc, testInfo, nodeInfo);
|
|
3082
3499
|
}
|
|
@@ -3141,6 +3558,38 @@ class LoadStrikeRunner {
|
|
|
3141
3558
|
}
|
|
3142
3559
|
}
|
|
3143
3560
|
await this.startSinks(sinkStates, sessionInfo, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3561
|
+
const iterationObservationRunId = String(this.internalOptions.iterationObservationRunId
|
|
3562
|
+
?? sessionInfo.portalReportingRunId
|
|
3563
|
+
?? sessionInfo.PortalReportingRunId
|
|
3564
|
+
?? testInfo.sessionId);
|
|
3565
|
+
const iterationObservationResultOwnerId = String(nodeInfo.nodeType).toLowerCase() === "agent"
|
|
3566
|
+
? String(this.options.agentCommandId
|
|
3567
|
+
?? `${nodeInfo.machineName}:${Math.max(Math.trunc(this.options.clusterShardIndex ?? 0), 0)}`)
|
|
3568
|
+
: "";
|
|
3569
|
+
const iterationObservationProcessGroup = 0;
|
|
3570
|
+
const iterationObservationExpectedResultOwnerCount64 = Math.max(Math.trunc(this.options.clusterShardCount ?? 1), 1).toString();
|
|
3571
|
+
const initializedIterationObservationSinks = sinkStates
|
|
3572
|
+
.filter((state) => !state.disabled)
|
|
3573
|
+
.map((state) => ({
|
|
3574
|
+
name: state.name,
|
|
3575
|
+
iterationObservationPortalSink: Boolean(state.sink.iterationObservationPortalSink),
|
|
3576
|
+
iterationObservationShapeLimited: Boolean(state.sink.iterationObservationShapeLimited),
|
|
3577
|
+
saveIterationBatch: resolveSinkSaveIterationBatch(state.sink),
|
|
3578
|
+
completeIterationObservationStream: resolveSinkCompleteIterationObservationStream(state.sink)
|
|
3579
|
+
}));
|
|
3580
|
+
const reporterIterationObservationSinks = this.internalOptions.iterationObservationSinks
|
|
3581
|
+
?? (clusterMode === "local-coordinator" || clusterMode === "nats-coordinator"
|
|
3582
|
+
? []
|
|
3583
|
+
: initializedIterationObservationSinks);
|
|
3584
|
+
const iterationObservationReporter = new iteration_observations_js_1.IterationObservationReporter({
|
|
3585
|
+
runId: iterationObservationRunId,
|
|
3586
|
+
sessionId: testInfo.sessionId,
|
|
3587
|
+
resultOwnerId: iterationObservationResultOwnerId,
|
|
3588
|
+
expectedResultOwnerCount64: iterationObservationExpectedResultOwnerCount64,
|
|
3589
|
+
processGroup: iterationObservationProcessGroup,
|
|
3590
|
+
settings: resolveIterationObservationSettings(this.options),
|
|
3591
|
+
sinks: reporterIterationObservationSinks
|
|
3592
|
+
});
|
|
3144
3593
|
const emitRealtimeSnapshot = async () => {
|
|
3145
3594
|
if (realtimeInFlight) {
|
|
3146
3595
|
return;
|
|
@@ -3207,21 +3656,30 @@ class LoadStrikeRunner {
|
|
|
3207
3656
|
let result;
|
|
3208
3657
|
let metricStats;
|
|
3209
3658
|
if (clusterMode === "local-coordinator") {
|
|
3210
|
-
const aggregated = await this.runCoordinatorWithLocalAgents(selectedScenarios, testInfo, nodeInfo, licenseClient, licenseSession);
|
|
3659
|
+
const aggregated = await this.runCoordinatorWithLocalAgents(selectedScenarios, testInfo, nodeInfo, licenseClient, licenseSession, iterationObservationRunId, initializedIterationObservationSinks);
|
|
3211
3660
|
metricStats = aggregated.metrics;
|
|
3212
3661
|
result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors, policyErrors);
|
|
3213
3662
|
}
|
|
3214
3663
|
else if (clusterMode === "nats-coordinator") {
|
|
3215
|
-
const aggregated = await this.runCoordinatorWithNats(selectedScenarios, testInfo, nodeInfo, licenseClient, licenseSession);
|
|
3664
|
+
const aggregated = await this.runCoordinatorWithNats(selectedScenarios, testInfo, nodeInfo, licenseClient, licenseSession, iterationObservationRunId, initializedIterationObservationSinks);
|
|
3216
3665
|
metricStats = aggregated.metrics;
|
|
3217
3666
|
result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors, policyErrors);
|
|
3218
3667
|
}
|
|
3219
3668
|
else {
|
|
3220
3669
|
const testAbortController = new AbortController();
|
|
3221
3670
|
const stopTestState = { value: false, reason: undefined };
|
|
3222
|
-
|
|
3671
|
+
const loadEngineV2Budget = this.options.loadEngineContractVersion === 2
|
|
3672
|
+
? (this.options.loadEngineV2BudgetOverride
|
|
3673
|
+
?? new load_engine_v2_js_1.LoadEngineV2ExecutionBudget(this.options.maxInFlight ?? 10000))
|
|
3674
|
+
: undefined;
|
|
3675
|
+
const loadEngineV2Telemetry = loadEngineV2Budget
|
|
3676
|
+
? new LoadEngineV2Telemetry(loadEngineV2Budget)
|
|
3677
|
+
: undefined;
|
|
3678
|
+
const executeSelectedScenario = (scenario, selectedScenarioIndex) => executeScenarioRuntime({
|
|
3223
3679
|
scenario,
|
|
3224
|
-
scenarioIndex
|
|
3680
|
+
scenarioIndex: this.options.loadEngineContractVersion === 2
|
|
3681
|
+
? this.scenarios.indexOf(scenario)
|
|
3682
|
+
: selectedScenarioIndex,
|
|
3225
3683
|
scenarioCount: selectedScenarios.length,
|
|
3226
3684
|
options: this.options,
|
|
3227
3685
|
logger: runLogger,
|
|
@@ -3236,12 +3694,26 @@ class LoadStrikeRunner {
|
|
|
3236
3694
|
scenarioDurationsMs,
|
|
3237
3695
|
stopTestState,
|
|
3238
3696
|
testAbortController,
|
|
3697
|
+
loadEngineV2Budget,
|
|
3698
|
+
loadEngineV2Telemetry,
|
|
3699
|
+
iterationObservationReporter,
|
|
3700
|
+
iterationObservationRunId,
|
|
3701
|
+
iterationObservationResultOwnerId,
|
|
3702
|
+
iterationObservationProcessGroup,
|
|
3239
3703
|
executeScenarioInvocation: (targetScenario, context, operation) => this.executeScenarioInvocation(targetScenario, context, operation),
|
|
3240
3704
|
invokeBeforeScenario: (runtimePolicies, scenarioName) => this.invokeBeforeScenario(runtimePolicies, scenarioName, policyErrors, runtimePolicyErrorMode),
|
|
3241
3705
|
invokeAfterScenario: (runtimePolicies, scenarioName, stats) => this.invokeAfterScenario(runtimePolicies, scenarioName, stats, policyErrors, runtimePolicyErrorMode),
|
|
3242
3706
|
invokeBeforeStep: (runtimePolicies, scenarioName, stepName) => this.invokeBeforeStep(runtimePolicies, scenarioName, stepName, policyErrors, runtimePolicyErrorMode),
|
|
3243
3707
|
invokeAfterStep: (runtimePolicies, scenarioName, stepName, reply) => this.invokeAfterStep(runtimePolicies, scenarioName, stepName, reply, policyErrors, runtimePolicyErrorMode)
|
|
3244
|
-
})
|
|
3708
|
+
});
|
|
3709
|
+
if (this.options.loadEngineV2SegmentLifecycleOverride) {
|
|
3710
|
+
for (let scenarioIndex = 0; scenarioIndex < selectedScenarios.length; scenarioIndex += 1) {
|
|
3711
|
+
await executeSelectedScenario(selectedScenarios[scenarioIndex], scenarioIndex);
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
else {
|
|
3715
|
+
await Promise.all(selectedScenarios.map(executeSelectedScenario));
|
|
3716
|
+
}
|
|
3245
3717
|
nodeInfo.currentOperation = stopTestState.value ? "Stop" : "Complete";
|
|
3246
3718
|
const scenarioStatList = Array.from(scenarioAccumulators.values())
|
|
3247
3719
|
.map((value) => value.build(scenarioDurationsMs.get(value.scenarioName) ?? 0))
|
|
@@ -3281,10 +3753,39 @@ class LoadStrikeRunner {
|
|
|
3281
3753
|
reportFiles: [],
|
|
3282
3754
|
logFiles: [...loggerSetup.logFiles],
|
|
3283
3755
|
correlationRows: buildDetailedCorrelationRows(),
|
|
3284
|
-
failedCorrelationRows: buildDetailedFailedCorrelationRows()
|
|
3756
|
+
failedCorrelationRows: buildDetailedFailedCorrelationRows(),
|
|
3757
|
+
...(loadEngineV2Telemetry
|
|
3758
|
+
? {
|
|
3759
|
+
generatorWarnings: loadEngineV2Telemetry.buildWarnings(),
|
|
3760
|
+
schedulerSegments: loadEngineV2Telemetry.buildSegments(),
|
|
3761
|
+
schedulerStats: loadEngineV2Telemetry.buildStats(),
|
|
3762
|
+
[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: loadEngineV2Telemetry.buildSchedulerDistributions()
|
|
3763
|
+
}
|
|
3764
|
+
: {})
|
|
3285
3765
|
};
|
|
3286
3766
|
}
|
|
3287
3767
|
await stopRealtimeReporting();
|
|
3768
|
+
const observationDelivery = await iterationObservationReporter.sealAndDrain();
|
|
3769
|
+
iterationObservationsFinalized = true;
|
|
3770
|
+
if (clusterMode !== "local-coordinator"
|
|
3771
|
+
&& clusterMode !== "nats-coordinator") {
|
|
3772
|
+
result.observationDeliveryStats = {
|
|
3773
|
+
lastBatchSequence64: observationDelivery.lastBatchSequence64,
|
|
3774
|
+
capturedCount64: observationDelivery.capturedCount64,
|
|
3775
|
+
deliveredCount64: observationDelivery.deliveredCount64,
|
|
3776
|
+
droppedBufferCount64: observationDelivery.droppedBufferCount64,
|
|
3777
|
+
droppedSinkCount64: observationDelivery.droppedSinkCount64
|
|
3778
|
+
};
|
|
3779
|
+
result.reportingComplete = observationDelivery.reportingComplete;
|
|
3780
|
+
}
|
|
3781
|
+
else {
|
|
3782
|
+
result.observationDeliveryStats ?? (result.observationDeliveryStats = emptyObservationDeliveryStats());
|
|
3783
|
+
result.reportingComplete ?? (result.reportingComplete = observationDelivery.reportingComplete);
|
|
3784
|
+
}
|
|
3785
|
+
result.generatorWarnings = [
|
|
3786
|
+
...(result.generatorWarnings ?? []),
|
|
3787
|
+
...iterationObservationReporter.buildWarnings()
|
|
3788
|
+
];
|
|
3288
3789
|
result.pluginsData = mergePluginData(result.pluginsData, await this.collectPluginData(plugins, attachRunResultAliases(result), pluginLifecycleErrors));
|
|
3289
3790
|
const finalizedResult = attachRunResultAliases(result);
|
|
3290
3791
|
finalizedResult.logFiles = mergeStringArrays(finalizedResult.logFiles, loggerSetup.logFiles);
|
|
@@ -3300,6 +3801,9 @@ class LoadStrikeRunner {
|
|
|
3300
3801
|
}
|
|
3301
3802
|
finally {
|
|
3302
3803
|
await stopRealtimeReporting();
|
|
3804
|
+
if (!iterationObservationsFinalized) {
|
|
3805
|
+
await iterationObservationReporter.sealAndDrain().catch(() => { });
|
|
3806
|
+
}
|
|
3303
3807
|
if (!sinksStopped) {
|
|
3304
3808
|
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3305
3809
|
}
|
|
@@ -3336,7 +3840,7 @@ class LoadStrikeRunner {
|
|
|
3336
3840
|
}
|
|
3337
3841
|
return filtered;
|
|
3338
3842
|
}
|
|
3339
|
-
async runClusterChildNode(targetScenarios, nodeType, machineName, includeWorkerExtensions, overrides = {}) {
|
|
3843
|
+
async runClusterChildNode(targetScenarios, nodeType, machineName, includeWorkerExtensions, overrides = {}, internalOptions = {}) {
|
|
3340
3844
|
if (!targetScenarios.length) {
|
|
3341
3845
|
return buildEmptyNodeStats({
|
|
3342
3846
|
startedUtc: new Date().toISOString(),
|
|
@@ -3364,7 +3868,7 @@ class LoadStrikeRunner {
|
|
|
3364
3868
|
displayConsoleMetrics: false,
|
|
3365
3869
|
reportingSinks: includeWorkerExtensions ? this.options.reportingSinks : [],
|
|
3366
3870
|
workerPlugins: includeWorkerExtensions ? this.options.workerPlugins : []
|
|
3367
|
-
});
|
|
3871
|
+
}, [], internalOptions);
|
|
3368
3872
|
const childResult = await childRunner.run();
|
|
3369
3873
|
const childStats = detailedToNodeStats(childResult);
|
|
3370
3874
|
return {
|
|
@@ -3377,70 +3881,127 @@ class LoadStrikeRunner {
|
|
|
3377
3881
|
logFiles: [...(childResult.logFiles ?? [])]
|
|
3378
3882
|
};
|
|
3379
3883
|
}
|
|
3380
|
-
async runCoordinatorWithLocalAgents(scenarios, testInfo, nodeInfo, licenseClient, licenseSession) {
|
|
3884
|
+
async runCoordinatorWithLocalAgents(scenarios, testInfo, nodeInfo, licenseClient, licenseSession, iterationObservationRunId, iterationObservationSinks) {
|
|
3381
3885
|
if (!licenseClient) {
|
|
3382
3886
|
throw new Error("Coordinator agent execution authorization requires an initialized licensing client.");
|
|
3383
3887
|
}
|
|
3384
|
-
const controllerRunToken =
|
|
3888
|
+
const controllerRunToken = currentLicenseSessionRunToken(licenseSession);
|
|
3385
3889
|
if (!controllerRunToken) {
|
|
3386
3890
|
throw new Error("Coordinator agent execution authorization requires an active controller run token.");
|
|
3387
3891
|
}
|
|
3388
|
-
const assignments = planRuntimeClusterAssignments(scenarios, Math.max(this.options.agentsCount ?? 1, 1), this.options.targetScenarios ?? [], this.options.agentTargetScenarios ?? []);
|
|
3892
|
+
const assignments = planRuntimeClusterAssignments(scenarios, Math.max(this.options.agentsCount ?? 1, 1), this.options.targetScenarios ?? [], this.options.agentTargetScenarios ?? [], this.options.loadEngineContractVersion === 2, this.options.coordinatorTargetScenarios ?? []);
|
|
3893
|
+
const sharedV2Budget = this.options.loadEngineContractVersion === 2
|
|
3894
|
+
? new load_engine_v2_js_1.LoadEngineV2ExecutionBudget(this.options.maxInFlight ?? 10000)
|
|
3895
|
+
: undefined;
|
|
3389
3896
|
const nodeResults = await Promise.all(assignments.map(async (targetScenarios, index) => {
|
|
3390
3897
|
const commandId = (0, node_crypto_1.randomBytes)(16).toString("hex");
|
|
3391
|
-
const agentExecutionToken = await licenseClient.createAgentExecutionToken(
|
|
3898
|
+
const agentExecutionToken = await licenseClient.createAgentExecutionToken(currentLicenseSessionRunToken(licenseSession), testInfo.sessionId, commandId, index, assignments.length, targetScenarios);
|
|
3392
3899
|
return this.runClusterChildNode(targetScenarios, "Agent", `local-agent-${index + 1}`, false, {
|
|
3393
3900
|
sessionId: testInfo.sessionId,
|
|
3394
3901
|
testSuite: testInfo.testSuite,
|
|
3395
3902
|
testName: testInfo.testName,
|
|
3396
3903
|
agentCommandId: commandId,
|
|
3397
|
-
agentExecutionToken
|
|
3904
|
+
agentExecutionToken,
|
|
3905
|
+
clusterShardIndex: index,
|
|
3906
|
+
clusterShardCount: assignments.length,
|
|
3907
|
+
loadEngineV2BudgetOverride: sharedV2Budget
|
|
3908
|
+
}, {
|
|
3909
|
+
iterationObservationRunId,
|
|
3910
|
+
iterationObservationSinks
|
|
3398
3911
|
});
|
|
3399
3912
|
}));
|
|
3400
3913
|
const coordinatorTargets = [...(this.options.coordinatorTargetScenarios ?? [])];
|
|
3401
3914
|
if (coordinatorTargets.length) {
|
|
3402
|
-
nodeResults.push(await this.runClusterChildNode(coordinatorTargets, "Coordinator", nodeInfo.machineName, false
|
|
3915
|
+
nodeResults.push(await this.runClusterChildNode(coordinatorTargets, "Coordinator", nodeInfo.machineName, false, {}, {
|
|
3916
|
+
iterationObservationRunId,
|
|
3917
|
+
iterationObservationSinks
|
|
3918
|
+
}));
|
|
3403
3919
|
}
|
|
3404
|
-
return aggregateNodeStats(testInfo, { ...nodeInfo, nodeType: "Coordinator" }, nodeResults);
|
|
3920
|
+
return aggregateNodeStats(testInfo, { ...nodeInfo, nodeType: "Coordinator" }, nodeResults, this.options.loadEngineContractVersion === 2);
|
|
3405
3921
|
}
|
|
3406
|
-
async runCoordinatorWithNats(scenarios, testInfo, nodeInfo, licenseClient, licenseSession) {
|
|
3922
|
+
async runCoordinatorWithNats(scenarios, testInfo, nodeInfo, licenseClient, licenseSession, iterationObservationRunId, iterationObservationSinks) {
|
|
3407
3923
|
if (!licenseClient) {
|
|
3408
3924
|
throw new Error("Coordinator agent execution authorization requires an initialized licensing client.");
|
|
3409
3925
|
}
|
|
3410
|
-
const controllerRunToken =
|
|
3926
|
+
const controllerRunToken = currentLicenseSessionRunToken(licenseSession);
|
|
3411
3927
|
if (!controllerRunToken) {
|
|
3412
3928
|
throw new Error("Coordinator agent execution authorization requires an active controller run token.");
|
|
3413
3929
|
}
|
|
3414
|
-
const assignments = planRuntimeClusterAssignments(scenarios, Math.max(this.options.agentsCount ?? 1, 1), this.options.targetScenarios ?? [], this.options.agentTargetScenarios ?? []);
|
|
3930
|
+
const assignments = planRuntimeClusterAssignments(scenarios, Math.max(this.options.agentsCount ?? 1, 1), this.options.targetScenarios ?? [], this.options.agentTargetScenarios ?? [], this.options.loadEngineContractVersion === 2, this.options.coordinatorTargetScenarios ?? []);
|
|
3931
|
+
const expectedAgentIds = this.options.loadEngineContractVersion === 2
|
|
3932
|
+
? normalizeRequiredV2AgentIds(this.options.expectedAgentIds, assignments.length)
|
|
3933
|
+
: undefined;
|
|
3415
3934
|
const coordinator = new cluster_js_1.DistributedClusterCoordinator({
|
|
3416
3935
|
clusterId: this.options.clusterId ?? "local",
|
|
3417
3936
|
sessionId: testInfo.sessionId,
|
|
3418
3937
|
testSuite: testInfo.testSuite,
|
|
3419
3938
|
testName: testInfo.testName,
|
|
3420
3939
|
expectedAgentResults: assignments.length,
|
|
3940
|
+
expectedAgentIds,
|
|
3941
|
+
loadEngineContractVersion: this.options.loadEngineContractVersion ?? 1,
|
|
3421
3942
|
agentGroup: this.options.agentGroup,
|
|
3422
3943
|
commandTimeoutMs: Math.max(Math.trunc((this.options.clusterCommandTimeoutSeconds ?? 120) * 1000), 1),
|
|
3423
3944
|
nats: this.options.natsServerUrl
|
|
3424
3945
|
? { ServerUrl: this.options.natsServerUrl }
|
|
3425
3946
|
: undefined
|
|
3426
3947
|
});
|
|
3427
|
-
const
|
|
3428
|
-
const
|
|
3948
|
+
const tokenFactory = (command) => licenseClient.createAgentExecutionToken(currentLicenseSessionRunToken(licenseSession), testInfo.sessionId, command.commandId, command.agentIndex, command.agentCount, command.targetScenarios);
|
|
3949
|
+
const dispatch = this.options.loadEngineContractVersion === 2
|
|
3950
|
+
? await coordinator.dispatchV2(assignments, buildRuntimeLoadEngineV2Plan(scenarios, this.options, testInfo, expectedAgentIds), tokenFactory)
|
|
3951
|
+
: await coordinator.dispatch(assignments, tokenFactory);
|
|
3952
|
+
const nodes = dispatch.nodeResults.map((value) => clusterNodeResultToNodeStats(value, testInfo, { ...nodeInfo, nodeType: "Agent" }, this.options.loadEngineContractVersion === 2));
|
|
3429
3953
|
const coordinatorTargets = [...(this.options.coordinatorTargetScenarios ?? [])];
|
|
3430
3954
|
if (coordinatorTargets.length) {
|
|
3431
|
-
nodes.push(await this.runClusterChildNode(coordinatorTargets, "Coordinator", nodeInfo.machineName, false
|
|
3955
|
+
nodes.push(await this.runClusterChildNode(coordinatorTargets, "Coordinator", nodeInfo.machineName, false, {}, {
|
|
3956
|
+
iterationObservationRunId,
|
|
3957
|
+
iterationObservationSinks
|
|
3958
|
+
}));
|
|
3432
3959
|
}
|
|
3433
|
-
let aggregated = aggregateNodeStats(testInfo, { ...nodeInfo, nodeType: "Coordinator" }, nodes);
|
|
3960
|
+
let aggregated = aggregateNodeStats(testInfo, { ...nodeInfo, nodeType: "Coordinator" }, nodes, this.options.loadEngineContractVersion === 2);
|
|
3434
3961
|
if (dispatch.missingNodes > 0) {
|
|
3435
3962
|
aggregated = appendClusterPluginHint(aggregated, `Timed out waiting for ${dispatch.missingNodes} agent node result(s).`);
|
|
3963
|
+
aggregated = attachNodeStatsAliases({
|
|
3964
|
+
...aggregated,
|
|
3965
|
+
reportingComplete: false,
|
|
3966
|
+
schedulerSegments: [
|
|
3967
|
+
...(aggregated.schedulerSegments ?? []),
|
|
3968
|
+
...(dispatch.ownerLoss?.schedulerSegments ?? [])
|
|
3969
|
+
],
|
|
3970
|
+
generatorWarnings: [
|
|
3971
|
+
...aggregated.generatorWarnings,
|
|
3972
|
+
...(dispatch.ownerLoss?.generatorWarnings ?? []).map((warning) => ({
|
|
3973
|
+
code: warning.code,
|
|
3974
|
+
scenarioName: "cluster",
|
|
3975
|
+
simulationIndex: -1,
|
|
3976
|
+
count64: warning.count64,
|
|
3977
|
+
message: `Result owner ${warning.agentId} was lost after assignment; application failures were not fabricated.`,
|
|
3978
|
+
firstObservedUtcNs: "0",
|
|
3979
|
+
lastObservedUtcNs: "0"
|
|
3980
|
+
}))
|
|
3981
|
+
]
|
|
3982
|
+
});
|
|
3436
3983
|
}
|
|
3437
3984
|
return aggregated;
|
|
3438
3985
|
}
|
|
3439
3986
|
async runAgentWithNats(startedUtc, testInfo, nodeInfo) {
|
|
3987
|
+
const v2AgentId = this.options.loadEngineContractVersion === 2
|
|
3988
|
+
? requireNonEmpty(this.options.agentId ?? "", "Remote Load Engine V2 agents require an explicit stable AgentId.")
|
|
3989
|
+
: `${nodeInfo.machineName}-${generateRuntimeSessionId()}`;
|
|
3440
3990
|
const agent = new cluster_js_1.DistributedClusterAgent({
|
|
3441
3991
|
clusterId: this.options.clusterId ?? "local",
|
|
3442
3992
|
sessionId: testInfo.sessionId,
|
|
3443
|
-
agentId:
|
|
3993
|
+
agentId: v2AgentId,
|
|
3994
|
+
loadEngineContractVersion: this.options.loadEngineContractVersion ?? 1,
|
|
3995
|
+
validateV2Plan: this.options.loadEngineContractVersion === 2
|
|
3996
|
+
? (received) => {
|
|
3997
|
+
const local = buildRuntimeLoadEngineV2Plan(this.scenarios, this.options, { ...testInfo, sessionId: received.sessionId }, received.expectedAgentIds);
|
|
3998
|
+
local.runId = received.runId;
|
|
3999
|
+
local.registrationNonce = received.registrationNonce;
|
|
4000
|
+
if ((0, cluster_js_1.buildLoadEngineV2Plan)(local).hash !== (0, cluster_js_1.buildLoadEngineV2Plan)(received).hash) {
|
|
4001
|
+
throw new Error("Load Engine V2 signed plan descriptors do not match immutable local scenario declarations.");
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
: undefined,
|
|
3444
4005
|
agentGroup: this.options.agentGroup,
|
|
3445
4006
|
nats: this.options.natsServerUrl
|
|
3446
4007
|
? { ServerUrl: this.options.natsServerUrl }
|
|
@@ -3450,13 +4011,16 @@ class LoadStrikeRunner {
|
|
|
3450
4011
|
const deadline = Date.now() + Math.max(Math.trunc((this.options.clusterCommandTimeoutSeconds ?? 120) * 1000), 1);
|
|
3451
4012
|
while (Date.now() < deadline) {
|
|
3452
4013
|
let handledStats = null;
|
|
3453
|
-
const
|
|
4014
|
+
const execute = async (dispatch) => {
|
|
3454
4015
|
handledStats = await this.runClusterChildNode(dispatch.scenarioNames, "Agent", nodeInfo.machineName, true, {
|
|
3455
4016
|
sessionId: testInfo.sessionId,
|
|
3456
4017
|
testSuite: testInfo.testSuite,
|
|
3457
4018
|
testName: testInfo.testName,
|
|
3458
4019
|
agentCommandId: dispatch.commandId,
|
|
3459
|
-
agentExecutionToken: dispatch.agentRunToken
|
|
4020
|
+
agentExecutionToken: dispatch.agentRunToken,
|
|
4021
|
+
clusterShardIndex: dispatch.agentIndex ?? 0,
|
|
4022
|
+
clusterShardCount: dispatch.agentCount ?? 1,
|
|
4023
|
+
loadEngineV2SegmentLifecycleOverride: dispatch.segmentLifecycle
|
|
3460
4024
|
});
|
|
3461
4025
|
return {
|
|
3462
4026
|
nodeId: handledStats.nodeInfo.machineName,
|
|
@@ -3464,9 +4028,12 @@ class LoadStrikeRunner {
|
|
|
3464
4028
|
allRequestCount: handledStats.allRequestCount,
|
|
3465
4029
|
allOkCount: handledStats.allOkCount,
|
|
3466
4030
|
allFailCount: handledStats.allFailCount,
|
|
3467
|
-
stats: nodeStatsToClusterPayload(handledStats)
|
|
4031
|
+
stats: nodeStatsToClusterPayload(handledStats, this.scenarios.filter((scenario) => dispatch.scenarioNames.includes(scenario.name)), this.options.loadEngineContractVersion === 2)
|
|
3468
4032
|
};
|
|
3469
|
-
}
|
|
4033
|
+
};
|
|
4034
|
+
const handled = this.options.loadEngineContractVersion === 2
|
|
4035
|
+
? await agent.pollAndExecuteV2Once(execute)
|
|
4036
|
+
: await agent.pollAndExecuteOnce(execute);
|
|
3470
4037
|
if (handled && handledStats) {
|
|
3471
4038
|
return toDetailedRunResultFromNodeStats(handledStats, startedUtc, [], []);
|
|
3472
4039
|
}
|
|
@@ -3746,13 +4313,148 @@ function hasPluginRows(value) {
|
|
|
3746
4313
|
}
|
|
3747
4314
|
return value.tables.some((table) => Array.isArray(table.rows) && table.rows.length > 0);
|
|
3748
4315
|
}
|
|
4316
|
+
async function executeV2FixedArrivals(args) {
|
|
4317
|
+
const { rate, intervalNs, totalArrivals, budget, cancellationToken, shouldStopNow, nextInstanceInfo, runBombingInvocation, logger, scenarioName, deadlineOffsetsNs, tolerancesNs, ownedOrdinals, shardIndex = 0, shardCount = 1, telemetry, segment } = args;
|
|
4318
|
+
const segmentStartNs = process.hrtime.bigint();
|
|
4319
|
+
const toleranceNs = (0, load_engine_v2_js_1.loadEngineV2LatenessToleranceNs)(rate, intervalNs);
|
|
4320
|
+
const active = new Set();
|
|
4321
|
+
let schedulerLate = 0n;
|
|
4322
|
+
let maxInFlight = 0n;
|
|
4323
|
+
let executionError;
|
|
4324
|
+
const normalizedShardCount = Math.max(Math.trunc(shardCount), 1);
|
|
4325
|
+
const normalizedShardIndex = Math.min(Math.max(Math.trunc(shardIndex), 0), normalizedShardCount - 1);
|
|
4326
|
+
const ordinals = ownedOrdinals
|
|
4327
|
+
? [...ownedOrdinals]
|
|
4328
|
+
: (() => {
|
|
4329
|
+
const values = [];
|
|
4330
|
+
for (let ordinal = BigInt(normalizedShardIndex); ordinal < totalArrivals; ordinal += BigInt(normalizedShardCount)) {
|
|
4331
|
+
values.push(ordinal);
|
|
4332
|
+
}
|
|
4333
|
+
return values;
|
|
4334
|
+
})();
|
|
4335
|
+
if (ordinals.some((ordinal, index) => ordinal < 0n || ordinal >= totalArrivals
|
|
4336
|
+
|| (index > 0 && ordinal <= ordinals[index - 1]))) {
|
|
4337
|
+
throw new Error("Load Engine V2 owned arrival ordinals must be sorted, unique, and in range.");
|
|
4338
|
+
}
|
|
4339
|
+
if (segment) {
|
|
4340
|
+
segment.planned = BigInt(ordinals.length);
|
|
4341
|
+
}
|
|
4342
|
+
for (const ordinal of ordinals) {
|
|
4343
|
+
if (shouldStopNow())
|
|
4344
|
+
break;
|
|
4345
|
+
const index = Number(ordinal);
|
|
4346
|
+
const deadlineNs = segmentStartNs
|
|
4347
|
+
+ (deadlineOffsetsNs?.[index] ?? (0, load_engine_v2_js_1.loadEngineV2FixedDeadlineNs)(ordinal, rate, intervalNs));
|
|
4348
|
+
await delayUntilMonotonicDeadline(deadlineNs, cancellationToken);
|
|
4349
|
+
if (shouldStopNow()) {
|
|
4350
|
+
break;
|
|
4351
|
+
}
|
|
4352
|
+
const nowNs = process.hrtime.bigint();
|
|
4353
|
+
if (segment)
|
|
4354
|
+
segment.due += 1n;
|
|
4355
|
+
if (segment)
|
|
4356
|
+
telemetry?.recordDecisionLag(segment, nowNs - deadlineNs);
|
|
4357
|
+
if ((0, load_engine_v2_js_1.classifyLoadEngineV2Arrival)(nowNs, deadlineNs, tolerancesNs?.[index] ?? toleranceNs, true) === "scheduler_late") {
|
|
4358
|
+
schedulerLate += 1n;
|
|
4359
|
+
if (segment) {
|
|
4360
|
+
segment.dropped += 1n;
|
|
4361
|
+
incrementReason(segment.dropReasons, "scheduler_late");
|
|
4362
|
+
}
|
|
4363
|
+
continue;
|
|
4364
|
+
}
|
|
4365
|
+
const release = budget.tryAcquire();
|
|
4366
|
+
if (!release) {
|
|
4367
|
+
maxInFlight += 1n;
|
|
4368
|
+
if (segment) {
|
|
4369
|
+
segment.dropped += 1n;
|
|
4370
|
+
incrementReason(segment.dropReasons, "max_in_flight");
|
|
4371
|
+
}
|
|
4372
|
+
continue;
|
|
4373
|
+
}
|
|
4374
|
+
if (segment)
|
|
4375
|
+
segment.started += 1n;
|
|
4376
|
+
const instanceInfo = nextInstanceInfo();
|
|
4377
|
+
let task;
|
|
4378
|
+
task = (async () => {
|
|
4379
|
+
if (segment)
|
|
4380
|
+
telemetry?.recordStartLag(segment, process.hrtime.bigint() - deadlineNs);
|
|
4381
|
+
await runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, ordinal);
|
|
4382
|
+
})().catch((error) => {
|
|
4383
|
+
executionError ?? (executionError = error);
|
|
4384
|
+
}).finally(() => {
|
|
4385
|
+
if (segment)
|
|
4386
|
+
segment.completed += 1n;
|
|
4387
|
+
release();
|
|
4388
|
+
active.delete(task);
|
|
4389
|
+
});
|
|
4390
|
+
active.add(task);
|
|
4391
|
+
}
|
|
4392
|
+
while (active.size > 0) {
|
|
4393
|
+
await Promise.race(active);
|
|
4394
|
+
}
|
|
4395
|
+
if (segment) {
|
|
4396
|
+
segment.unreached = segment.planned > segment.due ? segment.planned - segment.due : 0n;
|
|
4397
|
+
segment.accountingComplete = segment.due + segment.unreached === segment.planned
|
|
4398
|
+
&& segment.started + segment.dropped === segment.due
|
|
4399
|
+
&& segment.completed === segment.started;
|
|
4400
|
+
}
|
|
4401
|
+
if (telemetry && segment) {
|
|
4402
|
+
telemetry.recordWarning("scheduler_late", segment, schedulerLate);
|
|
4403
|
+
telemetry.recordWarning("max_in_flight", segment, maxInFlight);
|
|
4404
|
+
}
|
|
4405
|
+
if (schedulerLate > 0n) {
|
|
4406
|
+
logger.warn(`Load Engine V2 warning scheduler_late: dropped ${schedulerLate.toString()} overdue arrivals for scenario ${scenarioName}.`);
|
|
4407
|
+
}
|
|
4408
|
+
if (maxInFlight > 0n) {
|
|
4409
|
+
logger.warn(`Load Engine V2 warning max_in_flight: dropped ${maxInFlight.toString()} arrivals for scenario ${scenarioName}; the process limit is ${budget.maxInFlight}.`);
|
|
4410
|
+
}
|
|
4411
|
+
if (executionError !== undefined) {
|
|
4412
|
+
throw executionError;
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
async function delayUntilMonotonicDeadline(deadlineNs, cancellationToken) {
|
|
4416
|
+
while (!cancellationToken.aborted) {
|
|
4417
|
+
const remainingNs = deadlineNs - process.hrtime.bigint();
|
|
4418
|
+
if (remainingNs <= 0n) {
|
|
4419
|
+
return;
|
|
4420
|
+
}
|
|
4421
|
+
const remainingMs = Number((remainingNs + 999999n) / 1000000n);
|
|
4422
|
+
await delayWithAbort(Math.max(1, Math.min(remainingMs, 50)), cancellationToken);
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
function secondsToNanoseconds(seconds, name) {
|
|
4426
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
4427
|
+
throw new RangeError(`${name} must be greater than zero.`);
|
|
4428
|
+
}
|
|
4429
|
+
const nanoseconds = Math.trunc(seconds * 1000000000);
|
|
4430
|
+
if (!Number.isSafeInteger(nanoseconds) || nanoseconds <= 0) {
|
|
4431
|
+
throw new RangeError(`${name} is outside the supported nanosecond range.`);
|
|
4432
|
+
}
|
|
4433
|
+
return BigInt(nanoseconds);
|
|
4434
|
+
}
|
|
4435
|
+
function buildV2RampTolerances(offsets, durationNs) {
|
|
4436
|
+
return offsets.map((offset, index) => {
|
|
4437
|
+
const quantum = offsets.length === 1
|
|
4438
|
+
? durationNs
|
|
4439
|
+
: index === 0
|
|
4440
|
+
? offsets[1] - offset
|
|
4441
|
+
: offset - offsets[index - 1];
|
|
4442
|
+
return minBigInt(100000000n, maxBigInt(2000000n, maxBigInt(1n, quantum) * 4n));
|
|
4443
|
+
});
|
|
4444
|
+
}
|
|
4445
|
+
function minBigInt(left, right) {
|
|
4446
|
+
return left < right ? left : right;
|
|
4447
|
+
}
|
|
4448
|
+
function maxBigInt(left, right) {
|
|
4449
|
+
return left > right ? left : right;
|
|
4450
|
+
}
|
|
3749
4451
|
async function executeScenarioRuntime(args) {
|
|
3750
|
-
const { scenario, scenarioIndex, scenarioCount, options, logger, nodeInfo, testInfo, policies, restartIterationMaxAttempts, allRegisteredMetrics, scenarioRuntimes, stepRuntimes, scenarioAccumulators, scenarioDurationsMs, stopTestState, testAbortController, executeScenarioInvocation, invokeBeforeScenario, invokeAfterScenario, invokeBeforeStep, invokeAfterStep } = args;
|
|
4452
|
+
const { scenario, scenarioIndex, scenarioCount, options, logger, nodeInfo, testInfo, policies, restartIterationMaxAttempts, allRegisteredMetrics, scenarioRuntimes, stepRuntimes, scenarioAccumulators, scenarioDurationsMs, stopTestState, testAbortController, loadEngineV2Budget, loadEngineV2Telemetry, iterationObservationReporter, iterationObservationRunId = testInfo.sessionId, iterationObservationResultOwnerId = "", iterationObservationProcessGroup = 0, executeScenarioInvocation, invokeBeforeScenario, invokeAfterScenario, invokeBeforeStep, invokeAfterStep } = args;
|
|
3751
4453
|
const scenarioStartedMs = Date.now();
|
|
3752
4454
|
const scenarioContextData = {};
|
|
3753
4455
|
const registeredMetrics = [];
|
|
3754
4456
|
const runtime = ensureScenarioRuntime(scenarioRuntimes, scenario.name);
|
|
3755
|
-
const accumulator = new ScenarioStatsAccumulator(scenario.name, scenarioIndex);
|
|
4457
|
+
const accumulator = new ScenarioStatsAccumulator(scenario.name, scenarioIndex, options.loadEngineContractVersion === 2);
|
|
3756
4458
|
scenarioAccumulators.set(scenario.name, accumulator);
|
|
3757
4459
|
const scenarioAbortController = new AbortController();
|
|
3758
4460
|
const scenarioCancellationToken = combineAbortSignals(testAbortController.signal, scenarioAbortController.signal);
|
|
@@ -3761,12 +4463,25 @@ async function executeScenarioRuntime(args) {
|
|
|
3761
4463
|
? Date.now() + Math.trunc(scenarioCompletionTimeoutSeconds * 1000)
|
|
3762
4464
|
: Number.POSITIVE_INFINITY;
|
|
3763
4465
|
const scenarioPartition = attachScenarioPartitionAliases({
|
|
3764
|
-
number: 0,
|
|
3765
|
-
count: 1
|
|
4466
|
+
number: options.loadEngineContractVersion === 2 ? Math.max(Math.trunc(options.clusterShardIndex ?? 0), 0) : 0,
|
|
4467
|
+
count: options.loadEngineContractVersion === 2 ? Math.max(Math.trunc(options.clusterShardCount ?? 1), 1) : 1
|
|
3766
4468
|
});
|
|
4469
|
+
const trafficMixV2 = options.loadEngineContractVersion === 2
|
|
4470
|
+
? scenario.__loadStrikeTrafficMixV2Metadata()
|
|
4471
|
+
: undefined;
|
|
4472
|
+
if (trafficMixV2) {
|
|
4473
|
+
const expectedSeedId = (0, load_engine_v2_js_1.buildLoadEngineV2TrafficMixSeedId)(trafficMixV2.declarationIndex, trafficMixV2.name);
|
|
4474
|
+
if (trafficMixV2.seedId !== expectedSeedId) {
|
|
4475
|
+
throw new Error(`Load Engine V2 traffic-mix seed metadata differs for scenario ${scenario.name}.`);
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
3767
4478
|
let stopScenario = false;
|
|
3768
4479
|
let invocationNumber = 0;
|
|
3769
4480
|
let instanceCounter = 0;
|
|
4481
|
+
let nextV1ObservationOrdinal = 0n;
|
|
4482
|
+
let nextV2FallbackOrdinal = BigInt(scenarioPartition.number);
|
|
4483
|
+
let nextObservationStepSortIndex = 0;
|
|
4484
|
+
const observationStepSortIndexes = new Map();
|
|
3770
4485
|
const shouldStopNow = () => stopTestState.value
|
|
3771
4486
|
|| stopScenario
|
|
3772
4487
|
|| scenarioCancellationToken.aborted
|
|
@@ -3800,7 +4515,7 @@ async function executeScenarioRuntime(args) {
|
|
|
3800
4515
|
runtime.maxLatencyMs = Math.max(runtime.maxLatencyMs, latencyMs);
|
|
3801
4516
|
accumulator.recordScenario(reply, observedLatencyMs);
|
|
3802
4517
|
};
|
|
3803
|
-
const recordStepReply = (stepName, reply, observedLatencyMs) => {
|
|
4518
|
+
const recordStepReply = (stepName, reply, observedLatencyMs, sortIndex) => {
|
|
3804
4519
|
const key = `${scenario.name}::${stepName}`;
|
|
3805
4520
|
const stepRuntime = ensureStepRuntime(stepRuntimes, key, scenario.name, stepName);
|
|
3806
4521
|
if (reply.isSuccess) {
|
|
@@ -3823,11 +4538,35 @@ async function executeScenarioRuntime(args) {
|
|
|
3823
4538
|
stepRuntime.maxLatencyMs = Math.max(stepRuntime.maxLatencyMs, latencyMs);
|
|
3824
4539
|
const statusCode = normalizeStatusCode(reply.statusCode, reply.isSuccess);
|
|
3825
4540
|
stepRuntime.statusCodes[statusCode] = (stepRuntime.statusCodes[statusCode] ?? 0) + 1;
|
|
3826
|
-
accumulator.recordStep(stepName, reply, observedLatencyMs);
|
|
4541
|
+
return accumulator.recordStep(stepName, reply, observedLatencyMs, sortIndex);
|
|
4542
|
+
};
|
|
4543
|
+
const resolveObservationStepSortIndex = (stepName) => {
|
|
4544
|
+
const existing = observationStepSortIndexes.get(stepName);
|
|
4545
|
+
if (existing !== undefined) {
|
|
4546
|
+
return existing;
|
|
4547
|
+
}
|
|
4548
|
+
nextObservationStepSortIndex += 1;
|
|
4549
|
+
observationStepSortIndexes.set(stepName, nextObservationStepSortIndex);
|
|
4550
|
+
return nextObservationStepSortIndex;
|
|
4551
|
+
};
|
|
4552
|
+
const nextObservationOrdinal = (explicit) => {
|
|
4553
|
+
if (explicit !== undefined) {
|
|
4554
|
+
return explicit;
|
|
4555
|
+
}
|
|
4556
|
+
if (options.loadEngineContractVersion === 2) {
|
|
4557
|
+
const ordinal = nextV2FallbackOrdinal;
|
|
4558
|
+
nextV2FallbackOrdinal += BigInt(scenarioPartition.count);
|
|
4559
|
+
return ordinal;
|
|
4560
|
+
}
|
|
4561
|
+
const ordinal = nextV1ObservationOrdinal;
|
|
4562
|
+
nextV1ObservationOrdinal += 1n;
|
|
4563
|
+
return ordinal;
|
|
3827
4564
|
};
|
|
3828
4565
|
const runSingleInvocation = async (operation, instanceData, instanceNumber, instanceId, recordScenarioResult) => {
|
|
3829
4566
|
invocationNumber += 1;
|
|
3830
4567
|
const runtimeRandom = createRuntimeRandom();
|
|
4568
|
+
const attemptSteps = [];
|
|
4569
|
+
const recordedSteps = [];
|
|
3831
4570
|
const context = {
|
|
3832
4571
|
scenarioName: scenario.name,
|
|
3833
4572
|
data: scenarioContextData,
|
|
@@ -3856,7 +4595,12 @@ async function executeScenarioRuntime(args) {
|
|
|
3856
4595
|
testAbortController.abort(stopTestState.reason);
|
|
3857
4596
|
},
|
|
3858
4597
|
recordStep: (stepName, reply, observedLatencyMs) => {
|
|
3859
|
-
|
|
4598
|
+
const sortIndex = resolveObservationStepSortIndex(stepName);
|
|
4599
|
+
recordedSteps.push({ stepName, reply, observedLatencyMs, sortIndex });
|
|
4600
|
+
return sortIndex;
|
|
4601
|
+
},
|
|
4602
|
+
recordStepObservation: (observation) => {
|
|
4603
|
+
attemptSteps.push(observation);
|
|
3860
4604
|
},
|
|
3861
4605
|
shouldStopScenario: () => stopScenario || scenarioCancellationToken.aborted,
|
|
3862
4606
|
shouldStopTest: () => stopTestState.value || scenarioCancellationToken.aborted,
|
|
@@ -3864,28 +4608,92 @@ async function executeScenarioRuntime(args) {
|
|
|
3864
4608
|
invokeAfterStep: async (stepName, reply) => invokeAfterStep(policies, scenario.name, stepName, reply)
|
|
3865
4609
|
};
|
|
3866
4610
|
attachScenarioContextAliases(context);
|
|
3867
|
-
const
|
|
4611
|
+
const startedUtcNs = (0, iteration_observations_js_1.utcNowNs)();
|
|
4612
|
+
const startedAtNs = process.hrtime.bigint();
|
|
3868
4613
|
const reply = await executeScenarioInvocation(scenario, context, operation);
|
|
3869
|
-
const
|
|
4614
|
+
const observedLatencyNs = maxBigInt(process.hrtime.bigint() - startedAtNs, 0n);
|
|
4615
|
+
const completedUtcNs = startedUtcNs + observedLatencyNs;
|
|
4616
|
+
const observedLatencyMs = Number(observedLatencyNs) / 1000000;
|
|
3870
4617
|
if (recordScenarioResult) {
|
|
3871
4618
|
recordScenarioReply(reply, observedLatencyMs);
|
|
3872
4619
|
}
|
|
3873
|
-
return {
|
|
4620
|
+
return {
|
|
4621
|
+
reply,
|
|
4622
|
+
observedLatencyMs,
|
|
4623
|
+
startedUtcNs,
|
|
4624
|
+
completedUtcNs,
|
|
4625
|
+
observedLatencyUs: observedLatencyNs / 1000n,
|
|
4626
|
+
reportedLatencyUs: normalizeRawObservationLatencyMicroseconds(resolveRecordedLatency(reply.customLatencyMs, observedLatencyMs)),
|
|
4627
|
+
steps: attemptSteps,
|
|
4628
|
+
recordedSteps
|
|
4629
|
+
};
|
|
4630
|
+
};
|
|
4631
|
+
const captureAttemptObservation = (operation, globalOrdinal, attemptIndex, isFinalAttempt, attempt, simulationIndex, simulationKind, iterationId, globalSecondaryOrdinal = 0n) => {
|
|
4632
|
+
if (!iterationObservationReporter?.enabled) {
|
|
4633
|
+
return;
|
|
4634
|
+
}
|
|
4635
|
+
iterationObservationReporter.capture((0, iteration_observations_js_1.createIterationObservation)({
|
|
4636
|
+
runId: iterationObservationRunId,
|
|
4637
|
+
sessionId: testInfo.sessionId,
|
|
4638
|
+
resultOwnerId: iterationObservationResultOwnerId,
|
|
4639
|
+
processGroup: iterationObservationProcessGroup,
|
|
4640
|
+
scenarioName: scenario.name,
|
|
4641
|
+
scenarioIndex,
|
|
4642
|
+
simulationIndex,
|
|
4643
|
+
simulationKind,
|
|
4644
|
+
phase: operation === "WarmUp" ? "warmup" : "bombing",
|
|
4645
|
+
globalOrdinal64: globalOrdinal,
|
|
4646
|
+
globalSecondaryOrdinal64: globalSecondaryOrdinal,
|
|
4647
|
+
...(iterationId ? { iterationId } : {}),
|
|
4648
|
+
shardIndex: scenarioPartition.number,
|
|
4649
|
+
shardCount: scenarioPartition.count,
|
|
4650
|
+
attemptIndex,
|
|
4651
|
+
isFinalAttempt,
|
|
4652
|
+
startedUtcNs: attempt.startedUtcNs,
|
|
4653
|
+
completedUtcNs: attempt.completedUtcNs,
|
|
4654
|
+
observedLatencyUs64: attempt.observedLatencyUs,
|
|
4655
|
+
reportedLatencyUs64: attempt.reportedLatencyUs,
|
|
4656
|
+
isSuccess: attempt.reply.isSuccess,
|
|
4657
|
+
statusCode: normalizeStatusCode(attempt.reply.statusCode, attempt.reply.isSuccess),
|
|
4658
|
+
sizeBytes64: normalizeHistogramInteger(Math.max(toNumber(attempt.reply.sizeBytes), 0), "Scenario response bytes"),
|
|
4659
|
+
steps: attempt.steps
|
|
4660
|
+
}));
|
|
3874
4661
|
};
|
|
3875
|
-
const runBombingInvocation = async (instanceData, instanceNumber, instanceId) => {
|
|
4662
|
+
const runBombingInvocation = async (instanceData, instanceNumber, instanceId, explicitGlobalOrdinal, simulationIndex = -1, simulationKind = "SingleInvocation", explicitSecondaryOrdinal = 0n) => {
|
|
3876
4663
|
if (shouldStopNow()) {
|
|
3877
4664
|
return;
|
|
3878
4665
|
}
|
|
4666
|
+
const globalOrdinal = nextObservationOrdinal(explicitGlobalOrdinal);
|
|
4667
|
+
const identityKind = canonicalLoadEngineV2InvocationIdentityKind(simulationKind);
|
|
4668
|
+
const iterationId = options.loadEngineContractVersion === 2
|
|
4669
|
+
&& explicitGlobalOrdinal !== undefined
|
|
4670
|
+
&& identityKind
|
|
4671
|
+
? (0, cluster_js_1.buildLoadEngineV2GlobalInvocationId)(iterationObservationRunId, scenarioIndex, simulationIndex, identityKind, globalOrdinal, explicitSecondaryOrdinal)
|
|
4672
|
+
: undefined;
|
|
3879
4673
|
let attempts = 0;
|
|
3880
4674
|
const maxAttempts = 1 + (scenario.shouldRestartIterationOnFail() ? restartIterationMaxAttempts : 0);
|
|
3881
4675
|
while (attempts < maxAttempts && !shouldStopNow()) {
|
|
3882
4676
|
attempts += 1;
|
|
3883
|
-
|
|
4677
|
+
let attempt;
|
|
4678
|
+
try {
|
|
4679
|
+
attempt = await runSingleInvocation("Bombing", instanceData, instanceNumber, instanceId, false);
|
|
4680
|
+
}
|
|
4681
|
+
catch (error) {
|
|
4682
|
+
if (error instanceof RuntimePolicyCallbackError) {
|
|
4683
|
+
stopScenario = true;
|
|
4684
|
+
scenarioAbortController.abort(error);
|
|
4685
|
+
}
|
|
4686
|
+
throw error;
|
|
4687
|
+
}
|
|
3884
4688
|
const shouldRetry = !attempt.reply.isSuccess
|
|
3885
4689
|
&& scenario.shouldRestartIterationOnFail()
|
|
3886
4690
|
&& attempts < maxAttempts
|
|
3887
4691
|
&& !shouldStopNow();
|
|
4692
|
+
captureAttemptObservation("Bombing", globalOrdinal, attempts - 1, !shouldRetry, attempt, simulationIndex, simulationKind, iterationId, explicitSecondaryOrdinal);
|
|
3888
4693
|
if (!shouldRetry) {
|
|
4694
|
+
for (const step of attempt.recordedSteps) {
|
|
4695
|
+
recordStepReply(step.stepName, step.reply, step.observedLatencyMs, step.sortIndex);
|
|
4696
|
+
}
|
|
3889
4697
|
recordScenarioReply(attempt.reply, attempt.observedLatencyMs);
|
|
3890
4698
|
if (scenario.getMaxFailCount() > 0 && runtime.allFailCount >= scenario.getMaxFailCount()) {
|
|
3891
4699
|
stopScenario = true;
|
|
@@ -3905,30 +4713,282 @@ async function executeScenarioRuntime(args) {
|
|
|
3905
4713
|
const endMs = Date.now() + Math.trunc(warmUpDurationSeconds * 1000);
|
|
3906
4714
|
const instanceInfo = nextInstanceInfo();
|
|
3907
4715
|
while (Date.now() < endMs && !shouldStopNow()) {
|
|
3908
|
-
|
|
4716
|
+
const globalOrdinal = nextObservationOrdinal();
|
|
4717
|
+
const attempt = await runSingleInvocation("WarmUp", instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, false);
|
|
4718
|
+
captureAttemptObservation("WarmUp", globalOrdinal, 0, true, attempt, -1, "SingleInvocation");
|
|
3909
4719
|
}
|
|
3910
4720
|
};
|
|
3911
|
-
const
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
const
|
|
3916
|
-
const
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
4721
|
+
const executeV2TimedConstant = async (copies, durationNs, ramping, runSimulationInvocation, segment) => {
|
|
4722
|
+
if (!loadEngineV2Budget) {
|
|
4723
|
+
return;
|
|
4724
|
+
}
|
|
4725
|
+
const offsets = ramping ? (0, load_engine_v2_js_1.planRampingConstantDeadlines)(copies, durationNs) : Array(copies).fill(0n);
|
|
4726
|
+
const ownedWorkerSlots = trafficMixV2
|
|
4727
|
+
? (0, load_engine_v2_js_1.loadEngineV2TrafficMixOwnedUnits)(BigInt(copies), trafficMixV2.shareWeights, trafficMixV2.laneIndex, scenarioPartition.number, scenarioPartition.count)
|
|
4728
|
+
: offsets.flatMap((_offset, workerSlot) => workerSlot % scenarioPartition.count === scenarioPartition.number
|
|
4729
|
+
? [{ laneOrdinal: BigInt(workerSlot), globalRank: BigInt(workerSlot) }]
|
|
4730
|
+
: []);
|
|
4731
|
+
const quantum = ramping ? maxBigInt(1n, durationNs / BigInt(copies)) : 1n;
|
|
4732
|
+
const toleranceNs = minBigInt(100000000n, maxBigInt(2000000n, quantum * 4n));
|
|
4733
|
+
const startNs = process.hrtime.bigint();
|
|
4734
|
+
const endNs = startNs + durationNs;
|
|
4735
|
+
const active = new Set();
|
|
4736
|
+
let schedulerLate = 0;
|
|
4737
|
+
let unavailable = 0;
|
|
4738
|
+
let executionError;
|
|
4739
|
+
if (segment) {
|
|
4740
|
+
segment.requestedWorkers = BigInt(ownedWorkerSlots.length);
|
|
4741
|
+
}
|
|
4742
|
+
for (const unit of ownedWorkerSlots) {
|
|
4743
|
+
const offsetNs = offsets[Number(unit.globalRank)];
|
|
4744
|
+
await delayUntilMonotonicDeadline(startNs + offsetNs, scenarioCancellationToken);
|
|
4745
|
+
if (shouldStopNow()) {
|
|
4746
|
+
break;
|
|
4747
|
+
}
|
|
4748
|
+
const decisionNowNs = process.hrtime.bigint();
|
|
4749
|
+
if (segment)
|
|
4750
|
+
loadEngineV2Telemetry?.recordDecisionLag(segment, decisionNowNs - (startNs + offsetNs));
|
|
4751
|
+
if (decisionNowNs - (startNs + offsetNs) > toleranceNs) {
|
|
4752
|
+
schedulerLate += 1;
|
|
4753
|
+
if (segment) {
|
|
4754
|
+
segment.unavailableWorkers += 1n;
|
|
4755
|
+
incrementReason(segment.unavailableWorkerReasons, "scheduler_late");
|
|
4756
|
+
}
|
|
4757
|
+
continue;
|
|
4758
|
+
}
|
|
4759
|
+
const release = loadEngineV2Budget.tryAcquire();
|
|
4760
|
+
if (!release) {
|
|
4761
|
+
unavailable += 1;
|
|
4762
|
+
if (segment) {
|
|
4763
|
+
segment.unavailableWorkers += 1n;
|
|
4764
|
+
incrementReason(segment.unavailableWorkerReasons, "max_in_flight");
|
|
4765
|
+
}
|
|
4766
|
+
continue;
|
|
4767
|
+
}
|
|
4768
|
+
if (segment)
|
|
4769
|
+
segment.startedWorkers += 1n;
|
|
4770
|
+
const instanceInfo = nextInstanceInfo();
|
|
4771
|
+
let task;
|
|
4772
|
+
task = (async () => {
|
|
4773
|
+
if (segment) {
|
|
4774
|
+
loadEngineV2Telemetry?.recordStartLag(segment, process.hrtime.bigint() - (startNs + offsetNs));
|
|
4775
|
+
}
|
|
4776
|
+
let completedSinceYield = 0;
|
|
4777
|
+
let workerIterationIndex = 0n;
|
|
4778
|
+
while (process.hrtime.bigint() < endNs && !shouldStopNow()) {
|
|
4779
|
+
if (segment) {
|
|
4780
|
+
segment.planned += 1n;
|
|
4781
|
+
segment.due += 1n;
|
|
4782
|
+
segment.started += 1n;
|
|
4783
|
+
}
|
|
4784
|
+
await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, unit.globalRank, workerIterationIndex);
|
|
4785
|
+
workerIterationIndex += 1n;
|
|
4786
|
+
if (segment)
|
|
4787
|
+
segment.completed += 1n;
|
|
4788
|
+
completedSinceYield += 1;
|
|
4789
|
+
if (completedSinceYield >= 64) {
|
|
4790
|
+
completedSinceYield = 0;
|
|
4791
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
})().catch((error) => {
|
|
4795
|
+
executionError ?? (executionError = error);
|
|
4796
|
+
}).finally(() => {
|
|
4797
|
+
release();
|
|
4798
|
+
active.delete(task);
|
|
4799
|
+
});
|
|
4800
|
+
active.add(task);
|
|
4801
|
+
}
|
|
4802
|
+
while (active.size > 0) {
|
|
4803
|
+
await Promise.race(active);
|
|
4804
|
+
}
|
|
4805
|
+
if (segment) {
|
|
4806
|
+
segment.accountingComplete = segment.completed === segment.started
|
|
4807
|
+
&& segment.started === segment.due
|
|
4808
|
+
&& segment.due === segment.planned
|
|
4809
|
+
&& segment.startedWorkers + segment.unavailableWorkers === segment.requestedWorkers;
|
|
4810
|
+
loadEngineV2Telemetry?.recordWarning("scheduler_late", segment, BigInt(schedulerLate));
|
|
4811
|
+
loadEngineV2Telemetry?.recordWarning("max_in_flight", segment, BigInt(unavailable));
|
|
4812
|
+
}
|
|
4813
|
+
if (schedulerLate > 0) {
|
|
4814
|
+
logger.warn(`Load Engine V2 warning scheduler_late: ${schedulerLate} constant worker slots were unavailable for scenario ${scenario.name}.`);
|
|
4815
|
+
}
|
|
4816
|
+
if (unavailable > 0) {
|
|
4817
|
+
logger.warn(`Load Engine V2 warning max_in_flight: ${unavailable} constant worker slots were unavailable for scenario ${scenario.name}.`);
|
|
4818
|
+
}
|
|
4819
|
+
if (executionError !== undefined) {
|
|
4820
|
+
throw executionError;
|
|
4821
|
+
}
|
|
4822
|
+
};
|
|
4823
|
+
const executeV2IterationsConstant = async (copies, iterations, runSimulationInvocation, segment) => {
|
|
4824
|
+
if (!loadEngineV2Budget || iterations <= 0) {
|
|
4825
|
+
return;
|
|
4826
|
+
}
|
|
4827
|
+
let activeShardCount;
|
|
4828
|
+
let ownedWorkerSlots;
|
|
4829
|
+
let ownedIterations;
|
|
4830
|
+
if (trafficMixV2) {
|
|
4831
|
+
const laneCopies = (0, load_engine_v2_js_1.loadEngineV2TrafficMixLaneUnitCount)(BigInt(copies), trafficMixV2.shareWeights, trafficMixV2.laneIndex);
|
|
4832
|
+
const laneIterations = (0, load_engine_v2_js_1.loadEngineV2TrafficMixLaneUnitCount)(BigInt(iterations), trafficMixV2.shareWeights, trafficMixV2.laneIndex);
|
|
4833
|
+
if (laneIterations === 0n) {
|
|
4834
|
+
if (segment) {
|
|
4835
|
+
segment.requestedWorkers = 0n;
|
|
4836
|
+
segment.accountingComplete = true;
|
|
4837
|
+
}
|
|
4838
|
+
return;
|
|
4839
|
+
}
|
|
4840
|
+
if (laneCopies === 0n) {
|
|
4841
|
+
throw new Error("Load Engine V2 traffic-mix lane owns iterations but no constant worker slot.");
|
|
4842
|
+
}
|
|
4843
|
+
activeShardCount = Math.min(scenarioPartition.count, Number(laneCopies), Number(laneIterations));
|
|
4844
|
+
if (scenarioPartition.number >= activeShardCount) {
|
|
4845
|
+
if (segment) {
|
|
4846
|
+
segment.requestedWorkers = 0n;
|
|
4847
|
+
segment.accountingComplete = true;
|
|
4848
|
+
}
|
|
4849
|
+
return;
|
|
4850
|
+
}
|
|
4851
|
+
ownedWorkerSlots = (0, load_engine_v2_js_1.loadEngineV2TrafficMixOwnedUnits)(BigInt(copies), trafficMixV2.shareWeights, trafficMixV2.laneIndex, scenarioPartition.number, activeShardCount);
|
|
4852
|
+
ownedIterations = (0, load_engine_v2_js_1.loadEngineV2TrafficMixOwnedUnits)(BigInt(iterations), trafficMixV2.shareWeights, trafficMixV2.laneIndex, scenarioPartition.number, activeShardCount);
|
|
4853
|
+
}
|
|
4854
|
+
else {
|
|
4855
|
+
activeShardCount = Math.min(scenarioPartition.count, copies, iterations);
|
|
4856
|
+
if (scenarioPartition.number >= activeShardCount) {
|
|
4857
|
+
return;
|
|
4858
|
+
}
|
|
4859
|
+
ownedWorkerSlots = Array.from({ length: copies }, (_value, slot) => slot)
|
|
4860
|
+
.filter((slot) => slot % activeShardCount === scenarioPartition.number)
|
|
4861
|
+
.map((slot) => ({ laneOrdinal: BigInt(slot), globalRank: BigInt(slot) }));
|
|
4862
|
+
ownedIterations = Array.from({ length: iterations }, (_value, ordinal) => ordinal)
|
|
4863
|
+
.filter((ordinal) => ordinal % activeShardCount === scenarioPartition.number)
|
|
4864
|
+
.map((ordinal) => ({ laneOrdinal: BigInt(ordinal), globalRank: BigInt(ordinal) }));
|
|
4865
|
+
}
|
|
4866
|
+
if (scenarioPartition.number >= activeShardCount) {
|
|
4867
|
+
return;
|
|
4868
|
+
}
|
|
4869
|
+
const targetWorkers = Math.min(ownedWorkerSlots.length, ownedIterations.length);
|
|
4870
|
+
if (targetWorkers <= 0) {
|
|
4871
|
+
if (segment) {
|
|
4872
|
+
segment.requestedWorkers = 0n;
|
|
4873
|
+
segment.accountingComplete = true;
|
|
4874
|
+
}
|
|
4875
|
+
return;
|
|
4876
|
+
}
|
|
4877
|
+
if (segment) {
|
|
4878
|
+
segment.planned = BigInt(ownedIterations.length);
|
|
4879
|
+
segment.requestedWorkers = BigInt(targetWorkers);
|
|
4880
|
+
}
|
|
4881
|
+
const releases = [];
|
|
4882
|
+
let firstRelease;
|
|
4883
|
+
while (!firstRelease && !shouldStopNow()) {
|
|
4884
|
+
firstRelease = loadEngineV2Budget.tryAcquire();
|
|
4885
|
+
if (!firstRelease) {
|
|
4886
|
+
await delayWithAbort(1, scenarioCancellationToken);
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
if (!firstRelease) {
|
|
4890
|
+
if (segment) {
|
|
4891
|
+
segment.unreached = segment.planned;
|
|
4892
|
+
segment.unavailableWorkers = segment.requestedWorkers;
|
|
4893
|
+
if (segment.unavailableWorkers > 0n) {
|
|
4894
|
+
incrementReason(segment.unavailableWorkerReasons, "cancelled", segment.unavailableWorkers);
|
|
4895
|
+
}
|
|
4896
|
+
segment.accountingComplete = true;
|
|
4897
|
+
}
|
|
4898
|
+
return;
|
|
4899
|
+
}
|
|
4900
|
+
releases.push(firstRelease);
|
|
4901
|
+
for (let worker = 1; worker < targetWorkers; worker += 1) {
|
|
4902
|
+
const release = loadEngineV2Budget.tryAcquire();
|
|
4903
|
+
if (release) {
|
|
4904
|
+
releases.push(release);
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
if (releases.length < targetWorkers) {
|
|
4908
|
+
logger.warn(`Load Engine V2 warning max_in_flight: requested ${targetWorkers} constant workers but started ${releases.length} for scenario ${scenario.name}; all iterations will still run.`);
|
|
4909
|
+
}
|
|
4910
|
+
if (segment) {
|
|
4911
|
+
segment.startedWorkers = BigInt(releases.length);
|
|
4912
|
+
segment.unavailableWorkers = BigInt(targetWorkers - releases.length);
|
|
4913
|
+
if (segment.unavailableWorkers > 0n) {
|
|
4914
|
+
incrementReason(segment.unavailableWorkerReasons, "max_in_flight", segment.unavailableWorkers);
|
|
4915
|
+
loadEngineV2Telemetry?.recordWarning("max_in_flight", segment, segment.unavailableWorkers);
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
let nextIterationIndex = 0;
|
|
4919
|
+
const tasks = releases.map(async (release) => {
|
|
4920
|
+
const instanceInfo = nextInstanceInfo();
|
|
4921
|
+
try {
|
|
4922
|
+
while (!shouldStopNow()) {
|
|
4923
|
+
const unit = ownedIterations[nextIterationIndex];
|
|
4924
|
+
nextIterationIndex += 1;
|
|
4925
|
+
if (!unit) {
|
|
4926
|
+
break;
|
|
4927
|
+
}
|
|
4928
|
+
if (segment) {
|
|
4929
|
+
segment.due += 1n;
|
|
4930
|
+
segment.started += 1n;
|
|
4931
|
+
}
|
|
4932
|
+
await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, unit.globalRank, 0n);
|
|
4933
|
+
if (segment)
|
|
4934
|
+
segment.completed += 1n;
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
finally {
|
|
4938
|
+
release();
|
|
4939
|
+
}
|
|
4940
|
+
});
|
|
4941
|
+
await waitForScenarioTasks(tasks, scenario.name, scenarioCompletionTimeoutSeconds, logger, scenarioCancellationToken);
|
|
4942
|
+
if (segment) {
|
|
4943
|
+
segment.unreached = segment.planned > segment.due ? segment.planned - segment.due : 0n;
|
|
4944
|
+
segment.accountingComplete = segment.due + segment.unreached === segment.planned
|
|
4945
|
+
&& segment.started === segment.due
|
|
4946
|
+
&& segment.completed === segment.started
|
|
4947
|
+
&& segment.startedWorkers + segment.unavailableWorkers === segment.requestedWorkers;
|
|
4948
|
+
}
|
|
4949
|
+
};
|
|
4950
|
+
const executeSimulationAsync = async (simulation, simulationIndex) => {
|
|
4951
|
+
const descriptor = trafficMixV2
|
|
4952
|
+
? trafficMixV2.originalSimulations[simulationIndex]
|
|
4953
|
+
: simulation;
|
|
4954
|
+
if (!descriptor) {
|
|
4955
|
+
throw new Error(`Load Engine V2 traffic-mix phase ${simulationIndex} is missing its global descriptor.`);
|
|
4956
|
+
}
|
|
4957
|
+
const kind = String(descriptor.Kind ?? "");
|
|
4958
|
+
const simulationKind = kind || "SingleInvocation";
|
|
4959
|
+
const runSimulationInvocation = (instanceData, instanceNumber, instanceId, explicitGlobalOrdinal, explicitSecondaryOrdinal = 0n) => runBombingInvocation(instanceData, instanceNumber, instanceId, explicitGlobalOrdinal, simulationIndex, simulationKind, explicitSecondaryOrdinal);
|
|
4960
|
+
if (options.loadEngineContractVersion === 2) {
|
|
4961
|
+
nextV2FallbackOrdinal = BigInt(scenarioPartition.number);
|
|
4962
|
+
}
|
|
4963
|
+
const weight = trafficMixV2 ? 1 : Math.max(scenario.getWeight(), 1);
|
|
4964
|
+
const rate = applyScenarioWeight(toInt(descriptor.Rate), weight);
|
|
4965
|
+
const minRate = applyScenarioWeight(toInt(descriptor.MinRate), weight);
|
|
4966
|
+
const maxRate = applyScenarioWeight(toInt(descriptor.MaxRate), weight);
|
|
4967
|
+
const copies = Math.max(applyScenarioWeight(Math.max(toInt(descriptor.Copies), 1), weight), 1);
|
|
4968
|
+
const iterations = applyScenarioWeight(Math.max(toInt(descriptor.Iterations), 0), weight);
|
|
4969
|
+
const intervalMs = Math.max(Math.trunc(Math.max(toNumber(descriptor.IntervalSeconds), 0) * 1000), 0);
|
|
4970
|
+
const duringMs = Math.max(Math.trunc(Math.max(toNumber(descriptor.DuringSeconds), 0) * 1000), 0);
|
|
4971
|
+
accumulator.setLoadSimulation(kind, resolveLoadSimulationValue(descriptor, weight));
|
|
3922
4972
|
accumulator.setCurrentOperation("Bombing");
|
|
4973
|
+
const schedulerSegment = options.loadEngineContractVersion === 2
|
|
4974
|
+
? loadEngineV2Telemetry?.createSegment(scenario.name, scenarioIndex, simulationIndex, kind, scenarioPartition.number, scenarioPartition.count)
|
|
4975
|
+
: undefined;
|
|
4976
|
+
const trafficMixOwnedOrdinals = (total) => trafficMixV2
|
|
4977
|
+
? (0, load_engine_v2_js_1.loadEngineV2TrafficMixOwnedUnits)(total, trafficMixV2.shareWeights, trafficMixV2.laneIndex, scenarioPartition.number, scenarioPartition.count).map((unit) => unit.globalRank)
|
|
4978
|
+
: undefined;
|
|
3923
4979
|
if (kind === "KeepConstant") {
|
|
3924
4980
|
if (duringMs <= 0) {
|
|
3925
4981
|
return;
|
|
3926
4982
|
}
|
|
4983
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
4984
|
+
await executeV2TimedConstant(copies, secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "KeepConstant duration"), false, runSimulationInvocation, schedulerSegment);
|
|
4985
|
+
return;
|
|
4986
|
+
}
|
|
3927
4987
|
const endMs = Date.now() + duringMs;
|
|
3928
4988
|
const tasks = Array.from({ length: copies }, async () => {
|
|
3929
4989
|
const instanceInfo = nextInstanceInfo();
|
|
3930
4990
|
while (Date.now() < endMs && !shouldStopNow()) {
|
|
3931
|
-
await
|
|
4991
|
+
await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
|
|
3932
4992
|
}
|
|
3933
4993
|
});
|
|
3934
4994
|
await waitForScenarioTasks(tasks, scenario.name, scenarioCompletionTimeoutSeconds, logger, scenarioCancellationToken);
|
|
@@ -3938,6 +4998,10 @@ async function executeScenarioRuntime(args) {
|
|
|
3938
4998
|
if (duringMs <= 0) {
|
|
3939
4999
|
return;
|
|
3940
5000
|
}
|
|
5001
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5002
|
+
await executeV2TimedConstant(copies, secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "RampingConstant duration"), true, runSimulationInvocation, schedulerSegment);
|
|
5003
|
+
return;
|
|
5004
|
+
}
|
|
3941
5005
|
const tasks = [];
|
|
3942
5006
|
const endMs = Date.now() + duringMs;
|
|
3943
5007
|
const startIntervalMs = copies <= 1 ? 0 : Math.max(Math.trunc(duringMs / copies), 0);
|
|
@@ -3945,7 +5009,7 @@ async function executeScenarioRuntime(args) {
|
|
|
3945
5009
|
const instanceInfo = nextInstanceInfo();
|
|
3946
5010
|
tasks.push((async () => {
|
|
3947
5011
|
while (Date.now() < endMs && !shouldStopNow()) {
|
|
3948
|
-
await
|
|
5012
|
+
await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
|
|
3949
5013
|
}
|
|
3950
5014
|
})());
|
|
3951
5015
|
if (copy < copies && startIntervalMs > 0) {
|
|
@@ -3959,12 +5023,34 @@ async function executeScenarioRuntime(args) {
|
|
|
3959
5023
|
if (duringMs <= 0 || rate <= 0 || intervalMs <= 0) {
|
|
3960
5024
|
return;
|
|
3961
5025
|
}
|
|
5026
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5027
|
+
const intervalNs = secondsToNanoseconds(toNumber(descriptor.IntervalSeconds), "Inject interval");
|
|
5028
|
+
const durationNs = secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "Inject duration");
|
|
5029
|
+
await executeV2FixedArrivals({
|
|
5030
|
+
rate,
|
|
5031
|
+
intervalNs,
|
|
5032
|
+
totalArrivals: (0, load_engine_v2_js_1.loadEngineV2FixedArrivalCount)(rate, intervalNs, durationNs),
|
|
5033
|
+
budget: loadEngineV2Budget,
|
|
5034
|
+
cancellationToken: scenarioCancellationToken,
|
|
5035
|
+
shouldStopNow,
|
|
5036
|
+
nextInstanceInfo,
|
|
5037
|
+
runBombingInvocation: runSimulationInvocation,
|
|
5038
|
+
logger,
|
|
5039
|
+
scenarioName: scenario.name,
|
|
5040
|
+
shardIndex: scenarioPartition.number,
|
|
5041
|
+
shardCount: scenarioPartition.count,
|
|
5042
|
+
ownedOrdinals: trafficMixOwnedOrdinals((0, load_engine_v2_js_1.loadEngineV2FixedArrivalCount)(rate, intervalNs, durationNs)),
|
|
5043
|
+
telemetry: loadEngineV2Telemetry,
|
|
5044
|
+
segment: schedulerSegment
|
|
5045
|
+
});
|
|
5046
|
+
return;
|
|
5047
|
+
}
|
|
3962
5048
|
const pending = [];
|
|
3963
5049
|
const endMs = Date.now() + duringMs;
|
|
3964
5050
|
while (Date.now() < endMs && !shouldStopNow()) {
|
|
3965
5051
|
for (let index = 0; index < rate; index += 1) {
|
|
3966
5052
|
const instanceInfo = nextInstanceInfo();
|
|
3967
|
-
pending.push(
|
|
5053
|
+
pending.push(runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
|
|
3968
5054
|
}
|
|
3969
5055
|
await delayWithAbort(intervalMs, scenarioCancellationToken);
|
|
3970
5056
|
}
|
|
@@ -3975,6 +5061,31 @@ async function executeScenarioRuntime(args) {
|
|
|
3975
5061
|
if (duringMs <= 0 || rate <= 0 || intervalMs <= 0) {
|
|
3976
5062
|
return;
|
|
3977
5063
|
}
|
|
5064
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5065
|
+
const intervalNs = secondsToNanoseconds(toNumber(descriptor.IntervalSeconds), "RampingInject interval");
|
|
5066
|
+
const durationNs = secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "RampingInject duration");
|
|
5067
|
+
const offsets = (0, load_engine_v2_js_1.planRampingInjectionDeadlines)(rate, intervalNs, durationNs);
|
|
5068
|
+
await executeV2FixedArrivals({
|
|
5069
|
+
rate,
|
|
5070
|
+
intervalNs,
|
|
5071
|
+
totalArrivals: BigInt(offsets.length),
|
|
5072
|
+
deadlineOffsetsNs: offsets,
|
|
5073
|
+
tolerancesNs: buildV2RampTolerances(offsets, durationNs),
|
|
5074
|
+
budget: loadEngineV2Budget,
|
|
5075
|
+
cancellationToken: scenarioCancellationToken,
|
|
5076
|
+
shouldStopNow,
|
|
5077
|
+
nextInstanceInfo,
|
|
5078
|
+
runBombingInvocation: runSimulationInvocation,
|
|
5079
|
+
logger,
|
|
5080
|
+
scenarioName: scenario.name,
|
|
5081
|
+
shardIndex: scenarioPartition.number,
|
|
5082
|
+
shardCount: scenarioPartition.count,
|
|
5083
|
+
ownedOrdinals: trafficMixOwnedOrdinals(BigInt(offsets.length)),
|
|
5084
|
+
telemetry: loadEngineV2Telemetry,
|
|
5085
|
+
segment: schedulerSegment
|
|
5086
|
+
});
|
|
5087
|
+
return;
|
|
5088
|
+
}
|
|
3978
5089
|
const pending = [];
|
|
3979
5090
|
const startedAtMs = Date.now();
|
|
3980
5091
|
const endMs = startedAtMs + duringMs;
|
|
@@ -3984,7 +5095,7 @@ async function executeScenarioRuntime(args) {
|
|
|
3984
5095
|
const currentRate = Math.max(1, Math.ceil(rate * progress));
|
|
3985
5096
|
for (let index = 0; index < currentRate; index += 1) {
|
|
3986
5097
|
const instanceInfo = nextInstanceInfo();
|
|
3987
|
-
pending.push(
|
|
5098
|
+
pending.push(runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
|
|
3988
5099
|
}
|
|
3989
5100
|
await delayWithAbort(intervalMs, scenarioCancellationToken);
|
|
3990
5101
|
}
|
|
@@ -3995,6 +5106,37 @@ async function executeScenarioRuntime(args) {
|
|
|
3995
5106
|
if (duringMs <= 0 || maxRate <= 0 || intervalMs <= 0) {
|
|
3996
5107
|
return;
|
|
3997
5108
|
}
|
|
5109
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5110
|
+
const intervalNs = secondsToNanoseconds(toNumber(descriptor.IntervalSeconds), "InjectRandom interval");
|
|
5111
|
+
const durationNs = secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "InjectRandom duration");
|
|
5112
|
+
const descriptorSeed = (0, load_engine_v2_js_1.fnv1a32)(trafficMixV2
|
|
5113
|
+
? `traffic-mix\n${trafficMixV2.seedId}\n${simulationIndex}`
|
|
5114
|
+
: `${scenario.name}\n${simulationIndex}`);
|
|
5115
|
+
const rotated = ((descriptorSeed << 13) | (descriptorSeed >>> 19)) >>> 0;
|
|
5116
|
+
const seed = ((0, load_engine_v2_js_1.fnv1a32)(testInfo.sessionId) ^ rotated) >>> 0;
|
|
5117
|
+
const offsets = (0, load_engine_v2_js_1.planRandomInjectionDeadlines)(Math.max(0, minRate), maxRate, intervalNs, durationNs, seed);
|
|
5118
|
+
const tolerance = (0, load_engine_v2_js_1.loadEngineV2LatenessToleranceNs)(Math.max(1, maxRate), intervalNs);
|
|
5119
|
+
await executeV2FixedArrivals({
|
|
5120
|
+
rate: Math.max(1, maxRate),
|
|
5121
|
+
intervalNs,
|
|
5122
|
+
totalArrivals: BigInt(offsets.length),
|
|
5123
|
+
deadlineOffsetsNs: offsets,
|
|
5124
|
+
tolerancesNs: offsets.map(() => tolerance),
|
|
5125
|
+
budget: loadEngineV2Budget,
|
|
5126
|
+
cancellationToken: scenarioCancellationToken,
|
|
5127
|
+
shouldStopNow,
|
|
5128
|
+
nextInstanceInfo,
|
|
5129
|
+
runBombingInvocation: runSimulationInvocation,
|
|
5130
|
+
logger,
|
|
5131
|
+
scenarioName: scenario.name,
|
|
5132
|
+
shardIndex: scenarioPartition.number,
|
|
5133
|
+
shardCount: scenarioPartition.count,
|
|
5134
|
+
ownedOrdinals: trafficMixOwnedOrdinals(BigInt(offsets.length)),
|
|
5135
|
+
telemetry: loadEngineV2Telemetry,
|
|
5136
|
+
segment: schedulerSegment
|
|
5137
|
+
});
|
|
5138
|
+
return;
|
|
5139
|
+
}
|
|
3998
5140
|
const pending = [];
|
|
3999
5141
|
const normalizedMinRate = Math.max(1, minRate);
|
|
4000
5142
|
const normalizedMaxRate = Math.max(normalizedMinRate, maxRate);
|
|
@@ -4003,7 +5145,7 @@ async function executeScenarioRuntime(args) {
|
|
|
4003
5145
|
const currentRate = randomIntInclusive(normalizedMinRate, normalizedMaxRate);
|
|
4004
5146
|
for (let index = 0; index < currentRate; index += 1) {
|
|
4005
5147
|
const instanceInfo = nextInstanceInfo();
|
|
4006
|
-
pending.push(
|
|
5148
|
+
pending.push(runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
|
|
4007
5149
|
}
|
|
4008
5150
|
await delayWithAbort(intervalMs, scenarioCancellationToken);
|
|
4009
5151
|
}
|
|
@@ -4014,6 +5156,10 @@ async function executeScenarioRuntime(args) {
|
|
|
4014
5156
|
if (iterations <= 0) {
|
|
4015
5157
|
return;
|
|
4016
5158
|
}
|
|
5159
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5160
|
+
await executeV2IterationsConstant(copies, iterations, runSimulationInvocation, schedulerSegment);
|
|
5161
|
+
return;
|
|
5162
|
+
}
|
|
4017
5163
|
let remaining = iterations;
|
|
4018
5164
|
const tasks = Array.from({ length: copies }, async () => {
|
|
4019
5165
|
const instanceInfo = nextInstanceInfo();
|
|
@@ -4022,7 +5168,7 @@ async function executeScenarioRuntime(args) {
|
|
|
4022
5168
|
if (remaining < 0) {
|
|
4023
5169
|
break;
|
|
4024
5170
|
}
|
|
4025
|
-
await
|
|
5171
|
+
await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
|
|
4026
5172
|
}
|
|
4027
5173
|
});
|
|
4028
5174
|
await waitForScenarioTasks(tasks, scenario.name, scenarioCompletionTimeoutSeconds, logger, scenarioCancellationToken);
|
|
@@ -4032,13 +5178,33 @@ async function executeScenarioRuntime(args) {
|
|
|
4032
5178
|
if (iterations <= 0 || rate <= 0 || intervalMs <= 0) {
|
|
4033
5179
|
return;
|
|
4034
5180
|
}
|
|
5181
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5182
|
+
await executeV2FixedArrivals({
|
|
5183
|
+
rate,
|
|
5184
|
+
intervalNs: secondsToNanoseconds(toNumber(descriptor.IntervalSeconds), "IterationsForInject interval"),
|
|
5185
|
+
totalArrivals: BigInt(iterations),
|
|
5186
|
+
budget: loadEngineV2Budget,
|
|
5187
|
+
cancellationToken: scenarioCancellationToken,
|
|
5188
|
+
shouldStopNow,
|
|
5189
|
+
nextInstanceInfo,
|
|
5190
|
+
runBombingInvocation: runSimulationInvocation,
|
|
5191
|
+
logger,
|
|
5192
|
+
scenarioName: scenario.name,
|
|
5193
|
+
shardIndex: scenarioPartition.number,
|
|
5194
|
+
shardCount: scenarioPartition.count,
|
|
5195
|
+
ownedOrdinals: trafficMixOwnedOrdinals(BigInt(iterations)),
|
|
5196
|
+
telemetry: loadEngineV2Telemetry,
|
|
5197
|
+
segment: schedulerSegment
|
|
5198
|
+
});
|
|
5199
|
+
return;
|
|
5200
|
+
}
|
|
4035
5201
|
const pending = [];
|
|
4036
5202
|
let remaining = iterations;
|
|
4037
5203
|
while (remaining > 0 && !shouldStopNow()) {
|
|
4038
5204
|
const count = Math.min(rate, remaining);
|
|
4039
5205
|
for (let index = 0; index < count; index += 1) {
|
|
4040
5206
|
const instanceInfo = nextInstanceInfo();
|
|
4041
|
-
pending.push(
|
|
5207
|
+
pending.push(runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
|
|
4042
5208
|
}
|
|
4043
5209
|
remaining -= count;
|
|
4044
5210
|
if (remaining > 0) {
|
|
@@ -4050,10 +5216,19 @@ async function executeScenarioRuntime(args) {
|
|
|
4050
5216
|
}
|
|
4051
5217
|
if (kind === "Pause") {
|
|
4052
5218
|
if (duringMs > 0) {
|
|
4053
|
-
|
|
5219
|
+
if (options.loadEngineContractVersion === 2) {
|
|
5220
|
+
await delayUntilMonotonicDeadline(process.hrtime.bigint() + secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "Pause duration"), scenarioCancellationToken);
|
|
5221
|
+
}
|
|
5222
|
+
else {
|
|
5223
|
+
await delayWithAbort(duringMs, scenarioCancellationToken);
|
|
5224
|
+
}
|
|
4054
5225
|
}
|
|
5226
|
+
if (schedulerSegment)
|
|
5227
|
+
schedulerSegment.accountingComplete = true;
|
|
4055
5228
|
return;
|
|
4056
5229
|
}
|
|
5230
|
+
if (schedulerSegment)
|
|
5231
|
+
schedulerSegment.accountingComplete = true;
|
|
4057
5232
|
};
|
|
4058
5233
|
const initContext = {
|
|
4059
5234
|
customSettings: { ...(options.customSettings ?? {}) },
|
|
@@ -4085,15 +5260,33 @@ async function executeScenarioRuntime(args) {
|
|
|
4085
5260
|
accumulator.setCurrentOperation("Bombing");
|
|
4086
5261
|
const simulations = scenario.getSimulations();
|
|
4087
5262
|
if (!simulations.length) {
|
|
4088
|
-
|
|
4089
|
-
|
|
5263
|
+
if (options.loadEngineContractVersion === 2) {
|
|
5264
|
+
await options.loadEngineV2SegmentLifecycleOverride?.beforeSegment(scenarioIndex, 0);
|
|
5265
|
+
try {
|
|
5266
|
+
await executeSimulationAsync(LoadStrikeSimulation.iterationsForConstant(1, 1), 0);
|
|
5267
|
+
}
|
|
5268
|
+
finally {
|
|
5269
|
+
await options.loadEngineV2SegmentLifecycleOverride?.afterSegment(scenarioIndex, 0);
|
|
5270
|
+
}
|
|
5271
|
+
}
|
|
5272
|
+
else {
|
|
5273
|
+
const instanceInfo = nextInstanceInfo();
|
|
5274
|
+
await runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
|
|
5275
|
+
}
|
|
4090
5276
|
}
|
|
4091
5277
|
else {
|
|
4092
|
-
for (
|
|
5278
|
+
for (let simulationIndex = 0; simulationIndex < simulations.length; simulationIndex += 1) {
|
|
5279
|
+
const simulation = simulations[simulationIndex];
|
|
4093
5280
|
if (shouldStopNow()) {
|
|
4094
5281
|
break;
|
|
4095
5282
|
}
|
|
4096
|
-
await
|
|
5283
|
+
await options.loadEngineV2SegmentLifecycleOverride?.beforeSegment(scenarioIndex, simulationIndex);
|
|
5284
|
+
try {
|
|
5285
|
+
await executeSimulationAsync(simulation, simulationIndex);
|
|
5286
|
+
}
|
|
5287
|
+
finally {
|
|
5288
|
+
await options.loadEngineV2SegmentLifecycleOverride?.afterSegment(scenarioIndex, simulationIndex);
|
|
5289
|
+
}
|
|
4097
5290
|
}
|
|
4098
5291
|
}
|
|
4099
5292
|
accumulator.setCurrentOperation(stopScenario || stopTestState.value ? "Stop" : "Complete");
|
|
@@ -4182,18 +5375,29 @@ async function waitForScenarioTasks(tasks, scenarioName, timeoutSeconds, logger,
|
|
|
4182
5375
|
if (!tasks.length) {
|
|
4183
5376
|
return;
|
|
4184
5377
|
}
|
|
4185
|
-
const
|
|
5378
|
+
const settled = Promise.allSettled(tasks);
|
|
5379
|
+
const throwPolicyFailure = (results) => {
|
|
5380
|
+
const failure = results.find((result) => result.status === "rejected" && result.reason instanceof RuntimePolicyCallbackError);
|
|
5381
|
+
if (failure) {
|
|
5382
|
+
throw failure.reason;
|
|
5383
|
+
}
|
|
5384
|
+
};
|
|
4186
5385
|
if (timeoutSeconds <= 0) {
|
|
4187
|
-
await
|
|
5386
|
+
throwPolicyFailure(await settled);
|
|
4188
5387
|
return;
|
|
4189
5388
|
}
|
|
4190
5389
|
const completed = await Promise.race([
|
|
4191
|
-
|
|
5390
|
+
settled.then(() => true),
|
|
4192
5391
|
delayWithAbort(Math.trunc(timeoutSeconds * 1000), signal).then(() => false)
|
|
4193
5392
|
]);
|
|
4194
5393
|
if (!completed) {
|
|
5394
|
+
if (signal.reason instanceof RuntimePolicyCallbackError) {
|
|
5395
|
+
throw signal.reason;
|
|
5396
|
+
}
|
|
4195
5397
|
logger.warn(`Scenario ${scenarioName} timed out while waiting for completion (${timeoutSeconds}s).`);
|
|
5398
|
+
return;
|
|
4196
5399
|
}
|
|
5400
|
+
throwPolicyFailure(await settled);
|
|
4197
5401
|
}
|
|
4198
5402
|
function randomIntInclusive(minValue, maxValue) {
|
|
4199
5403
|
const min = Math.trunc(Math.min(minValue, maxValue));
|
|
@@ -4420,6 +5624,9 @@ function normalizeDataTransferStatsValue(value) {
|
|
|
4420
5624
|
const source = asAliasRecord(value);
|
|
4421
5625
|
return {
|
|
4422
5626
|
allBytes: pickAliasNumber(source, "allBytes", "AllBytes"),
|
|
5627
|
+
...(hasAliasValue(source, "allBytes64", "AllBytes64")
|
|
5628
|
+
? { allBytes64: pickAliasString(source, "allBytes64", "AllBytes64") }
|
|
5629
|
+
: {}),
|
|
4423
5630
|
maxBytes: pickAliasNumber(source, "maxBytes", "MaxBytes"),
|
|
4424
5631
|
meanBytes: pickAliasNumber(source, "meanBytes", "MeanBytes"),
|
|
4425
5632
|
minBytes: pickAliasNumber(source, "minBytes", "MinBytes"),
|
|
@@ -4427,6 +5634,9 @@ function normalizeDataTransferStatsValue(value) {
|
|
|
4427
5634
|
percent75: pickAliasNumber(source, "percent75", "Percent75"),
|
|
4428
5635
|
percent95: pickAliasNumber(source, "percent95", "Percent95"),
|
|
4429
5636
|
percent99: pickAliasNumber(source, "percent99", "Percent99"),
|
|
5637
|
+
...(hasAliasValue(source, "percent100", "Percent100")
|
|
5638
|
+
? { percent100: pickAliasNumber(source, "percent100", "Percent100") }
|
|
5639
|
+
: {}),
|
|
4430
5640
|
stdDev: pickAliasNumber(source, "stdDev", "StdDev")
|
|
4431
5641
|
};
|
|
4432
5642
|
}
|
|
@@ -4449,6 +5659,9 @@ function normalizeLatencyStatsValue(value) {
|
|
|
4449
5659
|
percent75: pickAliasNumber(source, "percent75", "Percent75"),
|
|
4450
5660
|
percent95: pickAliasNumber(source, "percent95", "Percent95"),
|
|
4451
5661
|
percent99: pickAliasNumber(source, "percent99", "Percent99"),
|
|
5662
|
+
...(hasAliasValue(source, "percent100", "Percent100")
|
|
5663
|
+
? { percent100: pickAliasNumber(source, "percent100", "Percent100") }
|
|
5664
|
+
: {}),
|
|
4452
5665
|
stdDev: pickAliasNumber(source, "stdDev", "StdDev")
|
|
4453
5666
|
};
|
|
4454
5667
|
}
|
|
@@ -4464,13 +5677,42 @@ function normalizeStatusCodeStatsValue(value) {
|
|
|
4464
5677
|
}
|
|
4465
5678
|
function normalizeMeasurementStatsValue(value) {
|
|
4466
5679
|
const source = asAliasRecord(value);
|
|
4467
|
-
|
|
5680
|
+
const projected = {
|
|
5681
|
+
...(hasAliasValue(source, "count64", "Count64")
|
|
5682
|
+
? { count64: pickAliasString(source, "count64", "Count64") }
|
|
5683
|
+
: {}),
|
|
5684
|
+
...(hasAliasValue(source, "distributionMode", "DistributionMode")
|
|
5685
|
+
? { distributionMode: pickAliasString(source, "distributionMode", "DistributionMode") }
|
|
5686
|
+
: {}),
|
|
5687
|
+
...(hasAliasValue(source, "maxRelativeError", "MaxRelativeError")
|
|
5688
|
+
? { maxRelativeError: pickAliasNumber(source, "maxRelativeError", "MaxRelativeError") }
|
|
5689
|
+
: {}),
|
|
4468
5690
|
dataTransfer: normalizeDataTransferStatsValue(pickAliasValue(source, "dataTransfer", "DataTransfer")),
|
|
4469
5691
|
latency: normalizeLatencyStatsValue(pickAliasValue(source, "latency", "Latency")),
|
|
4470
5692
|
request: normalizeRequestStatsValue(pickAliasValue(source, "request", "Request")),
|
|
4471
5693
|
statusCodes: pickAliasArray(source, "statusCodes", "StatusCodes")
|
|
4472
5694
|
.map((entry) => normalizeStatusCodeStatsValue(entry))
|
|
4473
5695
|
};
|
|
5696
|
+
const sidecarValue = pickAliasValue(source, "histogramSidecar", "HistogramSidecar");
|
|
5697
|
+
if (sidecarValue && typeof sidecarValue === "object" && !Array.isArray(sidecarValue)) {
|
|
5698
|
+
const sidecar = sidecarValue;
|
|
5699
|
+
if (sidecar?.latency && sidecar.size) {
|
|
5700
|
+
Object.defineProperty(projected, "histogramSidecar", {
|
|
5701
|
+
value: {
|
|
5702
|
+
latency: load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(sidecar.latency).toSidecar(),
|
|
5703
|
+
size: load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(sidecar.size).toSidecar(),
|
|
5704
|
+
allBytes64: String(sidecar.allBytes64 ?? "0"),
|
|
5705
|
+
lessOrEq80064: String(sidecar.lessOrEq80064 ?? "0"),
|
|
5706
|
+
more800Less120064: String(sidecar.more800Less120064 ?? "0"),
|
|
5707
|
+
moreOrEq120064: String(sidecar.moreOrEq120064 ?? "0")
|
|
5708
|
+
},
|
|
5709
|
+
enumerable: false,
|
|
5710
|
+
configurable: false,
|
|
5711
|
+
writable: false
|
|
5712
|
+
});
|
|
5713
|
+
}
|
|
5714
|
+
}
|
|
5715
|
+
return projected;
|
|
4474
5716
|
}
|
|
4475
5717
|
function normalizeLoadSimulationStatsValue(value) {
|
|
4476
5718
|
const source = asAliasRecord(value);
|
|
@@ -4570,6 +5812,9 @@ function normalizeStepStatsValue(value, index = 0) {
|
|
|
4570
5812
|
statusCodes: normalizeAliasNumberRecord(pickAliasValue(source, "statusCodes", "StatusCodes")),
|
|
4571
5813
|
ok: normalizeMeasurementStatsValue(pickAliasValue(source, "ok", "Ok")),
|
|
4572
5814
|
fail: normalizeMeasurementStatsValue(pickAliasValue(source, "fail", "Fail")),
|
|
5815
|
+
...(hasAliasValue(source, "allMeasurement", "AllMeasurement")
|
|
5816
|
+
? { allMeasurement: normalizeMeasurementStatsValue(pickAliasValue(source, "allMeasurement", "AllMeasurement")) }
|
|
5817
|
+
: {}),
|
|
4573
5818
|
sortIndex: hasAliasValue(source, "sortIndex", "SortIndex")
|
|
4574
5819
|
? pickAliasNumber(source, "sortIndex", "SortIndex")
|
|
4575
5820
|
: index
|
|
@@ -4609,6 +5854,9 @@ function normalizeScenarioStatsValue(value, index = 0) {
|
|
|
4609
5854
|
durationMs: pickAliasNumber(source, "durationMs", "DurationMs", "Duration"),
|
|
4610
5855
|
ok: normalizeMeasurementStatsValue(pickAliasValue(source, "ok", "Ok")),
|
|
4611
5856
|
fail: normalizeMeasurementStatsValue(pickAliasValue(source, "fail", "Fail")),
|
|
5857
|
+
...(hasAliasValue(source, "allMeasurement", "AllMeasurement")
|
|
5858
|
+
? { allMeasurement: normalizeMeasurementStatsValue(pickAliasValue(source, "allMeasurement", "AllMeasurement")) }
|
|
5859
|
+
: {}),
|
|
4612
5860
|
loadSimulationStats: normalizeLoadSimulationStatsValue(pickAliasValue(source, "loadSimulationStats", "LoadSimulationStats")),
|
|
4613
5861
|
sortIndex: hasAliasValue(source, "sortIndex", "SortIndex")
|
|
4614
5862
|
? pickAliasNumber(source, "sortIndex", "SortIndex")
|
|
@@ -4695,7 +5943,7 @@ function attachSessionStartInfoAliases(session) {
|
|
|
4695
5943
|
return session;
|
|
4696
5944
|
}
|
|
4697
5945
|
function attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, licenseSession) {
|
|
4698
|
-
const runToken =
|
|
5946
|
+
const runToken = currentLicenseSessionRunToken(licenseSession);
|
|
4699
5947
|
if (!runToken || !licenseClient) {
|
|
4700
5948
|
return;
|
|
4701
5949
|
}
|
|
@@ -4705,9 +5953,17 @@ function attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, l
|
|
|
4705
5953
|
sinkSession.portalReportingIngestUrl = ingestUrl;
|
|
4706
5954
|
sinkSession.portalReportingRunId = runId;
|
|
4707
5955
|
sessionInfo.runToken = runToken;
|
|
5956
|
+
Object.defineProperty(sessionInfo, PORTAL_RUN_TOKEN_PROVIDER, {
|
|
5957
|
+
configurable: true,
|
|
5958
|
+
enumerable: false,
|
|
5959
|
+
value: () => currentLicenseSessionRunToken(licenseSession)
|
|
5960
|
+
});
|
|
4708
5961
|
sessionInfo.portalReportingIngestUrl = ingestUrl;
|
|
4709
5962
|
sessionInfo.portalReportingRunId = runId;
|
|
4710
5963
|
}
|
|
5964
|
+
function currentLicenseSessionRunToken(licenseSession) {
|
|
5965
|
+
return stringValueOrDefault(licenseSession?.runToken, "").trim();
|
|
5966
|
+
}
|
|
4711
5967
|
function buildPortalReportingRunId(sessionId) {
|
|
4712
5968
|
const sessionPart = String(sessionId ?? "")
|
|
4713
5969
|
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
@@ -4792,6 +6048,7 @@ function attachDataTransferStatsAliases(stats) {
|
|
|
4792
6048
|
const projected = normalizeDataTransferStatsValue(stats);
|
|
4793
6049
|
return attachAliasMap(projected, {
|
|
4794
6050
|
AllBytes: "allBytes",
|
|
6051
|
+
AllBytes64: "allBytes64",
|
|
4795
6052
|
MaxBytes: "maxBytes",
|
|
4796
6053
|
MeanBytes: "meanBytes",
|
|
4797
6054
|
MinBytes: "minBytes",
|
|
@@ -4799,6 +6056,7 @@ function attachDataTransferStatsAliases(stats) {
|
|
|
4799
6056
|
Percent75: "percent75",
|
|
4800
6057
|
Percent95: "percent95",
|
|
4801
6058
|
Percent99: "percent99",
|
|
6059
|
+
Percent100: "percent100",
|
|
4802
6060
|
StdDev: "stdDev"
|
|
4803
6061
|
});
|
|
4804
6062
|
}
|
|
@@ -4822,6 +6080,7 @@ function attachLatencyStatsAliases(stats) {
|
|
|
4822
6080
|
Percent75: "percent75",
|
|
4823
6081
|
Percent95: "percent95",
|
|
4824
6082
|
Percent99: "percent99",
|
|
6083
|
+
Percent100: "percent100",
|
|
4825
6084
|
StdDev: "stdDev"
|
|
4826
6085
|
});
|
|
4827
6086
|
return projected;
|
|
@@ -4843,6 +6102,9 @@ function attachMeasurementStatsAliases(stats) {
|
|
|
4843
6102
|
projected.latency = attachLatencyStatsAliases(projected.latency);
|
|
4844
6103
|
projected.statusCodes = projected.statusCodes.map((value) => attachStatusCodeStatsAliases(value));
|
|
4845
6104
|
attachAliasMap(projected, {
|
|
6105
|
+
Count64: "count64",
|
|
6106
|
+
DistributionMode: "distributionMode",
|
|
6107
|
+
MaxRelativeError: "maxRelativeError",
|
|
4846
6108
|
Request: "request",
|
|
4847
6109
|
DataTransfer: "dataTransfer",
|
|
4848
6110
|
Latency: "latency",
|
|
@@ -4904,6 +6166,8 @@ function attachStepStatsAliases(step) {
|
|
|
4904
6166
|
const projected = normalizeStepStatsValue(step);
|
|
4905
6167
|
projected.ok = attachMeasurementStatsAliases(projected.ok);
|
|
4906
6168
|
projected.fail = attachMeasurementStatsAliases(projected.fail);
|
|
6169
|
+
if (projected.allMeasurement)
|
|
6170
|
+
projected.allMeasurement = attachMeasurementStatsAliases(projected.allMeasurement);
|
|
4907
6171
|
attachAliasMap(projected, {
|
|
4908
6172
|
ScenarioName: "scenarioName",
|
|
4909
6173
|
StepName: "stepName",
|
|
@@ -4918,6 +6182,7 @@ function attachStepStatsAliases(step) {
|
|
|
4918
6182
|
StatusCodes: "statusCodes",
|
|
4919
6183
|
Ok: "ok",
|
|
4920
6184
|
Fail: "fail",
|
|
6185
|
+
AllMeasurement: "allMeasurement",
|
|
4921
6186
|
SortIndex: "sortIndex"
|
|
4922
6187
|
});
|
|
4923
6188
|
return projected;
|
|
@@ -4956,7 +6221,20 @@ function attachLoadSimulationProjection(simulation) {
|
|
|
4956
6221
|
});
|
|
4957
6222
|
return simulation;
|
|
4958
6223
|
}
|
|
4959
|
-
function
|
|
6224
|
+
function cloneLoadEngineV2TrafficMixMetadata(metadata) {
|
|
6225
|
+
return {
|
|
6226
|
+
...metadata,
|
|
6227
|
+
shareWeights: [...metadata.shareWeights],
|
|
6228
|
+
originalSimulations: metadata.originalSimulations.map((simulation) => attachLoadSimulationProjection({ ...simulation }))
|
|
6229
|
+
};
|
|
6230
|
+
}
|
|
6231
|
+
function nextTrafficMixDeclarationIndex(scenarios) {
|
|
6232
|
+
return scenarios.reduce((next, scenario) => {
|
|
6233
|
+
const metadata = scenario.__loadStrikeTrafficMixV2Metadata();
|
|
6234
|
+
return metadata ? Math.max(next, metadata.declarationIndex + 1) : next;
|
|
6235
|
+
}, 0);
|
|
6236
|
+
}
|
|
6237
|
+
function expandTrafficMixScenarios(trafficMix, declarationIndex = 0) {
|
|
4960
6238
|
if (!(trafficMix instanceof LoadStrikeTrafficMix)) {
|
|
4961
6239
|
throw new TypeError("Traffic mix must be provided.");
|
|
4962
6240
|
}
|
|
@@ -4969,16 +6247,30 @@ function expandTrafficMixScenarios(trafficMix) {
|
|
|
4969
6247
|
throw new Error("Traffic mix scenario shares must be configured before registration.");
|
|
4970
6248
|
}
|
|
4971
6249
|
const weights = scenarioMix.map((share) => share.weight);
|
|
6250
|
+
const seedId = (0, load_engine_v2_js_1.buildLoadEngineV2TrafficMixSeedId)(declarationIndex, trafficMix.name);
|
|
4972
6251
|
return scenarioMix.map((share, index) => {
|
|
4973
|
-
const splitSimulations = totalLoad
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
6252
|
+
const splitSimulations = totalLoad.map((simulation) => splitTrafficSimulation(simulation, weights, index)
|
|
6253
|
+
?? trafficMixNoWorkSimulation(simulation));
|
|
6254
|
+
return share.scenario
|
|
6255
|
+
.withLoadSimulations(...splitSimulations)
|
|
6256
|
+
.__loadStrikeWithInternalLicenseFeatures(TRAFFIC_MIX_FEATURE)
|
|
6257
|
+
.__loadStrikeSetTrafficMixV2Metadata({
|
|
6258
|
+
declarationIndex,
|
|
6259
|
+
name: trafficMix.name,
|
|
6260
|
+
laneIndex: index,
|
|
6261
|
+
shareWeight: share.weight,
|
|
6262
|
+
shareWeights: weights,
|
|
6263
|
+
seedId,
|
|
6264
|
+
originalSimulations: totalLoad
|
|
6265
|
+
});
|
|
4980
6266
|
});
|
|
4981
6267
|
}
|
|
6268
|
+
function trafficMixNoWorkSimulation(simulation) {
|
|
6269
|
+
const kind = String(simulation.Kind ?? "");
|
|
6270
|
+
return kind === "IterationsForInject" || kind === "IterationsForConstant"
|
|
6271
|
+
? LoadStrikeSimulation.pause(0)
|
|
6272
|
+
: LoadStrikeSimulation.pause(Math.max(readFiniteSimulationNumber(simulation, "DuringSeconds"), 0));
|
|
6273
|
+
}
|
|
4982
6274
|
function splitTrafficSimulation(simulation, weights, index) {
|
|
4983
6275
|
const kind = String(simulation.Kind ?? "");
|
|
4984
6276
|
const duringSeconds = readFiniteSimulationNumber(simulation, "DuringSeconds");
|
|
@@ -5065,6 +6357,8 @@ function attachScenarioStatsAliases(scenario) {
|
|
|
5065
6357
|
const normalized = normalizeScenarioStatsValue(scenario);
|
|
5066
6358
|
normalized.ok = attachMeasurementStatsAliases(normalized.ok);
|
|
5067
6359
|
normalized.fail = attachMeasurementStatsAliases(normalized.fail);
|
|
6360
|
+
if (normalized.allMeasurement)
|
|
6361
|
+
normalized.allMeasurement = attachMeasurementStatsAliases(normalized.allMeasurement);
|
|
5068
6362
|
normalized.loadSimulationStats = attachLoadSimulationStatsAliases(normalized.loadSimulationStats);
|
|
5069
6363
|
normalized.stepStats = normalized.stepStats.map((value) => attachStepStatsAliases(value));
|
|
5070
6364
|
const findStepStats = scenario.findStepStats ?? ((stepName) => normalized.stepStats.find((value) => value.stepName === stepName));
|
|
@@ -5098,6 +6392,7 @@ function attachScenarioStatsAliases(scenario) {
|
|
|
5098
6392
|
DurationMs: "durationMs",
|
|
5099
6393
|
Ok: "ok",
|
|
5100
6394
|
Fail: "fail",
|
|
6395
|
+
AllMeasurement: "allMeasurement",
|
|
5101
6396
|
LoadSimulationStats: "loadSimulationStats",
|
|
5102
6397
|
SortIndex: "sortIndex",
|
|
5103
6398
|
StepStats: "stepStats"
|
|
@@ -5118,6 +6413,10 @@ function attachNodeStatsAliases(stats) {
|
|
|
5118
6413
|
: stats.scenarioStats.flatMap((value) => value.stepStats)).map((value) => attachStepStatsAliases(value));
|
|
5119
6414
|
stats.pluginsData = stats.pluginsData.map((value) => normalizePluginData(value.pluginName ?? value.PluginName ?? "", value));
|
|
5120
6415
|
stats.sinkErrors = stats.sinkErrors.map((value) => attachSinkErrorAliases(value));
|
|
6416
|
+
stats.generatorWarnings = (stats.generatorWarnings ?? []).map(attachGeneratorWarningAliases);
|
|
6417
|
+
stats.schedulerSegments = (stats.schedulerSegments ?? []).map((value) => normalizeSchedulerSegment(value));
|
|
6418
|
+
stats.observationDeliveryStats = normalizeObservationDeliveryStats(stats.observationDeliveryStats ?? emptyObservationDeliveryStats());
|
|
6419
|
+
stats.reportingComplete ?? (stats.reportingComplete = false);
|
|
5121
6420
|
const findScenarioStats = stats.findScenarioStats ?? ((scenarioName) => stats.scenarioStats.find((value) => value.scenarioName === scenarioName));
|
|
5122
6421
|
const getScenarioStats = stats.getScenarioStats ?? ((scenarioName) => {
|
|
5123
6422
|
const value = findScenarioStats(scenarioName);
|
|
@@ -5152,7 +6451,11 @@ function attachNodeStatsAliases(stats) {
|
|
|
5152
6451
|
DisabledSinks: "disabledSinks",
|
|
5153
6452
|
SinkErrors: "sinkErrors",
|
|
5154
6453
|
ReportFiles: "reportFiles",
|
|
5155
|
-
LogFiles: "logFiles"
|
|
6454
|
+
LogFiles: "logFiles",
|
|
6455
|
+
GeneratorWarnings: "generatorWarnings",
|
|
6456
|
+
SchedulerSegments: "schedulerSegments",
|
|
6457
|
+
ObservationDeliveryStats: "observationDeliveryStats",
|
|
6458
|
+
ReportingComplete: "reportingComplete"
|
|
5156
6459
|
});
|
|
5157
6460
|
defineAliasProperty(projected, "StartedUtc", () => parseAliasDate(stats.startedUtc));
|
|
5158
6461
|
defineAliasProperty(projected, "CompletedUtc", () => parseAliasDate(stats.completedUtc));
|
|
@@ -5208,11 +6511,43 @@ function attachRunResultAliases(result) {
|
|
|
5208
6511
|
.map((value) => ({ ...asAliasRecord(value) })),
|
|
5209
6512
|
failedCorrelationRows: pickAliasArray(source, "failedCorrelationRows", "FailedCorrelationRows")
|
|
5210
6513
|
.map((value) => ({ ...asAliasRecord(value) })),
|
|
6514
|
+
...(hasAliasValue(source, "generatorWarnings", "GeneratorWarnings")
|
|
6515
|
+
? {
|
|
6516
|
+
generatorWarnings: pickAliasArray(source, "generatorWarnings", "GeneratorWarnings")
|
|
6517
|
+
.map(attachGeneratorWarningAliases)
|
|
6518
|
+
}
|
|
6519
|
+
: {}),
|
|
6520
|
+
...(hasAliasValue(source, "schedulerSegments", "SchedulerSegments")
|
|
6521
|
+
? {
|
|
6522
|
+
schedulerSegments: pickAliasArray(source, "schedulerSegments", "SchedulerSegments")
|
|
6523
|
+
.map(normalizeSchedulerSegment)
|
|
6524
|
+
}
|
|
6525
|
+
: {}),
|
|
6526
|
+
...(hasAliasValue(source, "schedulerStats", "SchedulerStats")
|
|
6527
|
+
? { schedulerStats: normalizeSchedulerStats(pickAliasValue(source, "schedulerStats", "SchedulerStats")) }
|
|
6528
|
+
: {}),
|
|
6529
|
+
...(hasAliasValue(source, "observationDeliveryStats", "ObservationDeliveryStats")
|
|
6530
|
+
? {
|
|
6531
|
+
observationDeliveryStats: normalizeObservationDeliveryStats(pickAliasValue(source, "observationDeliveryStats", "ObservationDeliveryStats"))
|
|
6532
|
+
}
|
|
6533
|
+
: {}),
|
|
6534
|
+
...(hasAliasValue(source, "reportingComplete", "ReportingComplete")
|
|
6535
|
+
? { reportingComplete: pickAliasBoolean(source, "reportingComplete", "ReportingComplete") }
|
|
6536
|
+
: {}),
|
|
5211
6537
|
findScenarioStats,
|
|
5212
6538
|
getScenarioStats,
|
|
5213
6539
|
FindScenarioStats: findScenarioStats,
|
|
5214
6540
|
GetScenarioStats: getScenarioStats
|
|
5215
6541
|
};
|
|
6542
|
+
const schedulerDistributions = result[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS];
|
|
6543
|
+
if (schedulerDistributions) {
|
|
6544
|
+
Object.defineProperty(projected, LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS, {
|
|
6545
|
+
value: schedulerDistributions.map(cloneLoadEngineV2DistributionRecord),
|
|
6546
|
+
enumerable: false,
|
|
6547
|
+
configurable: false,
|
|
6548
|
+
writable: false
|
|
6549
|
+
});
|
|
6550
|
+
}
|
|
5216
6551
|
attachAliasMap(projected, {
|
|
5217
6552
|
AllBytes: "allBytes",
|
|
5218
6553
|
AllRequestCount: "allRequestCount",
|
|
@@ -5236,7 +6571,12 @@ function attachRunResultAliases(result) {
|
|
|
5236
6571
|
ReportFiles: "reportFiles",
|
|
5237
6572
|
LogFiles: "logFiles",
|
|
5238
6573
|
CorrelationRows: "correlationRows",
|
|
5239
|
-
FailedCorrelationRows: "failedCorrelationRows"
|
|
6574
|
+
FailedCorrelationRows: "failedCorrelationRows",
|
|
6575
|
+
GeneratorWarnings: "generatorWarnings",
|
|
6576
|
+
SchedulerSegments: "schedulerSegments",
|
|
6577
|
+
SchedulerStats: "schedulerStats",
|
|
6578
|
+
ObservationDeliveryStats: "observationDeliveryStats",
|
|
6579
|
+
ReportingComplete: "reportingComplete"
|
|
5240
6580
|
});
|
|
5241
6581
|
defineAliasProperty(projected, "StartedUtc", () => parseAliasDate(projected.startedUtc));
|
|
5242
6582
|
defineAliasProperty(projected, "CompletedUtc", () => parseAliasDate(projected.completedUtc));
|
|
@@ -5844,6 +7184,14 @@ function detailedToNodeStats(result, metricStats) {
|
|
|
5844
7184
|
sinkErrors: (result.sinkErrors ?? []).map((sinkError) => ({ ...sinkError })),
|
|
5845
7185
|
reportFiles: [...(result.reportFiles ?? [])],
|
|
5846
7186
|
logFiles: [...(result.logFiles ?? [])],
|
|
7187
|
+
generatorWarnings: (result.generatorWarnings ?? []).map(attachGeneratorWarningAliases),
|
|
7188
|
+
schedulerSegments: result.schedulerSegments?.map((segment) => ({ ...segment })),
|
|
7189
|
+
schedulerStats: result.schedulerStats
|
|
7190
|
+
? normalizeSchedulerStats(result.schedulerStats)
|
|
7191
|
+
: undefined,
|
|
7192
|
+
[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: result[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]?.map(cloneLoadEngineV2DistributionRecord),
|
|
7193
|
+
observationDeliveryStats: normalizeObservationDeliveryStats(result.observationDeliveryStats ?? emptyObservationDeliveryStats()),
|
|
7194
|
+
reportingComplete: result.reportingComplete ?? true,
|
|
5847
7195
|
findScenarioStats: (scenarioName) => scenarioStats.find((scenario) => scenario.scenarioName === scenarioName),
|
|
5848
7196
|
getScenarioStats: (scenarioName) => {
|
|
5849
7197
|
const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
|
|
@@ -5911,22 +7259,35 @@ function buildEmptyNodeStats(args) {
|
|
|
5911
7259
|
sinkErrors: [],
|
|
5912
7260
|
reportFiles: [],
|
|
5913
7261
|
logFiles: [],
|
|
7262
|
+
generatorWarnings: [],
|
|
7263
|
+
observationDeliveryStats: emptyObservationDeliveryStats(),
|
|
7264
|
+
reportingComplete: true,
|
|
5914
7265
|
findScenarioStats: (scenarioName) => undefined,
|
|
5915
7266
|
getScenarioStats: (scenarioName) => {
|
|
5916
7267
|
throw new Error(`Scenario stats not found: ${scenarioName}`);
|
|
5917
7268
|
}
|
|
5918
7269
|
});
|
|
5919
7270
|
}
|
|
5920
|
-
function planRuntimeClusterAssignments(scenarios, agentsCount, targetScenarios, agentTargetScenarios) {
|
|
7271
|
+
function planRuntimeClusterAssignments(scenarios, agentsCount, targetScenarios, agentTargetScenarios, globalV2 = false, coordinatorTargetScenarios = []) {
|
|
5921
7272
|
const resolvedAgentCount = Math.max(agentsCount, 1);
|
|
5922
|
-
|
|
7273
|
+
let selectedScenarios = targetScenarios.length
|
|
5923
7274
|
? scenarios.filter((scenario) => targetScenarios.includes(scenario.name))
|
|
5924
7275
|
: [...scenarios];
|
|
7276
|
+
if (globalV2 && coordinatorTargetScenarios.length) {
|
|
7277
|
+
const coordinatorNames = new Set(coordinatorTargetScenarios);
|
|
7278
|
+
selectedScenarios = selectedScenarios.filter((scenario) => !coordinatorNames.has(scenario.name));
|
|
7279
|
+
}
|
|
5925
7280
|
if (!selectedScenarios.length) {
|
|
5926
7281
|
return Array.from({ length: resolvedAgentCount }, () => []);
|
|
5927
7282
|
}
|
|
5928
7283
|
if (agentTargetScenarios.length) {
|
|
5929
|
-
|
|
7284
|
+
const coordinatorNames = new Set(coordinatorTargetScenarios);
|
|
7285
|
+
const names = agentTargetScenarios.filter((name) => !coordinatorNames.has(name));
|
|
7286
|
+
return Array.from({ length: resolvedAgentCount }, () => [...names]);
|
|
7287
|
+
}
|
|
7288
|
+
if (globalV2) {
|
|
7289
|
+
const scenarioNames = selectedScenarios.map((scenario) => scenario.name);
|
|
7290
|
+
return Array.from({ length: resolvedAgentCount }, () => [...scenarioNames]);
|
|
5930
7291
|
}
|
|
5931
7292
|
const weightedNames = [];
|
|
5932
7293
|
for (const scenario of selectedScenarios) {
|
|
@@ -5946,7 +7307,320 @@ function planRuntimeClusterAssignments(scenarios, agentsCount, targetScenarios,
|
|
|
5946
7307
|
}
|
|
5947
7308
|
return assignments.map((entry) => [...entry]);
|
|
5948
7309
|
}
|
|
5949
|
-
function
|
|
7310
|
+
function buildRuntimeLoadEngineV2Plan(scenarios, options, testInfo, expectedAgentIds) {
|
|
7311
|
+
validateLoadEngineV2ScenarioFeatures(scenarios);
|
|
7312
|
+
const kindToken = (value) => {
|
|
7313
|
+
const normalized = String(value ?? "").replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
7314
|
+
const tokens = {
|
|
7315
|
+
inject: "inject", injectrandom: "inject-random", rampinginject: "ramping-inject",
|
|
7316
|
+
keepconstant: "keep-constant", rampingconstant: "ramping-constant",
|
|
7317
|
+
iterationsforinject: "iterations-for-inject", iterationsforconstant: "iterations-for-constant",
|
|
7318
|
+
pause: "pause"
|
|
7319
|
+
};
|
|
7320
|
+
const token = tokens[normalized];
|
|
7321
|
+
if (!token)
|
|
7322
|
+
throw new Error(`Load Engine V2 simulation kind is unsupported: ${String(value ?? "")}`);
|
|
7323
|
+
return token;
|
|
7324
|
+
};
|
|
7325
|
+
const integer = (value) => Math.max(Math.trunc(toNumber(value)), 0).toString();
|
|
7326
|
+
const ns = (value) => Math.max(Math.round(toNumber(value) * 1000000000), 0).toString();
|
|
7327
|
+
const coordinatorNames = new Set(options.coordinatorTargetScenarios ?? []);
|
|
7328
|
+
const explicitAgentNames = new Set(options.agentTargetScenarios ?? []);
|
|
7329
|
+
const selectedNames = new Set(options.targetScenarios ?? []);
|
|
7330
|
+
const plannedScenarios = scenarios
|
|
7331
|
+
.map((scenario, declarationIndex) => ({ scenario, declarationIndex }))
|
|
7332
|
+
.filter(({ scenario }) => (selectedNames.size === 0 || selectedNames.has(scenario.name))
|
|
7333
|
+
&& (explicitAgentNames.size > 0
|
|
7334
|
+
? explicitAgentNames.has(scenario.name)
|
|
7335
|
+
: !coordinatorNames.has(scenario.name)));
|
|
7336
|
+
const implicitSingleInvocation = {
|
|
7337
|
+
simulationIndex64: "0",
|
|
7338
|
+
kind: "iterations-for-constant",
|
|
7339
|
+
rate64: "0",
|
|
7340
|
+
minRate64: "0",
|
|
7341
|
+
maxRate64: "0",
|
|
7342
|
+
copies64: "1",
|
|
7343
|
+
iterations64: "1",
|
|
7344
|
+
intervalNs64: "0",
|
|
7345
|
+
durationNs64: "0"
|
|
7346
|
+
};
|
|
7347
|
+
return {
|
|
7348
|
+
runId: testInfo.sessionId,
|
|
7349
|
+
sessionId: testInfo.sessionId,
|
|
7350
|
+
registrationNonce: (0, node_crypto_1.randomBytes)(16).toString("hex"),
|
|
7351
|
+
maxInFlight64: Math.max(options.maxInFlight ?? 10000, 1).toString(),
|
|
7352
|
+
reportingIntervalNs64: ns(options.reportingIntervalSeconds ?? 1),
|
|
7353
|
+
schedulerVisibleProcessorCount64: Math.max(node_os_1.default.cpus().length, 1).toString(),
|
|
7354
|
+
expectedAgentIds: [...expectedAgentIds],
|
|
7355
|
+
scenarios: plannedScenarios.map(({ scenario, declarationIndex }) => ({
|
|
7356
|
+
scenarioIndex64: declarationIndex.toString(),
|
|
7357
|
+
scenarioName: scenario.name,
|
|
7358
|
+
target: "agent",
|
|
7359
|
+
callbackExecutionMode: "async",
|
|
7360
|
+
declaredStepNames: scenario.getDeclaredSteps(),
|
|
7361
|
+
simulations: scenario.getSimulations().length
|
|
7362
|
+
? scenario.getSimulations().map((simulation, simulationIndex) => ({
|
|
7363
|
+
simulationIndex64: simulationIndex.toString(),
|
|
7364
|
+
kind: kindToken(simulation.Kind ?? simulation.kind),
|
|
7365
|
+
rate64: integer(simulation.Rate ?? simulation.rate),
|
|
7366
|
+
minRate64: integer(simulation.MinRate ?? simulation.minRate),
|
|
7367
|
+
maxRate64: integer(simulation.MaxRate ?? simulation.maxRate),
|
|
7368
|
+
copies64: integer(simulation.Copies ?? simulation.copies),
|
|
7369
|
+
iterations64: integer(simulation.Iterations ?? simulation.iterations),
|
|
7370
|
+
intervalNs64: ns(simulation.IntervalSeconds ?? simulation.intervalSeconds),
|
|
7371
|
+
durationNs64: ns(simulation.DuringSeconds ?? simulation.duringSeconds)
|
|
7372
|
+
}))
|
|
7373
|
+
: [{ ...implicitSingleInvocation }]
|
|
7374
|
+
}))
|
|
7375
|
+
};
|
|
7376
|
+
}
|
|
7377
|
+
function normalizeRequiredV2AgentIds(values, expectedCount) {
|
|
7378
|
+
const ids = normalizeOptionalStringArray(values) ?? [];
|
|
7379
|
+
if (ids.length !== expectedCount || new Set(ids).size !== ids.length) {
|
|
7380
|
+
throw new Error("Remote Load Engine V2 coordinators require an exact unique ExpectedAgentIds set matching AgentsCount.");
|
|
7381
|
+
}
|
|
7382
|
+
return ids;
|
|
7383
|
+
}
|
|
7384
|
+
function validateLoadEngineV2ScenarioFeatures(scenarios) {
|
|
7385
|
+
for (const scenario of scenarios) {
|
|
7386
|
+
if (scenario.getTrackingConfiguration()) {
|
|
7387
|
+
throw new Error(`Load Engine V2 correlation is not available in the supported non-correlation profile. Scenario=${scenario.name}.`);
|
|
7388
|
+
}
|
|
7389
|
+
}
|
|
7390
|
+
}
|
|
7391
|
+
function canonicalLoadEngineV2InvocationIdentityKind(simulationKind) {
|
|
7392
|
+
const normalized = simulationKind.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
7393
|
+
if (["inject", "injectrandom", "rampinginject", "iterationsforinject"].includes(normalized)) {
|
|
7394
|
+
return "arrival";
|
|
7395
|
+
}
|
|
7396
|
+
if (["keepconstant", "rampingconstant"].includes(normalized)) {
|
|
7397
|
+
return "worker-iteration";
|
|
7398
|
+
}
|
|
7399
|
+
if (normalized === "iterationsforconstant") {
|
|
7400
|
+
return "constant-iteration";
|
|
7401
|
+
}
|
|
7402
|
+
return "";
|
|
7403
|
+
}
|
|
7404
|
+
function buildRuntimeLoadEngineV2HistogramArtifact(result, scenarioDeclarations) {
|
|
7405
|
+
const emptyHistogram = () => new load_engine_v2_js_1.LoadStrikeHistogramV1().toSidecar();
|
|
7406
|
+
const distributions = [];
|
|
7407
|
+
const measurementSummaries = [];
|
|
7408
|
+
const statusBody = (measurement, outcome) => {
|
|
7409
|
+
const named = measurement.statusCodes
|
|
7410
|
+
.filter((row) => Boolean(row.statusCode || row.message))
|
|
7411
|
+
.map((row) => {
|
|
7412
|
+
const key = (0, cluster_js_1.buildLoadEngineV2StatusIdentityKey)(row.statusCode, row.message);
|
|
7413
|
+
if (!key)
|
|
7414
|
+
throw new Error("Load Engine V2 status identity unexpectedly resolved empty.");
|
|
7415
|
+
return {
|
|
7416
|
+
statusIdentityKeyHex: key.identity.toString("hex"),
|
|
7417
|
+
display: key.display,
|
|
7418
|
+
count64: Math.max(Math.trunc(row.count), 0).toString(),
|
|
7419
|
+
aggregatedObservationCount64: "0",
|
|
7420
|
+
hasAggregatedIdentities: false
|
|
7421
|
+
};
|
|
7422
|
+
})
|
|
7423
|
+
.sort((left, right) => Buffer.compare(Buffer.from(left.statusIdentityKeyHex, "hex"), Buffer.from(right.statusIdentityKeyHex, "hex")));
|
|
7424
|
+
let statuses = named;
|
|
7425
|
+
if (named.length > 64) {
|
|
7426
|
+
const retained = named.slice(0, 63);
|
|
7427
|
+
const aggregated = named.slice(63).reduce((sum, row) => sum + BigInt(row.count64), 0n);
|
|
7428
|
+
retained.push({
|
|
7429
|
+
statusIdentityKeyHex: Buffer.concat([Buffer.from("LS-ID1\n", "ascii"), Buffer.from([0x0d])]).toString("hex"),
|
|
7430
|
+
display: "<other>", count64: aggregated.toString(),
|
|
7431
|
+
aggregatedObservationCount64: aggregated.toString(), hasAggregatedIdentities: aggregated > 0n
|
|
7432
|
+
});
|
|
7433
|
+
statuses = retained;
|
|
7434
|
+
}
|
|
7435
|
+
return {
|
|
7436
|
+
outcome,
|
|
7437
|
+
statusObservationCount64: statuses.reduce((sum, row) => sum + BigInt(row.count64), 0n).toString(),
|
|
7438
|
+
statuses
|
|
7439
|
+
};
|
|
7440
|
+
};
|
|
7441
|
+
const appendMeasurement = (seriesKind, scenarioIndex64, scenarioName, identity, display, ok, fail, all, reservedOther) => {
|
|
7442
|
+
const emptyMeasurement = () => ({
|
|
7443
|
+
count64: "0",
|
|
7444
|
+
histogramSidecar: {
|
|
7445
|
+
latency: emptyHistogram(), size: emptyHistogram(), allBytes64: "0",
|
|
7446
|
+
lessOrEq80064: "0", more800Less120064: "0", moreOrEq120064: "0"
|
|
7447
|
+
},
|
|
7448
|
+
request: { count: 0, percent: 0, rps: 0 },
|
|
7449
|
+
dataTransfer: { allBytes: 0, minBytes: 0, maxBytes: 0, meanBytes: 0, percent50: 0,
|
|
7450
|
+
percent75: 0, percent95: 0, percent99: 0, percent100: 0, stdDev: 0 },
|
|
7451
|
+
latency: { latencyCount: { lessOrEq800: 0, more800Less1200: 0, moreOrEq1200: 0 },
|
|
7452
|
+
minMs: 0, maxMs: 0, meanMs: 0, percent50: 0, percent75: 0, percent95: 0,
|
|
7453
|
+
percent99: 0, stdDev: 0 },
|
|
7454
|
+
statusCodes: []
|
|
7455
|
+
});
|
|
7456
|
+
const okValue = ok ?? emptyMeasurement();
|
|
7457
|
+
const failValue = fail ?? emptyMeasurement();
|
|
7458
|
+
const allValue = all ?? emptyMeasurement();
|
|
7459
|
+
for (const [outcome, measurement] of [
|
|
7460
|
+
["ok", okValue], ["fail", failValue], ["all", allValue]
|
|
7461
|
+
]) {
|
|
7462
|
+
const sidecar = measurement.histogramSidecar;
|
|
7463
|
+
if (!sidecar)
|
|
7464
|
+
throw new Error("Load Engine V2 assigned measurement is missing histogram state.");
|
|
7465
|
+
distributions.push({
|
|
7466
|
+
seriesKind, scenarioIndex64, scenarioName, identityKeyHex: identity.toString("hex"),
|
|
7467
|
+
outcome, unit: "microseconds", histogram: sidecar.latency,
|
|
7468
|
+
exactTotalDecimalOrEmpty: sidecar.latency.exactTotal64,
|
|
7469
|
+
bands: [
|
|
7470
|
+
{ bandId64: "0", count64: sidecar.lessOrEq80064 },
|
|
7471
|
+
{ bandId64: "1", count64: sidecar.more800Less120064 },
|
|
7472
|
+
{ bandId64: "2", count64: sidecar.moreOrEq120064 }
|
|
7473
|
+
]
|
|
7474
|
+
});
|
|
7475
|
+
distributions.push({
|
|
7476
|
+
seriesKind, scenarioIndex64, scenarioName, identityKeyHex: identity.toString("hex"),
|
|
7477
|
+
outcome, unit: "bytes", histogram: sidecar.size,
|
|
7478
|
+
exactTotalDecimalOrEmpty: sidecar.size.exactTotal64
|
|
7479
|
+
});
|
|
7480
|
+
}
|
|
7481
|
+
const observation = BigInt(allValue.count64 ?? allValue.histogramSidecar?.latency.count64 ?? "0");
|
|
7482
|
+
const success = BigInt(okValue.count64 ?? okValue.histogramSidecar?.latency.count64 ?? "0");
|
|
7483
|
+
const failure = BigInt(failValue.count64 ?? failValue.histogramSidecar?.latency.count64 ?? "0");
|
|
7484
|
+
measurementSummaries.push({
|
|
7485
|
+
seriesKind, scenarioIndex64, scenarioName, identityKeyHex: identity.toString("hex"), display,
|
|
7486
|
+
observationCount64: observation.toString(), successCount64: success.toString(),
|
|
7487
|
+
failureCount64: failure.toString(),
|
|
7488
|
+
aggregatedObservationCount64: reservedOther ? observation.toString() : "0",
|
|
7489
|
+
hasAggregatedIdentities: reservedOther && observation > 0n,
|
|
7490
|
+
outcomes: [statusBody(okValue, "ok"), statusBody(failValue, "fail")]
|
|
7491
|
+
});
|
|
7492
|
+
};
|
|
7493
|
+
for (const scenario of result.scenarioStats) {
|
|
7494
|
+
const scenarioIndex64 = Math.max(Math.trunc(scenario.sortIndex), 0).toString();
|
|
7495
|
+
appendMeasurement("scenario", scenarioIndex64, scenario.scenarioName, (0, cluster_js_1.buildLoadEngineV2ScenarioIdentityKey)(scenarioIndex64), scenario.scenarioName, scenario.ok, scenario.fail, scenario.allMeasurement, false);
|
|
7496
|
+
const declaration = scenarioDeclarations?.find((value) => value.name === scenario.scenarioName);
|
|
7497
|
+
const declaredNames = declaration?.getDeclaredSteps()
|
|
7498
|
+
?? (scenarioDeclarations ? [] : undefined);
|
|
7499
|
+
if (declaredNames === undefined) {
|
|
7500
|
+
for (const step of scenario.stepStats) {
|
|
7501
|
+
const key = (0, cluster_js_1.buildLoadEngineV2StepIdentityKey)(scenarioIndex64, step.stepName);
|
|
7502
|
+
appendMeasurement("step", scenarioIndex64, scenario.scenarioName, key.identity, key.display, step.ok, step.fail, step.allMeasurement, false);
|
|
7503
|
+
}
|
|
7504
|
+
}
|
|
7505
|
+
else {
|
|
7506
|
+
const requiredStepAllMeasurement = (step) => {
|
|
7507
|
+
if (!step.allMeasurement) {
|
|
7508
|
+
throw new Error("Load Engine V2 assigned step is missing its all-outcome histogram state.");
|
|
7509
|
+
}
|
|
7510
|
+
return step.allMeasurement;
|
|
7511
|
+
};
|
|
7512
|
+
const groups = new Map();
|
|
7513
|
+
for (const declaredName of declaredNames) {
|
|
7514
|
+
const key = (0, cluster_js_1.buildLoadEngineV2StepIdentityKey)(scenarioIndex64, declaredName);
|
|
7515
|
+
groups.set(key.identity.toString("hex"), {
|
|
7516
|
+
identity: key.identity, display: key.display, routedToOther: false, steps: []
|
|
7517
|
+
});
|
|
7518
|
+
}
|
|
7519
|
+
for (const step of scenario.stepStats) {
|
|
7520
|
+
const resolved = (0, cluster_js_1.resolveLoadEngineV2StepIdentityKey)(scenarioIndex64, step.stepName, declaredNames);
|
|
7521
|
+
const key = resolved.identity.toString("hex");
|
|
7522
|
+
const group = groups.get(key) ?? {
|
|
7523
|
+
identity: resolved.identity,
|
|
7524
|
+
display: resolved.display,
|
|
7525
|
+
routedToOther: resolved.routedToOther,
|
|
7526
|
+
steps: []
|
|
7527
|
+
};
|
|
7528
|
+
group.steps.push(step);
|
|
7529
|
+
groups.set(key, group);
|
|
7530
|
+
}
|
|
7531
|
+
const reservedHex = (0, cluster_js_1.buildLoadEngineV2ReservedStepOtherIdentityKey)(scenarioIndex64).toString("hex");
|
|
7532
|
+
const declaredGroups = [...groups.entries()]
|
|
7533
|
+
.filter(([key]) => key !== reservedHex)
|
|
7534
|
+
.map(([, value]) => value)
|
|
7535
|
+
.sort((left, right) => Buffer.compare(left.identity, right.identity));
|
|
7536
|
+
for (const group of declaredGroups) {
|
|
7537
|
+
const ok = group.steps.length
|
|
7538
|
+
? aggregateMeasurementStats(group.steps.map((step) => step.ok), scenario.allRequestCount, scenario.durationMs, true)
|
|
7539
|
+
: undefined;
|
|
7540
|
+
const fail = group.steps.length
|
|
7541
|
+
? aggregateMeasurementStats(group.steps.map((step) => step.fail), scenario.allRequestCount, scenario.durationMs, true)
|
|
7542
|
+
: undefined;
|
|
7543
|
+
const all = group.steps.length
|
|
7544
|
+
? aggregateMeasurementStats(group.steps.map(requiredStepAllMeasurement), scenario.allRequestCount, scenario.durationMs, true)
|
|
7545
|
+
: undefined;
|
|
7546
|
+
appendMeasurement("step", scenarioIndex64, scenario.scenarioName, group.identity, group.display, ok, fail, all, false);
|
|
7547
|
+
}
|
|
7548
|
+
const other = groups.get(reservedHex);
|
|
7549
|
+
if (other?.steps.length) {
|
|
7550
|
+
appendMeasurement("step", scenarioIndex64, scenario.scenarioName, other.identity, "<other>", aggregateMeasurementStats(other.steps.map((step) => step.ok), scenario.allRequestCount, scenario.durationMs, true), aggregateMeasurementStats(other.steps.map((step) => step.fail), scenario.allRequestCount, scenario.durationMs, true), aggregateMeasurementStats(other.steps.map(requiredStepAllMeasurement), scenario.allRequestCount, scenario.durationMs, true), true);
|
|
7551
|
+
continue;
|
|
7552
|
+
}
|
|
7553
|
+
}
|
|
7554
|
+
appendMeasurement("step", scenarioIndex64, scenario.scenarioName, (0, cluster_js_1.buildLoadEngineV2ReservedStepOtherIdentityKey)(scenarioIndex64), "<other>", undefined, undefined, undefined, true);
|
|
7555
|
+
}
|
|
7556
|
+
for (const segment of result.schedulerSegments ?? []) {
|
|
7557
|
+
const scenarioIndex64 = segment.scenarioIndex.toString();
|
|
7558
|
+
const simulationIndex64 = segment.simulationIndex.toString();
|
|
7559
|
+
for (const kind of ["decision", "start"]) {
|
|
7560
|
+
const seriesKind = kind === "decision" ? "scheduler-decision-lag" : "scheduler-start-lag";
|
|
7561
|
+
const identityKeyHex = (0, cluster_js_1.buildLoadEngineV2SchedulerIdentityKey)(kind, scenarioIndex64, simulationIndex64).toString("hex");
|
|
7562
|
+
const signed = result[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]?.find((record) => record.seriesKind === seriesKind
|
|
7563
|
+
&& record.scenarioIndex64 === scenarioIndex64
|
|
7564
|
+
&& record.identityKeyHex === identityKeyHex
|
|
7565
|
+
&& record.outcome === "none"
|
|
7566
|
+
&& record.unit === "microseconds");
|
|
7567
|
+
if (!distributions.some((record) => record.seriesKind === seriesKind
|
|
7568
|
+
&& record.scenarioIndex64 === scenarioIndex64
|
|
7569
|
+
&& record.identityKeyHex === identityKeyHex)) {
|
|
7570
|
+
distributions.push(signed ? cloneLoadEngineV2DistributionRecord(signed) : {
|
|
7571
|
+
seriesKind,
|
|
7572
|
+
scenarioIndex64, scenarioName: segment.scenarioName,
|
|
7573
|
+
identityKeyHex,
|
|
7574
|
+
outcome: "none", unit: "microseconds", histogram: emptyHistogram(),
|
|
7575
|
+
exactTotalDecimalOrEmpty: "0"
|
|
7576
|
+
});
|
|
7577
|
+
}
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7580
|
+
return (0, load_engine_v2_js_1.serializeLoadEngineV2HistogramArtifact)({ distributions, measurementSummaries });
|
|
7581
|
+
}
|
|
7582
|
+
function cloneLoadEngineV2DistributionRecord(record) {
|
|
7583
|
+
return {
|
|
7584
|
+
...record,
|
|
7585
|
+
histogram: {
|
|
7586
|
+
...record.histogram,
|
|
7587
|
+
exactSamples64: [...record.histogram.exactSamples64],
|
|
7588
|
+
buckets: record.histogram.buckets.map((bucket) => ({ ...bucket }))
|
|
7589
|
+
},
|
|
7590
|
+
bands: record.bands?.map((band) => ({ ...band }))
|
|
7591
|
+
};
|
|
7592
|
+
}
|
|
7593
|
+
function mergeLoadEngineV2SchedulerDistributions(records) {
|
|
7594
|
+
const merged = new Map();
|
|
7595
|
+
for (const input of records) {
|
|
7596
|
+
if (input.seriesKind !== "scheduler-decision-lag" && input.seriesKind !== "scheduler-start-lag") {
|
|
7597
|
+
continue;
|
|
7598
|
+
}
|
|
7599
|
+
const key = [input.seriesKind, input.scenarioIndex64, input.identityKeyHex, input.outcome, input.unit].join("\0");
|
|
7600
|
+
const current = merged.get(key);
|
|
7601
|
+
if (!current) {
|
|
7602
|
+
merged.set(key, cloneLoadEngineV2DistributionRecord(input));
|
|
7603
|
+
continue;
|
|
7604
|
+
}
|
|
7605
|
+
if (current.scenarioName !== input.scenarioName) {
|
|
7606
|
+
throw new Error("Load Engine V2 scheduler histogram identities disagree across agents.");
|
|
7607
|
+
}
|
|
7608
|
+
const histogram = load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(current.histogram);
|
|
7609
|
+
histogram.merge(load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(input.histogram));
|
|
7610
|
+
current.histogram = histogram.toSidecar();
|
|
7611
|
+
current.exactTotalDecimalOrEmpty = histogram.toSidecar().exactTotal64;
|
|
7612
|
+
const bands = new Map();
|
|
7613
|
+
for (const band of [...(current.bands ?? []), ...(input.bands ?? [])]) {
|
|
7614
|
+
bands.set(band.bandId64, (bands.get(band.bandId64) ?? 0n) + BigInt(band.count64));
|
|
7615
|
+
}
|
|
7616
|
+
current.bands = Array.from(bands, ([bandId64, count]) => ({ bandId64, count64: count.toString() }));
|
|
7617
|
+
}
|
|
7618
|
+
return Array.from(merged.values());
|
|
7619
|
+
}
|
|
7620
|
+
function nodeStatsToClusterPayload(result, scenarioDeclarations, requireLoadEngineV2Histogram = false) {
|
|
7621
|
+
const histogramArtifactBase64 = requireLoadEngineV2Histogram
|
|
7622
|
+
? buildRuntimeLoadEngineV2HistogramArtifact(result, scenarioDeclarations).toString("base64")
|
|
7623
|
+
: undefined;
|
|
5950
7624
|
return {
|
|
5951
7625
|
allBytes: result.allBytes,
|
|
5952
7626
|
allRequestCount: result.allRequestCount,
|
|
@@ -5959,7 +7633,13 @@ function nodeStatsToClusterPayload(result) {
|
|
|
5959
7633
|
pluginsData: result.pluginsData,
|
|
5960
7634
|
nodeInfo: result.nodeInfo,
|
|
5961
7635
|
testInfo: result.testInfo,
|
|
5962
|
-
logFiles: [...(result.logFiles ?? [])]
|
|
7636
|
+
logFiles: [...(result.logFiles ?? [])],
|
|
7637
|
+
generatorWarnings: result.generatorWarnings,
|
|
7638
|
+
observationDeliveryStats: result.observationDeliveryStats,
|
|
7639
|
+
schedulerSegments: result.schedulerSegments,
|
|
7640
|
+
schedulerStats: result.schedulerStats,
|
|
7641
|
+
...(histogramArtifactBase64 ? { histogramArtifactBase64 } : {}),
|
|
7642
|
+
reportingComplete: result.reportingComplete
|
|
5963
7643
|
};
|
|
5964
7644
|
}
|
|
5965
7645
|
function toDetailedRunResultFromNodeStats(result, startedUtc, sinkErrors, policyErrors = []) {
|
|
@@ -5987,6 +7667,14 @@ function toDetailedRunResultFromNodeStats(result, startedUtc, sinkErrors, policy
|
|
|
5987
7667
|
policyErrors: policyErrors.map((value) => attachRuntimePolicyErrorAliases({ ...value })),
|
|
5988
7668
|
reportFiles: [...result.reportFiles],
|
|
5989
7669
|
logFiles: [...(result.logFiles ?? [])],
|
|
7670
|
+
generatorWarnings: (result.generatorWarnings ?? []).map(attachGeneratorWarningAliases),
|
|
7671
|
+
schedulerSegments: result.schedulerSegments?.map((segment) => ({ ...segment })),
|
|
7672
|
+
schedulerStats: result.schedulerStats
|
|
7673
|
+
? normalizeSchedulerStats(result.schedulerStats)
|
|
7674
|
+
: undefined,
|
|
7675
|
+
[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: result[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]?.map(cloneLoadEngineV2DistributionRecord),
|
|
7676
|
+
observationDeliveryStats: normalizeObservationDeliveryStats(result.observationDeliveryStats ?? emptyObservationDeliveryStats()),
|
|
7677
|
+
reportingComplete: result.reportingComplete ?? false,
|
|
5990
7678
|
correlationRows: buildDetailedCorrelationRows(),
|
|
5991
7679
|
failedCorrelationRows: buildDetailedFailedCorrelationRows()
|
|
5992
7680
|
});
|
|
@@ -6009,7 +7697,198 @@ function flattenMetricValues(metricStats) {
|
|
|
6009
7697
|
}))
|
|
6010
7698
|
];
|
|
6011
7699
|
}
|
|
6012
|
-
function
|
|
7700
|
+
function projectLoadEngineV2MeasurementsFromArtifact(sourceScenarios, artifact) {
|
|
7701
|
+
const distributions = new Map();
|
|
7702
|
+
for (const distribution of artifact.distributions) {
|
|
7703
|
+
const key = loadEngineV2DistributionProjectionKey(distribution.seriesKind, distribution.scenarioIndex64, distribution.identityKeyHex, distribution.outcome, distribution.unit);
|
|
7704
|
+
if (distributions.has(key)) {
|
|
7705
|
+
throw new Error("Load Engine V2 histogram artifact contains a duplicate distribution identity.");
|
|
7706
|
+
}
|
|
7707
|
+
distributions.set(key, distribution);
|
|
7708
|
+
}
|
|
7709
|
+
const scenarioSummaries = artifact.measurementSummaries
|
|
7710
|
+
.filter((summary) => summary.seriesKind === "scenario")
|
|
7711
|
+
.sort((left, right) => compareLoadEngineV2Decimal(left.scenarioIndex64, right.scenarioIndex64));
|
|
7712
|
+
if (scenarioSummaries.length !== sourceScenarios.length) {
|
|
7713
|
+
throw new Error("Load Engine V2 histogram artifact scenario summaries do not reconcile with the scheduler snapshot.");
|
|
7714
|
+
}
|
|
7715
|
+
const sourceByName = new Map();
|
|
7716
|
+
for (const scenario of sourceScenarios) {
|
|
7717
|
+
if (!scenario.scenarioName || sourceByName.has(scenario.scenarioName)) {
|
|
7718
|
+
throw new Error("Load Engine V2 scheduler snapshot scenario identities are empty or duplicated.");
|
|
7719
|
+
}
|
|
7720
|
+
sourceByName.set(scenario.scenarioName, scenario);
|
|
7721
|
+
}
|
|
7722
|
+
return scenarioSummaries.map((summary) => {
|
|
7723
|
+
const source = sourceByName.get(summary.scenarioName);
|
|
7724
|
+
if (!source) {
|
|
7725
|
+
throw new Error("Load Engine V2 histogram artifact scenario identity is absent from the scheduler snapshot.");
|
|
7726
|
+
}
|
|
7727
|
+
const expectedIdentity = (0, cluster_js_1.buildLoadEngineV2ScenarioIdentityKey)(summary.scenarioIndex64).toString("hex");
|
|
7728
|
+
if (summary.identityKeyHex !== expectedIdentity) {
|
|
7729
|
+
throw new Error("Load Engine V2 histogram artifact scenario identity is not canonical.");
|
|
7730
|
+
}
|
|
7731
|
+
const observationCount = loadEngineV2SafeNumber(summary.observationCount64, "scenario observation count");
|
|
7732
|
+
const successCount = loadEngineV2SafeNumber(summary.successCount64, "scenario success count");
|
|
7733
|
+
const failureCount = loadEngineV2SafeNumber(summary.failureCount64, "scenario failure count");
|
|
7734
|
+
if (source.allRequestCount !== observationCount
|
|
7735
|
+
|| source.allOkCount !== successCount
|
|
7736
|
+
|| source.allFailCount !== failureCount) {
|
|
7737
|
+
throw new Error("Load Engine V2 histogram artifact scenario counts do not reconcile with the scheduler snapshot.");
|
|
7738
|
+
}
|
|
7739
|
+
const ok = projectLoadEngineV2Measurement(summary, "ok", distributions, observationCount, source.durationMs, [source.ok]);
|
|
7740
|
+
const fail = projectLoadEngineV2Measurement(summary, "fail", distributions, observationCount, source.durationMs, [source.fail]);
|
|
7741
|
+
const allMeasurement = projectLoadEngineV2Measurement(summary, "all", distributions, observationCount, source.durationMs, [source.ok, source.fail]);
|
|
7742
|
+
const stepSummaries = artifact.measurementSummaries
|
|
7743
|
+
.filter((candidate) => candidate.seriesKind === "step"
|
|
7744
|
+
&& candidate.scenarioIndex64 === summary.scenarioIndex64
|
|
7745
|
+
&& candidate.scenarioName === summary.scenarioName)
|
|
7746
|
+
.sort((left, right) => Buffer.compare(Buffer.from(left.identityKeyHex, "hex"), Buffer.from(right.identityKeyHex, "hex")));
|
|
7747
|
+
const stepIdentitySet = new Set(stepSummaries.map((candidate) => candidate.identityKeyHex));
|
|
7748
|
+
const reservedOtherIdentity = (0, cluster_js_1.buildLoadEngineV2ReservedStepOtherIdentityKey)(summary.scenarioIndex64).toString("hex");
|
|
7749
|
+
const stepStats = stepSummaries.map((stepSummary, sortIndex) => {
|
|
7750
|
+
const matchingSourceSteps = source.stepStats.filter((step) => {
|
|
7751
|
+
const observedIdentity = (0, cluster_js_1.buildLoadEngineV2StepIdentityKey)(summary.scenarioIndex64, step.stepName).identity.toString("hex");
|
|
7752
|
+
return stepSummary.identityKeyHex === reservedOtherIdentity
|
|
7753
|
+
? !stepIdentitySet.has(observedIdentity)
|
|
7754
|
+
: observedIdentity === stepSummary.identityKeyHex;
|
|
7755
|
+
});
|
|
7756
|
+
const stepObservationCount = loadEngineV2SafeNumber(stepSummary.observationCount64, "step observation count");
|
|
7757
|
+
const stepOk = projectLoadEngineV2Measurement(stepSummary, "ok", distributions, observationCount, source.durationMs, matchingSourceSteps.map((step) => step.ok));
|
|
7758
|
+
const stepFail = projectLoadEngineV2Measurement(stepSummary, "fail", distributions, observationCount, source.durationMs, matchingSourceSteps.map((step) => step.fail));
|
|
7759
|
+
const stepAll = projectLoadEngineV2Measurement(stepSummary, "all", distributions, observationCount, source.durationMs, matchingSourceSteps.flatMap((step) => [step.ok, step.fail]));
|
|
7760
|
+
const totalLatencyMs = stepAll.latency.meanMs * stepObservationCount;
|
|
7761
|
+
return {
|
|
7762
|
+
scenarioName: summary.scenarioName,
|
|
7763
|
+
stepName: stepSummary.display,
|
|
7764
|
+
okCount: stepOk.request.count,
|
|
7765
|
+
failCount: stepFail.request.count,
|
|
7766
|
+
requestCount: stepObservationCount,
|
|
7767
|
+
totalBytes: stepAll.dataTransfer.allBytes,
|
|
7768
|
+
totalLatencyMs,
|
|
7769
|
+
avgLatencyMs: stepObservationCount > 0 ? totalLatencyMs / stepObservationCount : 0,
|
|
7770
|
+
minLatencyMs: stepAll.latency.minMs,
|
|
7771
|
+
maxLatencyMs: stepAll.latency.maxMs,
|
|
7772
|
+
statusCodes: aggregateStatusCodeCounts(stepOk.statusCodes, stepFail.statusCodes),
|
|
7773
|
+
ok: stepOk,
|
|
7774
|
+
fail: stepFail,
|
|
7775
|
+
allMeasurement: stepAll,
|
|
7776
|
+
sortIndex
|
|
7777
|
+
};
|
|
7778
|
+
});
|
|
7779
|
+
const totalLatencyMs = allMeasurement.latency.meanMs * observationCount;
|
|
7780
|
+
const projected = {
|
|
7781
|
+
scenarioName: summary.scenarioName,
|
|
7782
|
+
allRequestCount: observationCount,
|
|
7783
|
+
allOkCount: successCount,
|
|
7784
|
+
allFailCount: failureCount,
|
|
7785
|
+
totalBytes: allMeasurement.dataTransfer.allBytes,
|
|
7786
|
+
totalLatencyMs,
|
|
7787
|
+
avgLatencyMs: observationCount > 0 ? totalLatencyMs / observationCount : 0,
|
|
7788
|
+
minLatencyMs: allMeasurement.latency.minMs,
|
|
7789
|
+
maxLatencyMs: allMeasurement.latency.maxMs,
|
|
7790
|
+
statusCodes: aggregateStatusCodeCounts(ok.statusCodes, fail.statusCodes),
|
|
7791
|
+
allMeasurement,
|
|
7792
|
+
allBytes: allMeasurement.dataTransfer.allBytes,
|
|
7793
|
+
currentOperation: source.currentOperation,
|
|
7794
|
+
durationMs: source.durationMs,
|
|
7795
|
+
ok,
|
|
7796
|
+
fail,
|
|
7797
|
+
loadSimulationStats: { ...source.loadSimulationStats },
|
|
7798
|
+
sortIndex: loadEngineV2SafeNumber(summary.scenarioIndex64, "scenario index"),
|
|
7799
|
+
stepStats,
|
|
7800
|
+
findStepStats: (stepName) => stepStats.find((step) => step.stepName === stepName),
|
|
7801
|
+
getStepStats: (stepName) => {
|
|
7802
|
+
const step = stepStats.find((candidate) => candidate.stepName === stepName);
|
|
7803
|
+
if (!step)
|
|
7804
|
+
throw new Error(`Step stats not found: ${stepName}`);
|
|
7805
|
+
return step;
|
|
7806
|
+
}
|
|
7807
|
+
};
|
|
7808
|
+
return attachScenarioStatsAliases(projected);
|
|
7809
|
+
});
|
|
7810
|
+
}
|
|
7811
|
+
function projectLoadEngineV2Measurement(summary, outcome, distributions, allRequestCount, durationMs, sourceMeasurements) {
|
|
7812
|
+
const expectedCount64 = outcome === "ok"
|
|
7813
|
+
? summary.successCount64
|
|
7814
|
+
: outcome === "fail"
|
|
7815
|
+
? summary.failureCount64
|
|
7816
|
+
: summary.observationCount64;
|
|
7817
|
+
const latency = requireLoadEngineV2MeasurementDistribution(summary, outcome, "microseconds", distributions);
|
|
7818
|
+
const size = requireLoadEngineV2MeasurementDistribution(summary, outcome, "bytes", distributions);
|
|
7819
|
+
if (latency.histogram.count64 !== expectedCount64 || size.histogram.count64 !== expectedCount64) {
|
|
7820
|
+
throw new Error("Load Engine V2 histogram distribution counts do not reconcile with its measurement summary.");
|
|
7821
|
+
}
|
|
7822
|
+
const bands = new Map();
|
|
7823
|
+
for (const band of latency.bands ?? []) {
|
|
7824
|
+
if (bands.has(band.bandId64)) {
|
|
7825
|
+
throw new Error("Load Engine V2 latency distribution contains a duplicate band.");
|
|
7826
|
+
}
|
|
7827
|
+
bands.set(band.bandId64, BigInt(band.count64));
|
|
7828
|
+
}
|
|
7829
|
+
if (bands.size !== 3 || !bands.has("0") || !bands.has("1") || !bands.has("2")
|
|
7830
|
+
|| [...bands.values()].reduce((sum, value) => sum + value, 0n) !== BigInt(expectedCount64)) {
|
|
7831
|
+
throw new Error("Load Engine V2 latency distribution bands do not reconcile with its count.");
|
|
7832
|
+
}
|
|
7833
|
+
const sourceStatuses = new Map();
|
|
7834
|
+
for (const measurement of sourceMeasurements) {
|
|
7835
|
+
for (const status of measurement.statusCodes) {
|
|
7836
|
+
const identity = (0, cluster_js_1.buildLoadEngineV2StatusIdentityKey)(status.statusCode, status.message);
|
|
7837
|
+
if (identity)
|
|
7838
|
+
sourceStatuses.set(identity.identity.toString("hex"), status);
|
|
7839
|
+
}
|
|
7840
|
+
}
|
|
7841
|
+
const statusCodes = new Map();
|
|
7842
|
+
const outcomeSummaries = outcome === "all"
|
|
7843
|
+
? summary.outcomes
|
|
7844
|
+
: summary.outcomes.filter((candidate) => candidate.outcome === outcome);
|
|
7845
|
+
for (const outcomeSummary of outcomeSummaries) {
|
|
7846
|
+
for (const status of outcomeSummary.statuses) {
|
|
7847
|
+
const source = sourceStatuses.get(status.statusIdentityKeyHex);
|
|
7848
|
+
statusCodes.set(`${outcomeSummary.outcome}\0${status.statusIdentityKeyHex}`, {
|
|
7849
|
+
statusCode: source?.statusCode ?? status.display,
|
|
7850
|
+
message: source?.message ?? "",
|
|
7851
|
+
isError: source?.isError ?? outcomeSummary.outcome === "fail",
|
|
7852
|
+
count: loadEngineV2SafeNumber(status.count64, "status count")
|
|
7853
|
+
});
|
|
7854
|
+
}
|
|
7855
|
+
}
|
|
7856
|
+
const expectedCount = loadEngineV2SafeNumber(expectedCount64, "measurement count");
|
|
7857
|
+
const sizeHistogram = load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(size.histogram);
|
|
7858
|
+
return buildHistogramMeasurement({
|
|
7859
|
+
count: expectedCount,
|
|
7860
|
+
allBytes: loadEngineV2SafeNumber(sizeHistogram.exactTotal.toString(), "measurement byte total"),
|
|
7861
|
+
latency: load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(latency.histogram),
|
|
7862
|
+
size: sizeHistogram,
|
|
7863
|
+
statusCodes,
|
|
7864
|
+
lessOrEq800: loadEngineV2SafeNumber((bands.get("0") ?? 0n).toString(), "latency band count"),
|
|
7865
|
+
more800Less1200: loadEngineV2SafeNumber((bands.get("1") ?? 0n).toString(), "latency band count"),
|
|
7866
|
+
moreOrEq1200: loadEngineV2SafeNumber((bands.get("2") ?? 0n).toString(), "latency band count")
|
|
7867
|
+
}, allRequestCount, durationMs);
|
|
7868
|
+
}
|
|
7869
|
+
function requireLoadEngineV2MeasurementDistribution(summary, outcome, unit, distributions) {
|
|
7870
|
+
const distribution = distributions.get(loadEngineV2DistributionProjectionKey(summary.seriesKind, summary.scenarioIndex64, summary.identityKeyHex, outcome, unit));
|
|
7871
|
+
if (!distribution || distribution.scenarioName !== summary.scenarioName) {
|
|
7872
|
+
throw new Error("Load Engine V2 measurement summary is missing its canonical histogram distribution.");
|
|
7873
|
+
}
|
|
7874
|
+
return distribution;
|
|
7875
|
+
}
|
|
7876
|
+
function loadEngineV2DistributionProjectionKey(seriesKind, scenarioIndex64, identityKeyHex, outcome, unit) {
|
|
7877
|
+
return [seriesKind, scenarioIndex64, identityKeyHex, outcome, unit].join("\0");
|
|
7878
|
+
}
|
|
7879
|
+
function compareLoadEngineV2Decimal(left, right) {
|
|
7880
|
+
const a = BigInt(left);
|
|
7881
|
+
const b = BigInt(right);
|
|
7882
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
7883
|
+
}
|
|
7884
|
+
function loadEngineV2SafeNumber(value, field) {
|
|
7885
|
+
const parsed = BigInt(value);
|
|
7886
|
+
if (parsed < 0n || parsed > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
7887
|
+
throw new Error(`Load Engine V2 ${field} exceeds the JavaScript safe integer range.`);
|
|
7888
|
+
}
|
|
7889
|
+
return Number(parsed);
|
|
7890
|
+
}
|
|
7891
|
+
function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo, requireHistogramArtifact = false) {
|
|
6013
7892
|
const completedUtc = new Date().toISOString();
|
|
6014
7893
|
if (!result.success || !result.stats) {
|
|
6015
7894
|
return attachNodeStatsAliases({
|
|
@@ -6042,12 +7921,12 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
|
|
|
6042
7921
|
isFailed: true,
|
|
6043
7922
|
errorCount: 1,
|
|
6044
7923
|
exceptionMessage: result.error ?? "Agent execution failed."
|
|
6045
|
-
}]
|
|
7924
|
+
}],
|
|
7925
|
+
reportingComplete: false
|
|
6046
7926
|
});
|
|
6047
7927
|
}
|
|
6048
7928
|
const metrics = normalizeMetricStatsPayload(result.stats.metrics, result.stats.durationMs ?? 0);
|
|
6049
|
-
|
|
6050
|
-
const stepStats = scenarioStats.flatMap((value) => value.stepStats);
|
|
7929
|
+
let scenarioStats = normalizeScenarioStatsPayload(result.stats.scenarioStats);
|
|
6051
7930
|
const thresholds = normalizeThresholdPayload(result.stats.thresholds);
|
|
6052
7931
|
const pluginsData = normalizePluginsPayload(result.stats.pluginsData);
|
|
6053
7932
|
const nodeInfo = {
|
|
@@ -6060,6 +7939,17 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
|
|
|
6060
7939
|
...testInfo,
|
|
6061
7940
|
...(result.stats.testInfo ?? {})
|
|
6062
7941
|
};
|
|
7942
|
+
const histogramArtifactBase64 = String(result.stats.histogramArtifactBase64 ?? "");
|
|
7943
|
+
if (requireHistogramArtifact && !histogramArtifactBase64) {
|
|
7944
|
+
throw new Error("Load Engine V2 result omits the mandatory LS-H1 histogram artifact.");
|
|
7945
|
+
}
|
|
7946
|
+
const histogramArtifact = histogramArtifactBase64
|
|
7947
|
+
? (0, load_engine_v2_js_1.parseLoadEngineV2HistogramArtifact)(Buffer.from(histogramArtifactBase64, "base64"))
|
|
7948
|
+
: undefined;
|
|
7949
|
+
if (histogramArtifact) {
|
|
7950
|
+
scenarioStats = projectLoadEngineV2MeasurementsFromArtifact(scenarioStats, histogramArtifact);
|
|
7951
|
+
}
|
|
7952
|
+
const stepStats = scenarioStats.flatMap((value) => value.stepStats);
|
|
6063
7953
|
return attachNodeStatsAliases({
|
|
6064
7954
|
startedUtc: testInfo.createdUtc,
|
|
6065
7955
|
completedUtc,
|
|
@@ -6082,6 +7972,17 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
|
|
|
6082
7972
|
sinkErrors: [],
|
|
6083
7973
|
reportFiles: [],
|
|
6084
7974
|
logFiles: normalizeAliasStringArray(result.stats.logFiles),
|
|
7975
|
+
generatorWarnings: (result.stats.generatorWarnings ?? []).map(attachGeneratorWarningAliases),
|
|
7976
|
+
schedulerSegments: (result.stats.schedulerSegments ?? []).map(normalizeSchedulerSegment),
|
|
7977
|
+
schedulerStats: result.stats.schedulerStats
|
|
7978
|
+
? normalizeSchedulerStats(result.stats.schedulerStats)
|
|
7979
|
+
: undefined,
|
|
7980
|
+
[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: histogramArtifact?.distributions
|
|
7981
|
+
.filter((record) => record.seriesKind === "scheduler-decision-lag"
|
|
7982
|
+
|| record.seriesKind === "scheduler-start-lag")
|
|
7983
|
+
.map(cloneLoadEngineV2DistributionRecord),
|
|
7984
|
+
observationDeliveryStats: normalizeObservationDeliveryStats(result.stats.observationDeliveryStats ?? emptyObservationDeliveryStats()),
|
|
7985
|
+
reportingComplete: result.stats.reportingComplete ?? false,
|
|
6085
7986
|
findScenarioStats: (scenarioName) => scenarioStats.find((value) => value.scenarioName === scenarioName),
|
|
6086
7987
|
getScenarioStats: (scenarioName) => {
|
|
6087
7988
|
const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
|
|
@@ -6092,7 +7993,46 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
|
|
|
6092
7993
|
}
|
|
6093
7994
|
});
|
|
6094
7995
|
}
|
|
6095
|
-
function
|
|
7996
|
+
function emptyObservationDeliveryStats() {
|
|
7997
|
+
return normalizeObservationDeliveryStats({
|
|
7998
|
+
lastBatchSequence64: "-1",
|
|
7999
|
+
capturedCount64: "0",
|
|
8000
|
+
deliveredCount64: "0",
|
|
8001
|
+
droppedBufferCount64: "0",
|
|
8002
|
+
droppedSinkCount64: "0"
|
|
8003
|
+
});
|
|
8004
|
+
}
|
|
8005
|
+
function aggregateObservationDeliveryStats(nodes) {
|
|
8006
|
+
let lastBatchSequence = -1n;
|
|
8007
|
+
let captured = 0n;
|
|
8008
|
+
let delivered = 0n;
|
|
8009
|
+
let droppedBuffer = 0n;
|
|
8010
|
+
let droppedSink = 0n;
|
|
8011
|
+
for (const node of nodes) {
|
|
8012
|
+
const stats = node.observationDeliveryStats ?? emptyObservationDeliveryStats();
|
|
8013
|
+
lastBatchSequence = maxBigInt(lastBatchSequence, parseObservationDecimal(stats.lastBatchSequence64, -1n));
|
|
8014
|
+
captured += parseObservationDecimal(stats.capturedCount64);
|
|
8015
|
+
delivered += parseObservationDecimal(stats.deliveredCount64);
|
|
8016
|
+
droppedBuffer += parseObservationDecimal(stats.droppedBufferCount64);
|
|
8017
|
+
droppedSink += parseObservationDecimal(stats.droppedSinkCount64);
|
|
8018
|
+
}
|
|
8019
|
+
return normalizeObservationDeliveryStats({
|
|
8020
|
+
lastBatchSequence64: lastBatchSequence.toString(),
|
|
8021
|
+
capturedCount64: captured.toString(),
|
|
8022
|
+
deliveredCount64: delivered.toString(),
|
|
8023
|
+
droppedBufferCount64: droppedBuffer.toString(),
|
|
8024
|
+
droppedSinkCount64: droppedSink.toString()
|
|
8025
|
+
});
|
|
8026
|
+
}
|
|
8027
|
+
function parseObservationDecimal(value, fallback = 0n) {
|
|
8028
|
+
try {
|
|
8029
|
+
return BigInt(value);
|
|
8030
|
+
}
|
|
8031
|
+
catch {
|
|
8032
|
+
return fallback;
|
|
8033
|
+
}
|
|
8034
|
+
}
|
|
8035
|
+
function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes, requireHistograms = false) {
|
|
6096
8036
|
if (!nodes.length) {
|
|
6097
8037
|
return buildEmptyNodeStats({
|
|
6098
8038
|
startedUtc: testInfo.createdUtc,
|
|
@@ -6101,12 +8041,22 @@ function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes) {
|
|
|
6101
8041
|
testInfo
|
|
6102
8042
|
});
|
|
6103
8043
|
}
|
|
6104
|
-
const scenarioStats = aggregateScenarioStats(nodes);
|
|
8044
|
+
const scenarioStats = aggregateScenarioStats(nodes, requireHistograms);
|
|
6105
8045
|
const stepStats = scenarioStats.flatMap((value) => value.stepStats);
|
|
6106
8046
|
const metrics = aggregateMetricStats(nodes);
|
|
6107
8047
|
const thresholds = aggregateThresholds(nodes);
|
|
6108
8048
|
const pluginsData = aggregatePluginsData(nodes);
|
|
6109
8049
|
const completedUtc = new Date().toISOString();
|
|
8050
|
+
const schedulerSegments = nodes.flatMap((value) => value.schedulerSegments ?? []);
|
|
8051
|
+
const schedulerStatsRows = nodes.flatMap((value) => value.schedulerStats ? [value.schedulerStats] : []);
|
|
8052
|
+
const schedulerStats = schedulerSegments.length || schedulerStatsRows.length
|
|
8053
|
+
? {
|
|
8054
|
+
configuredMaxInFlight: schedulerStatsRows.reduce((maximum, value) => Math.max(maximum, value.configuredMaxInFlight), 0),
|
|
8055
|
+
maxInFlightObserved: schedulerStatsRows.reduce((maximum, value) => Math.max(maximum, value.maxInFlightObserved), 0),
|
|
8056
|
+
currentInFlight: schedulerStatsRows.reduce((sum, value) => sum + value.currentInFlight, 0),
|
|
8057
|
+
segments: schedulerSegments
|
|
8058
|
+
}
|
|
8059
|
+
: undefined;
|
|
6110
8060
|
return attachNodeStatsAliases({
|
|
6111
8061
|
startedUtc: testInfo.createdUtc,
|
|
6112
8062
|
completedUtc,
|
|
@@ -6129,6 +8079,12 @@ function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes) {
|
|
|
6129
8079
|
sinkErrors: [],
|
|
6130
8080
|
reportFiles: [],
|
|
6131
8081
|
logFiles: mergeStringArrays(...nodes.map((value) => value.logFiles ?? [])),
|
|
8082
|
+
generatorWarnings: nodes.flatMap((value) => value.generatorWarnings ?? []),
|
|
8083
|
+
...(schedulerSegments.length ? { schedulerSegments } : {}),
|
|
8084
|
+
...(schedulerStats ? { schedulerStats } : {}),
|
|
8085
|
+
[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: mergeLoadEngineV2SchedulerDistributions(nodes.flatMap((value) => value[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS] ?? [])),
|
|
8086
|
+
observationDeliveryStats: aggregateObservationDeliveryStats(nodes),
|
|
8087
|
+
reportingComplete: nodes.every((value) => value.reportingComplete ?? false),
|
|
6132
8088
|
findScenarioStats: (scenarioName) => scenarioStats.find((value) => value.scenarioName === scenarioName),
|
|
6133
8089
|
getScenarioStats: (scenarioName) => {
|
|
6134
8090
|
const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
|
|
@@ -6139,7 +8095,7 @@ function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes) {
|
|
|
6139
8095
|
}
|
|
6140
8096
|
});
|
|
6141
8097
|
}
|
|
6142
|
-
function aggregateScenarioStats(nodes) {
|
|
8098
|
+
function aggregateScenarioStats(nodes, requireHistograms = false) {
|
|
6143
8099
|
const grouped = new Map();
|
|
6144
8100
|
for (const node of nodes) {
|
|
6145
8101
|
for (const scenario of node.scenarioStats) {
|
|
@@ -6153,9 +8109,12 @@ function aggregateScenarioStats(nodes) {
|
|
|
6153
8109
|
.map((items) => {
|
|
6154
8110
|
const allRequestCount = items.reduce((sum, value) => sum + value.allRequestCount, 0);
|
|
6155
8111
|
const durationMs = items.reduce((max, value) => Math.max(max, value.durationMs), 0);
|
|
6156
|
-
const ok = aggregateMeasurementStats(items.map((value) => value.ok), allRequestCount, durationMs);
|
|
6157
|
-
const fail = aggregateMeasurementStats(items.map((value) => value.fail), allRequestCount, durationMs);
|
|
6158
|
-
const
|
|
8112
|
+
const ok = aggregateMeasurementStats(items.map((value) => value.ok), allRequestCount, durationMs, requireHistograms);
|
|
8113
|
+
const fail = aggregateMeasurementStats(items.map((value) => value.fail), allRequestCount, durationMs, requireHistograms);
|
|
8114
|
+
const allMeasurement = requireHistograms
|
|
8115
|
+
? aggregateMeasurementStats([ok, fail], allRequestCount, durationMs, true)
|
|
8116
|
+
: undefined;
|
|
8117
|
+
const stepStats = aggregateStepStats(items, requireHistograms);
|
|
6159
8118
|
const scenarioName = items[0]?.scenarioName ?? "";
|
|
6160
8119
|
const currentOperation = selectScenarioOperation(items.map((value) => value.currentOperation));
|
|
6161
8120
|
const loadSimulationStats = items.find((value) => value.loadSimulationStats.simulationName)?.loadSimulationStats ?? {
|
|
@@ -6180,6 +8139,7 @@ function aggregateScenarioStats(nodes) {
|
|
|
6180
8139
|
durationMs,
|
|
6181
8140
|
ok,
|
|
6182
8141
|
fail,
|
|
8142
|
+
...(allMeasurement ? { allMeasurement } : {}),
|
|
6183
8143
|
loadSimulationStats,
|
|
6184
8144
|
sortIndex: Math.min(...items.map((value) => value.sortIndex)),
|
|
6185
8145
|
stepStats,
|
|
@@ -6195,7 +8155,7 @@ function aggregateScenarioStats(nodes) {
|
|
|
6195
8155
|
return attachScenarioStatsAliases(scenario);
|
|
6196
8156
|
});
|
|
6197
8157
|
}
|
|
6198
|
-
function aggregateStepStats(scenarios) {
|
|
8158
|
+
function aggregateStepStats(scenarios, requireHistograms = false) {
|
|
6199
8159
|
const grouped = new Map();
|
|
6200
8160
|
for (const scenario of scenarios) {
|
|
6201
8161
|
for (const step of scenario.stepStats) {
|
|
@@ -6209,8 +8169,11 @@ function aggregateStepStats(scenarios) {
|
|
|
6209
8169
|
return Array.from(grouped.values())
|
|
6210
8170
|
.sort((left, right) => Math.min(...left.map((value) => value.sortIndex)) - Math.min(...right.map((value) => value.sortIndex)))
|
|
6211
8171
|
.map((items) => {
|
|
6212
|
-
const ok = aggregateMeasurementStats(items.map((value) => value.ok), allScenarioRequests, scenarioDurationMs);
|
|
6213
|
-
const fail = aggregateMeasurementStats(items.map((value) => value.fail), allScenarioRequests, scenarioDurationMs);
|
|
8172
|
+
const ok = aggregateMeasurementStats(items.map((value) => value.ok), allScenarioRequests, scenarioDurationMs, requireHistograms);
|
|
8173
|
+
const fail = aggregateMeasurementStats(items.map((value) => value.fail), allScenarioRequests, scenarioDurationMs, requireHistograms);
|
|
8174
|
+
const allMeasurement = requireHistograms
|
|
8175
|
+
? aggregateMeasurementStats([ok, fail], allScenarioRequests, scenarioDurationMs, true)
|
|
8176
|
+
: undefined;
|
|
6214
8177
|
const requestCount = ok.request.count + fail.request.count;
|
|
6215
8178
|
return {
|
|
6216
8179
|
scenarioName: items[0]?.scenarioName ?? "",
|
|
@@ -6226,14 +8189,53 @@ function aggregateStepStats(scenarios) {
|
|
|
6226
8189
|
statusCodes: aggregateStatusCodeCounts(ok.statusCodes, fail.statusCodes),
|
|
6227
8190
|
ok,
|
|
6228
8191
|
fail,
|
|
8192
|
+
...(allMeasurement ? { allMeasurement } : {}),
|
|
6229
8193
|
sortIndex: Math.min(...items.map((value) => value.sortIndex))
|
|
6230
8194
|
};
|
|
6231
8195
|
});
|
|
6232
8196
|
}
|
|
6233
|
-
function aggregateMeasurementStats(measurements, allRequestCount, durationMs) {
|
|
8197
|
+
function aggregateMeasurementStats(measurements, allRequestCount, durationMs, requireHistograms = false) {
|
|
6234
8198
|
if (!measurements.length) {
|
|
6235
8199
|
return buildMeasurementPlaceholder(0, allRequestCount, durationMs);
|
|
6236
8200
|
}
|
|
8201
|
+
if (measurements.every((measurement) => measurement.histogramSidecar)) {
|
|
8202
|
+
const latency = load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(measurements[0].histogramSidecar.latency);
|
|
8203
|
+
const size = load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(measurements[0].histogramSidecar.size);
|
|
8204
|
+
for (const measurement of measurements.slice(1)) {
|
|
8205
|
+
latency.merge(load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(measurement.histogramSidecar.latency));
|
|
8206
|
+
size.merge(load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(measurement.histogramSidecar.size));
|
|
8207
|
+
}
|
|
8208
|
+
const statusCodes = new Map();
|
|
8209
|
+
for (const measurement of measurements) {
|
|
8210
|
+
for (const status of measurement.statusCodes) {
|
|
8211
|
+
const key = `${status.statusCode}|${status.message}|${status.isError ? "1" : "0"}`;
|
|
8212
|
+
const current = statusCodes.get(key);
|
|
8213
|
+
if (current)
|
|
8214
|
+
current.count += status.count;
|
|
8215
|
+
else
|
|
8216
|
+
statusCodes.set(key, {
|
|
8217
|
+
statusCode: status.statusCode,
|
|
8218
|
+
message: status.message,
|
|
8219
|
+
isError: status.isError,
|
|
8220
|
+
count: status.count
|
|
8221
|
+
});
|
|
8222
|
+
}
|
|
8223
|
+
}
|
|
8224
|
+
const sumSidecar = (key) => Number(measurements.reduce((sum, measurement) => sum + BigInt(measurement.histogramSidecar[key]), 0n));
|
|
8225
|
+
return buildHistogramMeasurement({
|
|
8226
|
+
count: Number(latency.count),
|
|
8227
|
+
allBytes: measurements.reduce((sum, measurement) => sum + measurement.dataTransfer.allBytes, 0),
|
|
8228
|
+
latency,
|
|
8229
|
+
size,
|
|
8230
|
+
statusCodes,
|
|
8231
|
+
lessOrEq800: sumSidecar("lessOrEq80064"),
|
|
8232
|
+
more800Less1200: sumSidecar("more800Less120064"),
|
|
8233
|
+
moreOrEq1200: sumSidecar("moreOrEq120064")
|
|
8234
|
+
}, allRequestCount, durationMs);
|
|
8235
|
+
}
|
|
8236
|
+
if (requireHistograms) {
|
|
8237
|
+
throw new Error("Load Engine V2 aggregation requires canonical histogram state for every node measurement.");
|
|
8238
|
+
}
|
|
6237
8239
|
const weights = measurements.map((value) => value.request.count);
|
|
6238
8240
|
const totalCount = measurements.reduce((sum, value) => sum + value.request.count, 0);
|
|
6239
8241
|
return {
|
|
@@ -8055,6 +10057,68 @@ function normalizeOptionalReportFormats(value) {
|
|
|
8055
10057
|
const normalized = normalizeReportFormats(value);
|
|
8056
10058
|
return normalized.length ? normalized : undefined;
|
|
8057
10059
|
}
|
|
10060
|
+
function normalizeDeclaredStepNames(value) {
|
|
10061
|
+
if (!Array.isArray(value)) {
|
|
10062
|
+
throw new TypeError("Declared step names must be provided as text values.");
|
|
10063
|
+
}
|
|
10064
|
+
const seen = new Set();
|
|
10065
|
+
const normalized = [];
|
|
10066
|
+
for (const entry of value) {
|
|
10067
|
+
if (typeof entry !== "string" || !entry.trim()) {
|
|
10068
|
+
throw new Error("Declared step name must be non-empty text.");
|
|
10069
|
+
}
|
|
10070
|
+
const stepName = entry.trim();
|
|
10071
|
+
for (let index = 0; index < stepName.length; index += 1) {
|
|
10072
|
+
const code = stepName.charCodeAt(index);
|
|
10073
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
10074
|
+
const next = stepName.charCodeAt(index + 1);
|
|
10075
|
+
if (!(next >= 0xdc00 && next <= 0xdfff)) {
|
|
10076
|
+
throw new Error("Declared step name contains an invalid Unicode scalar.");
|
|
10077
|
+
}
|
|
10078
|
+
index += 1;
|
|
10079
|
+
}
|
|
10080
|
+
else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
10081
|
+
throw new Error("Declared step name contains an invalid Unicode scalar.");
|
|
10082
|
+
}
|
|
10083
|
+
}
|
|
10084
|
+
if (!seen.has(stepName)) {
|
|
10085
|
+
seen.add(stepName);
|
|
10086
|
+
normalized.push(stepName);
|
|
10087
|
+
}
|
|
10088
|
+
}
|
|
10089
|
+
return normalized;
|
|
10090
|
+
}
|
|
10091
|
+
function resolveIterationObservationSettings(options) {
|
|
10092
|
+
return {
|
|
10093
|
+
flushIntervalMs: options.iterationObservationFlushIntervalSeconds === undefined
|
|
10094
|
+
? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.flushIntervalMs
|
|
10095
|
+
: options.iterationObservationFlushIntervalSeconds * 1000,
|
|
10096
|
+
maxBufferBytes: options.maxIterationObservationBufferBytes
|
|
10097
|
+
?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.maxBufferBytes,
|
|
10098
|
+
maxObservationsPerBatch: options.maxIterationObservationsPerBatch
|
|
10099
|
+
?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.maxObservationsPerBatch,
|
|
10100
|
+
maxBatchBytes: options.maxIterationObservationBatchBytes
|
|
10101
|
+
?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.maxBatchBytes,
|
|
10102
|
+
sinkQueueDepth: options.iterationObservationSinkQueueDepth
|
|
10103
|
+
?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.sinkQueueDepth,
|
|
10104
|
+
sinkParallelism: options.iterationObservationSinkParallelism
|
|
10105
|
+
?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.sinkParallelism,
|
|
10106
|
+
drainTimeoutMs: options.iterationObservationDrainTimeoutSeconds === undefined
|
|
10107
|
+
? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.drainTimeoutMs
|
|
10108
|
+
: options.iterationObservationDrainTimeoutSeconds * 1000
|
|
10109
|
+
};
|
|
10110
|
+
}
|
|
10111
|
+
function validateRunContextIterationObservationSettings(values) {
|
|
10112
|
+
(0, iteration_observations_js_1.validateIterationObservationSettings)(resolveIterationObservationSettings({
|
|
10113
|
+
iterationObservationFlushIntervalSeconds: values.IterationObservationFlushIntervalSeconds,
|
|
10114
|
+
maxIterationObservationBufferBytes: values.MaxIterationObservationBufferBytes,
|
|
10115
|
+
maxIterationObservationsPerBatch: values.MaxIterationObservationsPerBatch,
|
|
10116
|
+
maxIterationObservationBatchBytes: values.MaxIterationObservationBatchBytes,
|
|
10117
|
+
iterationObservationSinkQueueDepth: values.IterationObservationSinkQueueDepth,
|
|
10118
|
+
iterationObservationSinkParallelism: values.IterationObservationSinkParallelism,
|
|
10119
|
+
iterationObservationDrainTimeoutSeconds: values.IterationObservationDrainTimeoutSeconds
|
|
10120
|
+
}));
|
|
10121
|
+
}
|
|
8058
10122
|
function assertNoDisableLicenseEnforcementOption(value, source) {
|
|
8059
10123
|
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
8060
10124
|
return;
|
|
@@ -8073,12 +10137,121 @@ function normalizeRunContextCollectionShapes(values) {
|
|
|
8073
10137
|
TargetScenarios: normalizeOptionalStringArray(values.TargetScenarios),
|
|
8074
10138
|
AgentTargetScenarios: normalizeOptionalStringArray(values.AgentTargetScenarios),
|
|
8075
10139
|
CoordinatorTargetScenarios: normalizeOptionalStringArray(values.CoordinatorTargetScenarios),
|
|
10140
|
+
ExpectedAgentIds: normalizeOptionalStringArray(values.ExpectedAgentIds),
|
|
8076
10141
|
ReportFormats: normalizeOptionalReportFormats(values.ReportFormats)
|
|
8077
10142
|
};
|
|
8078
10143
|
validateNamedReportingSinks(normalized.ReportingSinks ?? []);
|
|
8079
10144
|
validateNamedWorkerPlugins(normalized.WorkerPlugins ?? []);
|
|
10145
|
+
validateLoadEngineV2Options(normalized.LoadEngineContractVersion, normalized.MaxInFlight);
|
|
10146
|
+
validateRunContextIterationObservationSettings(normalized);
|
|
8080
10147
|
return normalized;
|
|
8081
10148
|
}
|
|
10149
|
+
function normalizeAliasStringRecord(value) {
|
|
10150
|
+
const source = asAliasRecord(value);
|
|
10151
|
+
const output = {};
|
|
10152
|
+
for (const [key, entry] of Object.entries(source)) {
|
|
10153
|
+
output[key] = String(entry);
|
|
10154
|
+
}
|
|
10155
|
+
return output;
|
|
10156
|
+
}
|
|
10157
|
+
function attachGeneratorWarningAliases(value) {
|
|
10158
|
+
const source = asAliasRecord(value);
|
|
10159
|
+
const projected = {
|
|
10160
|
+
code: pickAliasString(source, "code", "Code"),
|
|
10161
|
+
...(hasAliasValue(source, "sinkName", "SinkName")
|
|
10162
|
+
? { sinkName: pickAliasString(source, "sinkName", "SinkName") }
|
|
10163
|
+
: {}),
|
|
10164
|
+
scenarioName: pickAliasString(source, "scenarioName", "ScenarioName"),
|
|
10165
|
+
...(hasAliasValue(source, "scenarioIndex", "ScenarioIndex")
|
|
10166
|
+
? { scenarioIndex: pickAliasNumber(source, "scenarioIndex", "ScenarioIndex") }
|
|
10167
|
+
: {}),
|
|
10168
|
+
simulationIndex: pickAliasNumber(source, "simulationIndex", "SimulationIndex"),
|
|
10169
|
+
...(hasAliasValue(source, "simulationKind", "SimulationKind")
|
|
10170
|
+
? { simulationKind: pickAliasString(source, "simulationKind", "SimulationKind") }
|
|
10171
|
+
: {}),
|
|
10172
|
+
count64: pickAliasString(source, "count64", "Count64"),
|
|
10173
|
+
message: pickAliasString(source, "message", "Message"),
|
|
10174
|
+
firstObservedUtcNs: pickAliasString(source, "firstObservedUtcNs", "FirstObservedUtcNs"),
|
|
10175
|
+
lastObservedUtcNs: pickAliasString(source, "lastObservedUtcNs", "LastObservedUtcNs")
|
|
10176
|
+
};
|
|
10177
|
+
return attachAliasMap(projected, {
|
|
10178
|
+
Code: "code",
|
|
10179
|
+
SinkName: "sinkName",
|
|
10180
|
+
ScenarioName: "scenarioName",
|
|
10181
|
+
ScenarioIndex: "scenarioIndex",
|
|
10182
|
+
SimulationIndex: "simulationIndex",
|
|
10183
|
+
SimulationKind: "simulationKind",
|
|
10184
|
+
Count64: "count64",
|
|
10185
|
+
Message: "message",
|
|
10186
|
+
FirstObservedUtcNs: "firstObservedUtcNs",
|
|
10187
|
+
LastObservedUtcNs: "lastObservedUtcNs"
|
|
10188
|
+
});
|
|
10189
|
+
}
|
|
10190
|
+
function normalizeSchedulerSegment(value) {
|
|
10191
|
+
const source = asAliasRecord(value);
|
|
10192
|
+
return {
|
|
10193
|
+
scenarioName: pickAliasString(source, "scenarioName", "ScenarioName"),
|
|
10194
|
+
scenarioIndex: pickAliasNumber(source, "scenarioIndex", "ScenarioIndex"),
|
|
10195
|
+
simulationIndex: pickAliasNumber(source, "simulationIndex", "SimulationIndex"),
|
|
10196
|
+
kind: pickAliasString(source, "kind", "Kind"),
|
|
10197
|
+
shardIndex: pickAliasNumber(source, "shardIndex", "ShardIndex"),
|
|
10198
|
+
shardCount: Math.max(pickAliasNumber(source, "shardCount", "ShardCount"), 1),
|
|
10199
|
+
plannedIterations64: pickAliasString(source, "plannedIterations64", "PlannedIterations64"),
|
|
10200
|
+
dueIterations64: pickAliasString(source, "dueIterations64", "DueIterations64"),
|
|
10201
|
+
startedIterations64: pickAliasString(source, "startedIterations64", "StartedIterations64"),
|
|
10202
|
+
completedIterations64: pickAliasString(source, "completedIterations64", "CompletedIterations64"),
|
|
10203
|
+
droppedIterations64: pickAliasString(source, "droppedIterations64", "DroppedIterations64"),
|
|
10204
|
+
unreachedIterations64: pickAliasString(source, "unreachedIterations64", "UnreachedIterations64"),
|
|
10205
|
+
requestedWorkerSlots64: pickAliasString(source, "requestedWorkerSlots64", "RequestedWorkerSlots64"),
|
|
10206
|
+
startedWorkerSlots64: pickAliasString(source, "startedWorkerSlots64", "StartedWorkerSlots64"),
|
|
10207
|
+
unavailableWorkerSlots64: pickAliasString(source, "unavailableWorkerSlots64", "UnavailableWorkerSlots64"),
|
|
10208
|
+
dropReasons: normalizeAliasStringRecord(pickAliasValue(source, "dropReasons", "DropReasons")),
|
|
10209
|
+
unavailableWorkerReasons: normalizeAliasStringRecord(pickAliasValue(source, "unavailableWorkerReasons", "UnavailableWorkerReasons")),
|
|
10210
|
+
deliveryPercent: pickAliasNumber(source, "deliveryPercent", "DeliveryPercent"),
|
|
10211
|
+
accountingComplete: pickAliasBoolean(source, "accountingComplete", "AccountingComplete")
|
|
10212
|
+
};
|
|
10213
|
+
}
|
|
10214
|
+
function normalizeSchedulerStats(value) {
|
|
10215
|
+
const source = asAliasRecord(value);
|
|
10216
|
+
return {
|
|
10217
|
+
configuredMaxInFlight: pickAliasNumber(source, "configuredMaxInFlight", "ConfiguredMaxInFlight"),
|
|
10218
|
+
maxInFlightObserved: pickAliasNumber(source, "maxInFlightObserved", "MaxInFlightObserved"),
|
|
10219
|
+
currentInFlight: pickAliasNumber(source, "currentInFlight", "CurrentInFlight"),
|
|
10220
|
+
segments: pickAliasArray(source, "segments", "Segments").map(normalizeSchedulerSegment)
|
|
10221
|
+
};
|
|
10222
|
+
}
|
|
10223
|
+
function normalizeObservationDeliveryStats(value) {
|
|
10224
|
+
const source = asAliasRecord(value);
|
|
10225
|
+
return attachAliasMap({
|
|
10226
|
+
lastBatchSequence64: pickAliasString(source, "lastBatchSequence64", "LastBatchSequence64"),
|
|
10227
|
+
capturedCount64: pickAliasString(source, "capturedCount64", "CapturedCount64"),
|
|
10228
|
+
deliveredCount64: pickAliasString(source, "deliveredCount64", "DeliveredCount64"),
|
|
10229
|
+
droppedBufferCount64: pickAliasString(source, "droppedBufferCount64", "DroppedBufferCount64"),
|
|
10230
|
+
droppedSinkCount64: pickAliasString(source, "droppedSinkCount64", "DroppedSinkCount64")
|
|
10231
|
+
}, {
|
|
10232
|
+
LastBatchSequence64: "lastBatchSequence64",
|
|
10233
|
+
CapturedCount64: "capturedCount64",
|
|
10234
|
+
DeliveredCount64: "deliveredCount64",
|
|
10235
|
+
DroppedBufferCount64: "droppedBufferCount64",
|
|
10236
|
+
DroppedSinkCount64: "droppedSinkCount64"
|
|
10237
|
+
});
|
|
10238
|
+
}
|
|
10239
|
+
function validateLoadEngineV2Options(contractVersion, maxInFlight) {
|
|
10240
|
+
if (contractVersion !== undefined && contractVersion !== 1 && contractVersion !== 2) {
|
|
10241
|
+
throw new RangeError("Load engine contract version must be either 1 or 2.");
|
|
10242
|
+
}
|
|
10243
|
+
if (maxInFlight !== undefined) {
|
|
10244
|
+
validateV2MaxInFlight(contractVersion, maxInFlight);
|
|
10245
|
+
}
|
|
10246
|
+
}
|
|
10247
|
+
function validateV2MaxInFlight(contractVersion, maxInFlight) {
|
|
10248
|
+
if (contractVersion !== 2) {
|
|
10249
|
+
throw new Error("MaxInFlight is available only when Load Engine V2 is selected.");
|
|
10250
|
+
}
|
|
10251
|
+
if (!Number.isSafeInteger(maxInFlight) || maxInFlight < 1 || maxInFlight > 1000000) {
|
|
10252
|
+
throw new RangeError("MaxInFlight must be an integer from 1 through 1000000.");
|
|
10253
|
+
}
|
|
10254
|
+
}
|
|
8082
10255
|
function normalizeRunnerOptionCollectionShapes(options) {
|
|
8083
10256
|
assertNoDisableLicenseEnforcementOption(options, "LoadStrikeRunner");
|
|
8084
10257
|
const normalized = {
|
|
@@ -8086,10 +10259,13 @@ function normalizeRunnerOptionCollectionShapes(options) {
|
|
|
8086
10259
|
targetScenarios: normalizeOptionalStringArray(options.targetScenarios),
|
|
8087
10260
|
agentTargetScenarios: normalizeOptionalStringArray(options.agentTargetScenarios),
|
|
8088
10261
|
coordinatorTargetScenarios: normalizeOptionalStringArray(options.coordinatorTargetScenarios),
|
|
10262
|
+
expectedAgentIds: normalizeOptionalStringArray(options.expectedAgentIds),
|
|
8089
10263
|
reportFormats: normalizeOptionalReportFormats(options.reportFormats)
|
|
8090
10264
|
};
|
|
8091
10265
|
validateNamedReportingSinks(normalized.reportingSinks ?? []);
|
|
8092
10266
|
validateNamedWorkerPlugins(normalized.workerPlugins ?? []);
|
|
10267
|
+
validateLoadEngineV2Options(normalized.loadEngineContractVersion, normalized.maxInFlight);
|
|
10268
|
+
(0, iteration_observations_js_1.validateIterationObservationSettings)(resolveIterationObservationSettings(normalized));
|
|
8093
10269
|
return normalized;
|
|
8094
10270
|
}
|
|
8095
10271
|
function normalizedRuntimePolicyErrorMode(value) {
|
|
@@ -8153,6 +10329,7 @@ function extractContextOverridesFromConfig(config) {
|
|
|
8153
10329
|
setString("ReportFileName", "ReportFileName", "LoadStrike:ReportFileName");
|
|
8154
10330
|
setString("ClusterId", "ClusterId", "LoadStrike:ClusterId");
|
|
8155
10331
|
setString("AgentGroup", "AgentGroup", "LoadStrike:AgentGroup");
|
|
10332
|
+
setString("AgentId", "AgentId", "LoadStrike:AgentId");
|
|
8156
10333
|
setString("NatsServerUrl", "NatsServerUrl", "LoadStrike:NatsServerUrl");
|
|
8157
10334
|
setString("RunnerKey", "RunnerKey", "LoadStrike:RunnerKey");
|
|
8158
10335
|
setString("RuntimePolicyErrorMode", "RuntimePolicyErrorMode", "LoadStrike:RuntimePolicyErrorMode");
|
|
@@ -8182,6 +10359,13 @@ function extractContextOverridesFromConfig(config) {
|
|
|
8182
10359
|
}
|
|
8183
10360
|
}
|
|
8184
10361
|
setPositiveNumber("ReportingIntervalSeconds", "ReportingIntervalSeconds", "LoadStrike:ReportingIntervalSeconds");
|
|
10362
|
+
setPositiveNumber("IterationObservationFlushIntervalSeconds", "IterationObservationFlushInterval", "IterationObservationFlushIntervalSeconds", "LoadStrike:IterationObservationFlushInterval");
|
|
10363
|
+
setPositiveNumber("MaxIterationObservationBufferBytes", "MaxIterationObservationBufferBytes", "LoadStrike:MaxIterationObservationBufferBytes");
|
|
10364
|
+
setPositiveNumber("MaxIterationObservationsPerBatch", "MaxIterationObservationsPerBatch", "LoadStrike:MaxIterationObservationsPerBatch");
|
|
10365
|
+
setPositiveNumber("MaxIterationObservationBatchBytes", "MaxIterationObservationBatchBytes", "LoadStrike:MaxIterationObservationBatchBytes");
|
|
10366
|
+
setPositiveNumber("IterationObservationSinkQueueDepth", "IterationObservationSinkQueueDepth", "LoadStrike:IterationObservationSinkQueueDepth");
|
|
10367
|
+
setPositiveNumber("IterationObservationSinkParallelism", "IterationObservationSinkParallelism", "LoadStrike:IterationObservationSinkParallelism");
|
|
10368
|
+
setPositiveNumber("IterationObservationDrainTimeoutSeconds", "IterationObservationDrainTimeout", "IterationObservationDrainTimeoutSeconds", "LoadStrike:IterationObservationDrainTimeout");
|
|
8185
10369
|
setPositiveNumber("ScenarioCompletionTimeoutSeconds", "ScenarioCompletionTimeoutSeconds", "LoadStrike:ScenarioCompletionTimeoutSeconds");
|
|
8186
10370
|
setPositiveNumber("ClusterCommandTimeoutSeconds", "ClusterCommandTimeoutSeconds", "LoadStrike:ClusterCommandTimeoutSeconds");
|
|
8187
10371
|
setPositiveNumber("LicenseValidationTimeoutSeconds", "LicenseValidationTimeoutSeconds", "LoadStrike:LicenseValidation:TimeoutSeconds");
|
|
@@ -8189,6 +10373,14 @@ function extractContextOverridesFromConfig(config) {
|
|
|
8189
10373
|
if (reportingIntervalMs > 0) {
|
|
8190
10374
|
patch.ReportingIntervalSeconds = reportingIntervalMs / 1000;
|
|
8191
10375
|
}
|
|
10376
|
+
const observationFlushIntervalMs = toNumber(pick("IterationObservationFlushIntervalMs", "LoadStrike:IterationObservationFlushIntervalMs"));
|
|
10377
|
+
if (observationFlushIntervalMs > 0) {
|
|
10378
|
+
patch.IterationObservationFlushIntervalSeconds = observationFlushIntervalMs / 1000;
|
|
10379
|
+
}
|
|
10380
|
+
const observationDrainTimeoutMs = toNumber(pick("IterationObservationDrainTimeoutMs", "LoadStrike:IterationObservationDrainTimeoutMs"));
|
|
10381
|
+
if (observationDrainTimeoutMs > 0) {
|
|
10382
|
+
patch.IterationObservationDrainTimeoutSeconds = observationDrainTimeoutMs / 1000;
|
|
10383
|
+
}
|
|
8192
10384
|
const scenarioCompletionTimeoutMs = toNumber(pick("ScenarioCompletionTimeoutMs", "LoadStrike:ScenarioCompletionTimeoutMs"));
|
|
8193
10385
|
if (scenarioCompletionTimeoutMs > 0) {
|
|
8194
10386
|
patch.ScenarioCompletionTimeoutSeconds = scenarioCompletionTimeoutMs / 1000;
|
|
@@ -8233,6 +10425,10 @@ function extractContextOverridesFromConfig(config) {
|
|
|
8233
10425
|
if (agentTargetScenarios.length) {
|
|
8234
10426
|
patch.AgentTargetScenarios = agentTargetScenarios;
|
|
8235
10427
|
}
|
|
10428
|
+
const expectedAgentIds = normalizeStringArray(pick("ExpectedAgentIds", "LoadStrike:ExpectedAgentIds"));
|
|
10429
|
+
if (expectedAgentIds.length) {
|
|
10430
|
+
patch.ExpectedAgentIds = expectedAgentIds;
|
|
10431
|
+
}
|
|
8236
10432
|
const coordinatorTargetScenarios = normalizeStringArray(pick("CoordinatorTargetScenarios", "LoadStrike:CoordinatorTargetScenarios"));
|
|
8237
10433
|
if (coordinatorTargetScenarios.length) {
|
|
8238
10434
|
patch.CoordinatorTargetScenarios = coordinatorTargetScenarios;
|
|
@@ -8295,10 +10491,14 @@ function toRunContext(options) {
|
|
|
8295
10491
|
const normalized = normalizeRunnerOptionCollectionShapes(options);
|
|
8296
10492
|
return {
|
|
8297
10493
|
ConsoleMetricsEnabled: normalized.displayConsoleMetrics,
|
|
10494
|
+
LoadEngineContractVersion: normalized.loadEngineContractVersion,
|
|
10495
|
+
MaxInFlight: normalized.maxInFlight,
|
|
8298
10496
|
NodeType: normalized.nodeType,
|
|
8299
10497
|
LocalDevClusterEnabled: normalized.localDevClusterEnabled,
|
|
8300
10498
|
AgentGroup: normalized.agentGroup,
|
|
8301
10499
|
AgentsCount: normalized.agentsCount,
|
|
10500
|
+
AgentId: normalized.agentId,
|
|
10501
|
+
ExpectedAgentIds: normalized.expectedAgentIds,
|
|
8302
10502
|
TargetScenarios: normalized.targetScenarios,
|
|
8303
10503
|
AgentTargetScenarios: normalized.agentTargetScenarios,
|
|
8304
10504
|
CoordinatorTargetScenarios: normalized.coordinatorTargetScenarios,
|
|
@@ -8317,6 +10517,13 @@ function toRunContext(options) {
|
|
|
8317
10517
|
ReportFolderPath: normalized.reportFolderPath,
|
|
8318
10518
|
ReportFormats: normalized.reportFormats,
|
|
8319
10519
|
ReportingIntervalSeconds: normalized.reportingIntervalSeconds,
|
|
10520
|
+
IterationObservationFlushIntervalSeconds: normalized.iterationObservationFlushIntervalSeconds,
|
|
10521
|
+
MaxIterationObservationBufferBytes: normalized.maxIterationObservationBufferBytes,
|
|
10522
|
+
MaxIterationObservationsPerBatch: normalized.maxIterationObservationsPerBatch,
|
|
10523
|
+
MaxIterationObservationBatchBytes: normalized.maxIterationObservationBatchBytes,
|
|
10524
|
+
IterationObservationSinkQueueDepth: normalized.iterationObservationSinkQueueDepth,
|
|
10525
|
+
IterationObservationSinkParallelism: normalized.iterationObservationSinkParallelism,
|
|
10526
|
+
IterationObservationDrainTimeoutSeconds: normalized.iterationObservationDrainTimeoutSeconds,
|
|
8320
10527
|
MinimumLogLevel: normalized.minimumLogLevel,
|
|
8321
10528
|
LoggerConfig: normalized.loggerConfig,
|
|
8322
10529
|
ReportingSinks: normalized.reportingSinks,
|
|
@@ -8331,7 +10538,9 @@ function toRunContext(options) {
|
|
|
8331
10538
|
CustomSettings: normalized.customSettings,
|
|
8332
10539
|
GlobalCustomSettings: normalized.globalCustomSettings,
|
|
8333
10540
|
AgentExecutionToken: normalized.agentExecutionToken,
|
|
8334
|
-
AgentCommandId: normalized.agentCommandId
|
|
10541
|
+
AgentCommandId: normalized.agentCommandId,
|
|
10542
|
+
ClusterShardIndex: normalized.clusterShardIndex,
|
|
10543
|
+
ClusterShardCount: normalized.clusterShardCount
|
|
8335
10544
|
};
|
|
8336
10545
|
}
|
|
8337
10546
|
class RuntimePolicyCallbackError extends Error {
|
|
@@ -8419,11 +10628,15 @@ function looksLikeRunContext(value) {
|
|
|
8419
10628
|
const keys = new Set(Object.keys(value));
|
|
8420
10629
|
return [
|
|
8421
10630
|
"ConsoleMetricsEnabled",
|
|
10631
|
+
"LoadEngineContractVersion",
|
|
10632
|
+
"MaxInFlight",
|
|
8422
10633
|
"LocalDevClusterEnabled",
|
|
8423
10634
|
"ConfigPath",
|
|
8424
10635
|
"InfraConfigPath",
|
|
8425
10636
|
"AgentGroup",
|
|
8426
10637
|
"AgentsCount",
|
|
10638
|
+
"AgentId",
|
|
10639
|
+
"ExpectedAgentIds",
|
|
8427
10640
|
"AgentTargetScenarios",
|
|
8428
10641
|
"ClusterId",
|
|
8429
10642
|
"CoordinatorTargetScenarios",
|
|
@@ -8438,6 +10651,13 @@ function looksLikeRunContext(value) {
|
|
|
8438
10651
|
"ReportFolderPath",
|
|
8439
10652
|
"ReportFormats",
|
|
8440
10653
|
"ReportingIntervalSeconds",
|
|
10654
|
+
"IterationObservationFlushIntervalSeconds",
|
|
10655
|
+
"MaxIterationObservationBufferBytes",
|
|
10656
|
+
"MaxIterationObservationsPerBatch",
|
|
10657
|
+
"MaxIterationObservationBatchBytes",
|
|
10658
|
+
"IterationObservationSinkQueueDepth",
|
|
10659
|
+
"IterationObservationSinkParallelism",
|
|
10660
|
+
"IterationObservationDrainTimeoutSeconds",
|
|
8441
10661
|
"ReportingSinks",
|
|
8442
10662
|
"SinkRetryCount",
|
|
8443
10663
|
"SinkRetryBackoffMs",
|
|
@@ -8579,6 +10799,18 @@ function resolveSinkSaveRunResult(sink) {
|
|
|
8579
10799
|
? method.bind(sink)
|
|
8580
10800
|
: undefined;
|
|
8581
10801
|
}
|
|
10802
|
+
function resolveSinkSaveIterationBatch(sink) {
|
|
10803
|
+
const method = sink.saveIterationBatch ?? sink.SaveIterationBatch;
|
|
10804
|
+
return typeof method === "function"
|
|
10805
|
+
? method.bind(sink)
|
|
10806
|
+
: undefined;
|
|
10807
|
+
}
|
|
10808
|
+
function resolveSinkCompleteIterationObservationStream(sink) {
|
|
10809
|
+
const method = sink.completeIterationObservationStream ?? sink.CompleteIterationObservationStream;
|
|
10810
|
+
return typeof method === "function"
|
|
10811
|
+
? method.bind(sink)
|
|
10812
|
+
: undefined;
|
|
10813
|
+
}
|
|
8582
10814
|
function resolveSinkStop(sink) {
|
|
8583
10815
|
const method = sink.stop ?? sink.Stop;
|
|
8584
10816
|
return typeof method === "function"
|
|
@@ -8996,13 +11228,17 @@ exports.__loadstrikeTestExports = {
|
|
|
8996
11228
|
ManagedScenarioTrackingRuntime,
|
|
8997
11229
|
ScenarioStatsAccumulator,
|
|
8998
11230
|
StepStatsAccumulator,
|
|
11231
|
+
planRuntimeClusterAssignments,
|
|
8999
11232
|
TrackingFieldSelector: correlation_js_1.TrackingFieldSelector,
|
|
9000
11233
|
addCorrelationRow,
|
|
9001
11234
|
addFailedResponseRow,
|
|
9002
11235
|
aggregateNodeStats,
|
|
11236
|
+
aggregateMeasurementStats,
|
|
9003
11237
|
asRecord,
|
|
9004
11238
|
assertNoDisableLicenseEnforcementOption,
|
|
9005
11239
|
buildEmptyNodeStats,
|
|
11240
|
+
buildRuntimeLoadEngineV2HistogramArtifact,
|
|
11241
|
+
buildRuntimeLoadEngineV2Plan,
|
|
9006
11242
|
buildGroupedCorrelationRows,
|
|
9007
11243
|
buildMeasurementPlaceholder,
|
|
9008
11244
|
buildRichHtmlReport,
|
|
@@ -9075,7 +11311,9 @@ exports.__loadstrikeTestExports = {
|
|
|
9075
11311
|
resolveSinkName,
|
|
9076
11312
|
resolveSinkSaveRealtimeMetrics,
|
|
9077
11313
|
resolveSinkSaveRealtimeStats,
|
|
11314
|
+
resolveSinkSaveIterationBatch,
|
|
9078
11315
|
resolveSinkSaveRunResult,
|
|
11316
|
+
resolveSinkCompleteIterationObservationStream,
|
|
9079
11317
|
resolveSinkStart,
|
|
9080
11318
|
resolveSinkStop,
|
|
9081
11319
|
resolveWorkerPlugins,
|
|
@@ -9096,6 +11334,7 @@ exports.__loadstrikeTestExports = {
|
|
|
9096
11334
|
tryParseNodeTypeToken,
|
|
9097
11335
|
tryReadConfigValue,
|
|
9098
11336
|
validateNamedReportingSinks,
|
|
11337
|
+
validateLoadEngineV2ScenarioFeatures,
|
|
9099
11338
|
validateRegisteredScenarios,
|
|
9100
11339
|
validateRuntimeRedisCorrelationStoreConfiguration,
|
|
9101
11340
|
validateRuntimeTrackingConfiguration,
|