@loadstrike/loadstrike-sdk 1.0.30201 → 1.0.31001
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 +28 -0
- package/dist/cjs/cluster.js +2417 -7
- package/dist/cjs/index.js +13 -2
- package/dist/cjs/iteration-observation-diagnostics.js +513 -0
- package/dist/cjs/iteration-observations.js +884 -0
- package/dist/cjs/load-engine-v2.js +966 -0
- package/dist/cjs/local.js +128 -30
- package/dist/cjs/reporting.js +148 -19
- package/dist/cjs/runtime.js +2514 -196
- package/dist/cjs/sink-retry-policy.js +52 -0
- package/dist/cjs/sinks.js +580 -23
- package/dist/cjs/transports.js +154 -163
- package/dist/esm/cluster.js +2386 -7
- package/dist/esm/index.js +1 -0
- package/dist/esm/iteration-observation-diagnostics.js +508 -0
- package/dist/esm/iteration-observations.js +871 -0
- package/dist/esm/load-engine-v2.js +942 -0
- package/dist/esm/local.js +128 -30
- package/dist/esm/reporting.js +148 -19
- package/dist/esm/runtime.js +2515 -197
- package/dist/esm/sink-retry-policy.js +44 -0
- package/dist/esm/sinks.js +580 -23
- package/dist/esm/transports.js +154 -163
- package/dist/types/cluster.d.ts +379 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/iteration-observation-diagnostics.d.ts +21 -0
- package/dist/types/iteration-observations.d.ts +230 -0
- package/dist/types/load-engine-v2.d.ts +147 -0
- package/dist/types/runtime.d.ts +216 -8
- package/dist/types/sink-retry-policy.d.ts +9 -0
- package/dist/types/sinks.d.ts +73 -0
- package/dist/types/transports.d.ts +6 -8
- package/package.json +3 -4
package/dist/cjs/runtime.js
CHANGED
|
@@ -14,6 +14,12 @@ 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 iteration_observation_diagnostics_js_1 = require("./iteration-observation-diagnostics.js");
|
|
20
|
+
const sink_retry_policy_js_1 = require("./sink-retry-policy.js");
|
|
21
|
+
const LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS = Symbol("loadstrike.load-engine-v2.scheduler-distributions");
|
|
22
|
+
const PORTAL_RUN_TOKEN_PROVIDER = Symbol.for("loadstrike.internal.portal-run-token-provider");
|
|
17
23
|
exports.LoadStrikeNodeType = {
|
|
18
24
|
SingleNode: "SingleNode",
|
|
19
25
|
Coordinator: "Coordinator",
|
|
@@ -147,12 +153,20 @@ class LoadStrikePluginData {
|
|
|
147
153
|
}
|
|
148
154
|
exports.LoadStrikePluginData = LoadStrikePluginData;
|
|
149
155
|
class MeasurementAccumulator {
|
|
150
|
-
constructor() {
|
|
156
|
+
constructor(useHistogram = false) {
|
|
157
|
+
this.useHistogram = useHistogram;
|
|
151
158
|
this.count = 0;
|
|
152
159
|
this.allBytes = 0;
|
|
153
160
|
this.latenciesMs = [];
|
|
154
161
|
this.sizesBytes = [];
|
|
162
|
+
this.latencyLessOrEq800 = 0;
|
|
163
|
+
this.latencyMore800Less1200 = 0;
|
|
164
|
+
this.latencyMoreOrEq1200 = 0;
|
|
155
165
|
this.statusCodes = new Map();
|
|
166
|
+
if (useHistogram) {
|
|
167
|
+
this.latencyHistogram = new load_engine_v2_js_1.LoadStrikeHistogramV1();
|
|
168
|
+
this.sizeHistogram = new load_engine_v2_js_1.LoadStrikeHistogramV1();
|
|
169
|
+
}
|
|
156
170
|
}
|
|
157
171
|
get Count() {
|
|
158
172
|
return this.count;
|
|
@@ -169,8 +183,20 @@ class MeasurementAccumulator {
|
|
|
169
183
|
const key = `${statusCode}|${message}|${reply.isSuccess ? "ok" : "fail"}`;
|
|
170
184
|
this.count += 1;
|
|
171
185
|
this.allBytes += sizeBytes;
|
|
172
|
-
this.
|
|
173
|
-
|
|
186
|
+
if (this.useHistogram) {
|
|
187
|
+
this.latencyHistogram.record(normalizeLatencyMicroseconds(latencyMs));
|
|
188
|
+
this.sizeHistogram.record(normalizeHistogramInteger(sizeBytes, "Response size"));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
this.latenciesMs.push(latencyMs);
|
|
192
|
+
this.sizesBytes.push(sizeBytes);
|
|
193
|
+
}
|
|
194
|
+
if (latencyMs <= 800)
|
|
195
|
+
this.latencyLessOrEq800 += 1;
|
|
196
|
+
else if (latencyMs < 1200)
|
|
197
|
+
this.latencyMore800Less1200 += 1;
|
|
198
|
+
else
|
|
199
|
+
this.latencyMoreOrEq1200 += 1;
|
|
174
200
|
const existing = this.statusCodes.get(key);
|
|
175
201
|
if (existing) {
|
|
176
202
|
existing.count += 1;
|
|
@@ -188,6 +214,9 @@ class MeasurementAccumulator {
|
|
|
188
214
|
* Use this when all builder inputs are ready to be materialized.
|
|
189
215
|
*/
|
|
190
216
|
build(allRequestCount, durationMs) {
|
|
217
|
+
if (this.useHistogram) {
|
|
218
|
+
return buildHistogramMeasurement(this.histogramSnapshot(), allRequestCount, durationMs);
|
|
219
|
+
}
|
|
191
220
|
const count = this.count;
|
|
192
221
|
const totalDurationMs = Math.max(durationMs, 0);
|
|
193
222
|
const latencyValues = [...this.latenciesMs];
|
|
@@ -236,14 +265,274 @@ class MeasurementAccumulator {
|
|
|
236
265
|
statusCodes
|
|
237
266
|
};
|
|
238
267
|
}
|
|
268
|
+
buildCombined(other, allRequestCount, durationMs) {
|
|
269
|
+
if (!this.useHistogram || !other.useHistogram) {
|
|
270
|
+
throw new Error("Combined measurements require Load Engine V2 histograms.");
|
|
271
|
+
}
|
|
272
|
+
const left = this.histogramSnapshot();
|
|
273
|
+
const right = other.histogramSnapshot();
|
|
274
|
+
left.latency.merge(right.latency);
|
|
275
|
+
left.size.merge(right.size);
|
|
276
|
+
for (const [key, value] of right.statusCodes) {
|
|
277
|
+
const existing = left.statusCodes.get(key);
|
|
278
|
+
if (existing)
|
|
279
|
+
existing.count += value.count;
|
|
280
|
+
else
|
|
281
|
+
left.statusCodes.set(key, { ...value });
|
|
282
|
+
}
|
|
283
|
+
return buildHistogramMeasurement({
|
|
284
|
+
count: left.count + right.count,
|
|
285
|
+
allBytes: left.allBytes + right.allBytes,
|
|
286
|
+
latency: left.latency,
|
|
287
|
+
size: left.size,
|
|
288
|
+
statusCodes: left.statusCodes,
|
|
289
|
+
lessOrEq800: left.lessOrEq800 + right.lessOrEq800,
|
|
290
|
+
more800Less1200: left.more800Less1200 + right.more800Less1200,
|
|
291
|
+
moreOrEq1200: left.moreOrEq1200 + right.moreOrEq1200
|
|
292
|
+
}, allRequestCount, durationMs);
|
|
293
|
+
}
|
|
294
|
+
histogramSnapshot() {
|
|
295
|
+
return {
|
|
296
|
+
count: this.count,
|
|
297
|
+
allBytes: this.allBytes,
|
|
298
|
+
latency: this.latencyHistogram.clone(),
|
|
299
|
+
size: this.sizeHistogram.clone(),
|
|
300
|
+
statusCodes: new Map(Array.from(this.statusCodes, ([key, value]) => [key, { ...value }])),
|
|
301
|
+
lessOrEq800: this.latencyLessOrEq800,
|
|
302
|
+
more800Less1200: this.latencyMore800Less1200,
|
|
303
|
+
moreOrEq1200: this.latencyMoreOrEq1200
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function buildHistogramMeasurement(snapshot, allRequestCount, durationMs) {
|
|
308
|
+
const count = snapshot.count;
|
|
309
|
+
const totalDurationMs = Math.max(durationMs, 0);
|
|
310
|
+
const latency = snapshot.latency;
|
|
311
|
+
const size = snapshot.size;
|
|
312
|
+
return {
|
|
313
|
+
count64: latency.count.toString(),
|
|
314
|
+
distributionMode: latency.mode === "quantized-v1" || size.mode === "quantized-v1"
|
|
315
|
+
? "quantized-v1"
|
|
316
|
+
: "exact-normalized",
|
|
317
|
+
maxRelativeError: Math.max(latency.maxRelativeError, size.maxRelativeError),
|
|
318
|
+
histogramSidecar: {
|
|
319
|
+
latency: latency.toSidecar(),
|
|
320
|
+
size: size.toSidecar(),
|
|
321
|
+
allBytes64: size.exactTotal.toString(),
|
|
322
|
+
lessOrEq80064: snapshot.lessOrEq800.toString(),
|
|
323
|
+
more800Less120064: snapshot.more800Less1200.toString(),
|
|
324
|
+
moreOrEq120064: snapshot.moreOrEq1200.toString()
|
|
325
|
+
},
|
|
326
|
+
request: {
|
|
327
|
+
count,
|
|
328
|
+
percent: allRequestCount <= 0 ? 0 : Math.round((100 * count) / allRequestCount),
|
|
329
|
+
rps: totalDurationMs <= 0 ? 0 : count / (totalDurationMs / 1000)
|
|
330
|
+
},
|
|
331
|
+
dataTransfer: {
|
|
332
|
+
allBytes: snapshot.allBytes,
|
|
333
|
+
allBytes64: size.exactTotal.toString(),
|
|
334
|
+
minBytes: Number(size.minimum),
|
|
335
|
+
maxBytes: Number(size.maximum),
|
|
336
|
+
meanBytes: Math.round(size.mean),
|
|
337
|
+
percent50: Number(size.percentile(0.5)),
|
|
338
|
+
percent75: Number(size.percentile(0.75)),
|
|
339
|
+
percent95: Number(size.percentile(0.95)),
|
|
340
|
+
percent99: Number(size.percentile(0.99)),
|
|
341
|
+
percent100: Number(size.percentile(1)),
|
|
342
|
+
stdDev: size.populationStandardDeviation
|
|
343
|
+
},
|
|
344
|
+
latency: {
|
|
345
|
+
latencyCount: {
|
|
346
|
+
lessOrEq800: snapshot.lessOrEq800,
|
|
347
|
+
more800Less1200: snapshot.more800Less1200,
|
|
348
|
+
moreOrEq1200: snapshot.moreOrEq1200
|
|
349
|
+
},
|
|
350
|
+
minMs: Number(latency.minimum) / 1000,
|
|
351
|
+
maxMs: Number(latency.maximum) / 1000,
|
|
352
|
+
meanMs: latency.mean / 1000,
|
|
353
|
+
percent50: Number(latency.percentile(0.5)) / 1000,
|
|
354
|
+
percent75: Number(latency.percentile(0.75)) / 1000,
|
|
355
|
+
percent95: Number(latency.percentile(0.95)) / 1000,
|
|
356
|
+
percent99: Number(latency.percentile(0.99)) / 1000,
|
|
357
|
+
percent100: Number(latency.percentile(1)) / 1000,
|
|
358
|
+
stdDev: latency.populationStandardDeviation / 1000
|
|
359
|
+
},
|
|
360
|
+
statusCodes: Array.from(snapshot.statusCodes.values())
|
|
361
|
+
.sort((left, right) => right.count - left.count)
|
|
362
|
+
.map((value) => ({
|
|
363
|
+
count: value.count,
|
|
364
|
+
isError: value.isError,
|
|
365
|
+
message: value.message,
|
|
366
|
+
percent: count <= 0 ? 0 : Math.round((100 * value.count) / count),
|
|
367
|
+
statusCode: value.statusCode
|
|
368
|
+
}))
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function normalizeLatencyMicroseconds(latencyMs) {
|
|
372
|
+
return normalizeHistogramInteger(Math.max(latencyMs, 0) * 1000, "Latency");
|
|
373
|
+
}
|
|
374
|
+
function normalizeRawObservationLatencyMicroseconds(latencyMs) {
|
|
375
|
+
const maximum = 9223372036854775807n;
|
|
376
|
+
if (!Number.isFinite(latencyMs) || latencyMs <= 0) {
|
|
377
|
+
return 0n;
|
|
378
|
+
}
|
|
379
|
+
const microseconds = latencyMs * 1000;
|
|
380
|
+
if (!Number.isFinite(microseconds) || microseconds >= Number(maximum)) {
|
|
381
|
+
return maximum;
|
|
382
|
+
}
|
|
383
|
+
return BigInt(Math.max(0, Math.round(microseconds)));
|
|
384
|
+
}
|
|
385
|
+
function normalizeHistogramInteger(value, name) {
|
|
386
|
+
if (!Number.isFinite(value) || value > Number(9223372036854775807n)) {
|
|
387
|
+
throw new RangeError(`${name} is outside the supported histogram range.`);
|
|
388
|
+
}
|
|
389
|
+
return BigInt(Math.max(0, Math.round(value)));
|
|
390
|
+
}
|
|
391
|
+
class LoadEngineV2Telemetry {
|
|
392
|
+
constructor(budget) {
|
|
393
|
+
this.budget = budget;
|
|
394
|
+
this.mutableSegments = [];
|
|
395
|
+
this.warnings = new Map();
|
|
396
|
+
}
|
|
397
|
+
createSegment(scenarioName, scenarioIndex, simulationIndex, kind, shardIndex, shardCount) {
|
|
398
|
+
const segment = {
|
|
399
|
+
scenarioName,
|
|
400
|
+
scenarioIndex,
|
|
401
|
+
simulationIndex,
|
|
402
|
+
kind,
|
|
403
|
+
shardIndex,
|
|
404
|
+
shardCount,
|
|
405
|
+
planned: 0n,
|
|
406
|
+
due: 0n,
|
|
407
|
+
started: 0n,
|
|
408
|
+
completed: 0n,
|
|
409
|
+
dropped: 0n,
|
|
410
|
+
unreached: 0n,
|
|
411
|
+
requestedWorkers: 0n,
|
|
412
|
+
startedWorkers: 0n,
|
|
413
|
+
unavailableWorkers: 0n,
|
|
414
|
+
dropReasons: new Map(),
|
|
415
|
+
unavailableWorkerReasons: new Map(),
|
|
416
|
+
decisionLag: new load_engine_v2_js_1.LoadStrikeHistogramV1(),
|
|
417
|
+
startLag: new load_engine_v2_js_1.LoadStrikeHistogramV1(),
|
|
418
|
+
accountingComplete: false
|
|
419
|
+
};
|
|
420
|
+
this.mutableSegments.push(segment);
|
|
421
|
+
return segment;
|
|
422
|
+
}
|
|
423
|
+
recordWarning(code, segment, count) {
|
|
424
|
+
if (count <= 0n)
|
|
425
|
+
return;
|
|
426
|
+
const key = `${code}\n${segment.scenarioIndex}\n${segment.simulationIndex}`;
|
|
427
|
+
const nowNs = BigInt(Date.now()) * 1000000n;
|
|
428
|
+
const existing = this.warnings.get(key);
|
|
429
|
+
if (existing) {
|
|
430
|
+
existing.count += count;
|
|
431
|
+
existing.lastObservedUtcNs = nowNs;
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
this.warnings.set(key, {
|
|
435
|
+
code,
|
|
436
|
+
scenarioName: segment.scenarioName,
|
|
437
|
+
scenarioIndex: segment.scenarioIndex,
|
|
438
|
+
simulationIndex: segment.simulationIndex,
|
|
439
|
+
simulationKind: segment.kind,
|
|
440
|
+
count,
|
|
441
|
+
firstObservedUtcNs: nowNs,
|
|
442
|
+
lastObservedUtcNs: nowNs
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
recordDecisionLag(segment, lagNs) {
|
|
447
|
+
segment.decisionLag.record(maxBigInt(lagNs, 0n) / 1000n);
|
|
448
|
+
}
|
|
449
|
+
recordStartLag(segment, lagNs) {
|
|
450
|
+
segment.startLag.record(maxBigInt(lagNs, 0n) / 1000n);
|
|
451
|
+
}
|
|
452
|
+
buildSchedulerDistributions() {
|
|
453
|
+
return this.mutableSegments.flatMap((segment) => {
|
|
454
|
+
const scenarioIndex64 = segment.scenarioIndex.toString();
|
|
455
|
+
const simulationIndex64 = segment.simulationIndex.toString();
|
|
456
|
+
return [
|
|
457
|
+
["scheduler-decision-lag", "decision", segment.decisionLag],
|
|
458
|
+
["scheduler-start-lag", "start", segment.startLag]
|
|
459
|
+
].map(([seriesKind, identityKind, histogram]) => ({
|
|
460
|
+
seriesKind,
|
|
461
|
+
scenarioIndex64,
|
|
462
|
+
scenarioName: segment.scenarioName,
|
|
463
|
+
identityKeyHex: (0, cluster_js_1.buildLoadEngineV2SchedulerIdentityKey)(identityKind, scenarioIndex64, simulationIndex64).toString("hex"),
|
|
464
|
+
outcome: "none",
|
|
465
|
+
unit: "microseconds",
|
|
466
|
+
histogram: histogram.toSidecar(),
|
|
467
|
+
exactTotalDecimalOrEmpty: histogram.toSidecar().exactTotal64
|
|
468
|
+
}));
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
buildWarnings() {
|
|
472
|
+
return Array.from(this.warnings.values())
|
|
473
|
+
.sort((left, right) => left.code.localeCompare(right.code)
|
|
474
|
+
|| left.scenarioName.localeCompare(right.scenarioName)
|
|
475
|
+
|| left.simulationIndex - right.simulationIndex)
|
|
476
|
+
.map((value) => ({
|
|
477
|
+
code: value.code,
|
|
478
|
+
scenarioName: value.scenarioName,
|
|
479
|
+
scenarioIndex: value.scenarioIndex,
|
|
480
|
+
simulationIndex: value.simulationIndex,
|
|
481
|
+
simulationKind: value.simulationKind,
|
|
482
|
+
count64: value.count.toString(),
|
|
483
|
+
message: value.code,
|
|
484
|
+
firstObservedUtcNs: value.firstObservedUtcNs.toString(),
|
|
485
|
+
lastObservedUtcNs: value.lastObservedUtcNs.toString()
|
|
486
|
+
}));
|
|
487
|
+
}
|
|
488
|
+
buildSegments() {
|
|
489
|
+
return this.mutableSegments.map((segment) => ({
|
|
490
|
+
scenarioName: segment.scenarioName,
|
|
491
|
+
scenarioIndex: segment.scenarioIndex,
|
|
492
|
+
simulationIndex: segment.simulationIndex,
|
|
493
|
+
kind: segment.kind,
|
|
494
|
+
shardIndex: segment.shardIndex,
|
|
495
|
+
shardCount: segment.shardCount,
|
|
496
|
+
plannedIterations64: segment.planned.toString(),
|
|
497
|
+
dueIterations64: segment.due.toString(),
|
|
498
|
+
startedIterations64: segment.started.toString(),
|
|
499
|
+
completedIterations64: segment.completed.toString(),
|
|
500
|
+
droppedIterations64: segment.dropped.toString(),
|
|
501
|
+
unreachedIterations64: segment.unreached.toString(),
|
|
502
|
+
requestedWorkerSlots64: segment.requestedWorkers.toString(),
|
|
503
|
+
startedWorkerSlots64: segment.startedWorkers.toString(),
|
|
504
|
+
unavailableWorkerSlots64: segment.unavailableWorkers.toString(),
|
|
505
|
+
dropReasons: Object.fromEntries(Array.from(segment.dropReasons, ([key, count]) => [key, count.toString()])),
|
|
506
|
+
unavailableWorkerReasons: Object.fromEntries(Array.from(segment.unavailableWorkerReasons, ([key, count]) => [key, count.toString()])),
|
|
507
|
+
deliveryPercent: segment.due === 0n ? 100 : Number(segment.started * 10000n / segment.due) / 100,
|
|
508
|
+
accountingComplete: segment.accountingComplete
|
|
509
|
+
}));
|
|
510
|
+
}
|
|
511
|
+
buildStats() {
|
|
512
|
+
return {
|
|
513
|
+
configuredMaxInFlight: this.budget.maxInFlight,
|
|
514
|
+
maxInFlightObserved: this.budget.highWater,
|
|
515
|
+
currentInFlight: this.budget.current,
|
|
516
|
+
segments: this.buildSegments()
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function incrementReason(reasons, code, count = 1n) {
|
|
521
|
+
reasons.set(code, (reasons.get(code) ?? 0n) + count);
|
|
522
|
+
}
|
|
523
|
+
function ownedV2OrdinalCount(total, shardIndex, shardCount) {
|
|
524
|
+
if (total <= BigInt(shardIndex))
|
|
525
|
+
return 0n;
|
|
526
|
+
return (total - 1n - BigInt(shardIndex)) / BigInt(shardCount) + 1n;
|
|
239
527
|
}
|
|
240
528
|
class StepStatsAccumulator {
|
|
241
|
-
constructor(scenarioName, stepName, sortIndex) {
|
|
529
|
+
constructor(scenarioName, stepName, sortIndex, useHistogram = false) {
|
|
242
530
|
this.scenarioName = scenarioName;
|
|
243
531
|
this.stepName = stepName;
|
|
244
532
|
this.sortIndex = sortIndex;
|
|
245
|
-
this.
|
|
246
|
-
this.
|
|
533
|
+
this.useHistogram = useHistogram;
|
|
534
|
+
this.ok = new MeasurementAccumulator(useHistogram);
|
|
535
|
+
this.fail = new MeasurementAccumulator(useHistogram);
|
|
247
536
|
}
|
|
248
537
|
/**
|
|
249
538
|
* Exposes the public record operation.
|
|
@@ -282,6 +571,7 @@ class StepStatsAccumulator {
|
|
|
282
571
|
minLatencyMs: minCandidates.length ? Math.min(...minCandidates) : 0,
|
|
283
572
|
maxLatencyMs: maxCandidates.length ? Math.max(...maxCandidates) : 0,
|
|
284
573
|
statusCodes,
|
|
574
|
+
allMeasurement: this.useHistogram ? this.ok.buildCombined(this.fail, requestCount, durationMs) : undefined,
|
|
285
575
|
ok,
|
|
286
576
|
fail,
|
|
287
577
|
sortIndex: this.sortIndex
|
|
@@ -289,15 +579,16 @@ class StepStatsAccumulator {
|
|
|
289
579
|
}
|
|
290
580
|
}
|
|
291
581
|
class ScenarioStatsAccumulator {
|
|
292
|
-
constructor(scenarioName, sortIndex) {
|
|
582
|
+
constructor(scenarioName, sortIndex, useHistogram = false) {
|
|
293
583
|
this.scenarioName = scenarioName;
|
|
294
584
|
this.sortIndex = sortIndex;
|
|
295
|
-
this.
|
|
296
|
-
this.fail = new MeasurementAccumulator();
|
|
585
|
+
this.useHistogram = useHistogram;
|
|
297
586
|
this.steps = new Map();
|
|
298
587
|
this.nextStepSortIndex = 0;
|
|
299
588
|
this.loadSimulationStats = { simulationName: "", value: 0 };
|
|
300
589
|
this.currentOperation = "None";
|
|
590
|
+
this.ok = new MeasurementAccumulator(useHistogram);
|
|
591
|
+
this.fail = new MeasurementAccumulator(useHistogram);
|
|
301
592
|
}
|
|
302
593
|
/**
|
|
303
594
|
* Exposes the public setLoadSimulation operation.
|
|
@@ -338,13 +629,18 @@ class ScenarioStatsAccumulator {
|
|
|
338
629
|
* Exposes the public recordStep operation.
|
|
339
630
|
* Use this when the surrounding wrapper type makes this operation the clearest way to express your intent.
|
|
340
631
|
*/
|
|
341
|
-
recordStep(stepName, reply, observedLatencyMs) {
|
|
632
|
+
recordStep(stepName, reply, observedLatencyMs, sortIndex) {
|
|
342
633
|
const existing = this.steps.get(stepName);
|
|
343
|
-
const
|
|
634
|
+
const resolvedSortIndex = sortIndex === undefined
|
|
635
|
+
? this.nextStepSortIndex + 1
|
|
636
|
+
: Math.max(Math.trunc(sortIndex), 0);
|
|
637
|
+
const step = existing ?? new StepStatsAccumulator(this.scenarioName, stepName, resolvedSortIndex, this.useHistogram);
|
|
344
638
|
if (!existing) {
|
|
639
|
+
this.nextStepSortIndex = Math.max(this.nextStepSortIndex, resolvedSortIndex);
|
|
345
640
|
this.steps.set(stepName, step);
|
|
346
641
|
}
|
|
347
642
|
step.record(reply, observedLatencyMs);
|
|
643
|
+
return step.sortIndex;
|
|
348
644
|
}
|
|
349
645
|
/**
|
|
350
646
|
* Builds the configured payload or helper object.
|
|
@@ -373,6 +669,7 @@ class ScenarioStatsAccumulator {
|
|
|
373
669
|
minLatencyMs: minCandidates.length ? Math.min(...minCandidates) : 0,
|
|
374
670
|
maxLatencyMs: maxCandidates.length ? Math.max(...maxCandidates) : 0,
|
|
375
671
|
statusCodes,
|
|
672
|
+
allMeasurement: this.useHistogram ? this.ok.buildCombined(this.fail, totalRequests, durationMs) : undefined,
|
|
376
673
|
allBytes,
|
|
377
674
|
currentOperation: this.currentOperation,
|
|
378
675
|
durationMs: Math.max(durationMs, 0),
|
|
@@ -468,7 +765,8 @@ class LoadStrikeStep {
|
|
|
468
765
|
const internal = context;
|
|
469
766
|
await internal.invokeBeforeStep(stepName);
|
|
470
767
|
let reply;
|
|
471
|
-
const
|
|
768
|
+
const startedUtcNs = (0, iteration_observations_js_1.utcNowNs)();
|
|
769
|
+
const startedAtNs = process.hrtime.bigint();
|
|
472
770
|
try {
|
|
473
771
|
reply = normalizeReply(await run());
|
|
474
772
|
}
|
|
@@ -476,8 +774,21 @@ class LoadStrikeStep {
|
|
|
476
774
|
reply = LoadStrikeResponse.fail("step_exception", resolveRuntimeErrorMessage(error, "step failed"), 0);
|
|
477
775
|
}
|
|
478
776
|
reply = attachReplyProjection(reply);
|
|
479
|
-
const
|
|
480
|
-
|
|
777
|
+
const observedLatencyNs = maxBigInt(process.hrtime.bigint() - startedAtNs, 0n);
|
|
778
|
+
const completedUtcNs = startedUtcNs + observedLatencyNs;
|
|
779
|
+
const observedLatencyMs = Number(observedLatencyNs) / 1000000;
|
|
780
|
+
const recordedSortIndex = internal.recordStep(stepName, reply, observedLatencyMs);
|
|
781
|
+
internal.recordStepObservation?.((0, iteration_observations_js_1.createIterationStepObservation)({
|
|
782
|
+
stepName,
|
|
783
|
+
sortIndex: typeof recordedSortIndex === "number" ? recordedSortIndex : 0,
|
|
784
|
+
startedUtcNs,
|
|
785
|
+
completedUtcNs,
|
|
786
|
+
observedLatencyUs64: observedLatencyNs / 1000n,
|
|
787
|
+
reportedLatencyUs64: normalizeRawObservationLatencyMicroseconds(resolveRecordedLatency(reply.customLatencyMs, observedLatencyMs)),
|
|
788
|
+
isSuccess: reply.isSuccess,
|
|
789
|
+
statusCode: normalizeStatusCode(reply.statusCode, reply.isSuccess),
|
|
790
|
+
sizeBytes64: normalizeHistogramInteger(Math.max(toNumber(reply.sizeBytes), 0), "Step response bytes")
|
|
791
|
+
}));
|
|
481
792
|
await internal.invokeAfterStep(stepName, reply);
|
|
482
793
|
return reply;
|
|
483
794
|
}
|
|
@@ -1016,10 +1327,14 @@ class LoadStrikeContext {
|
|
|
1016
1327
|
const normalizedValues = normalizeRunContextCollectionShapes(this.values);
|
|
1017
1328
|
return {
|
|
1018
1329
|
displayConsoleMetrics: normalizedValues.ConsoleMetricsEnabled,
|
|
1330
|
+
loadEngineContractVersion: normalizedValues.LoadEngineContractVersion,
|
|
1331
|
+
maxInFlight: normalizedValues.MaxInFlight,
|
|
1019
1332
|
nodeType: normalizedValues.NodeType,
|
|
1020
1333
|
localDevClusterEnabled: normalizedValues.LocalDevClusterEnabled,
|
|
1021
1334
|
agentGroup: normalizedValues.AgentGroup,
|
|
1022
1335
|
agentsCount: normalizedValues.AgentsCount,
|
|
1336
|
+
agentId: normalizedValues.AgentId,
|
|
1337
|
+
expectedAgentIds: normalizedValues.ExpectedAgentIds,
|
|
1023
1338
|
targetScenarios: normalizedValues.TargetScenarios,
|
|
1024
1339
|
agentTargetScenarios: normalizedValues.AgentTargetScenarios,
|
|
1025
1340
|
coordinatorTargetScenarios: normalizedValues.CoordinatorTargetScenarios,
|
|
@@ -1038,6 +1353,13 @@ class LoadStrikeContext {
|
|
|
1038
1353
|
reportFolderPath: normalizedValues.ReportFolderPath,
|
|
1039
1354
|
reportFormats: normalizedValues.ReportFormats,
|
|
1040
1355
|
reportingIntervalSeconds: normalizedValues.ReportingIntervalSeconds,
|
|
1356
|
+
iterationObservationFlushIntervalSeconds: normalizedValues.IterationObservationFlushIntervalSeconds,
|
|
1357
|
+
maxIterationObservationBufferBytes: normalizedValues.MaxIterationObservationBufferBytes,
|
|
1358
|
+
maxIterationObservationsPerBatch: normalizedValues.MaxIterationObservationsPerBatch,
|
|
1359
|
+
maxIterationObservationBatchBytes: normalizedValues.MaxIterationObservationBatchBytes,
|
|
1360
|
+
iterationObservationSinkQueueDepth: normalizedValues.IterationObservationSinkQueueDepth,
|
|
1361
|
+
iterationObservationSinkParallelism: normalizedValues.IterationObservationSinkParallelism,
|
|
1362
|
+
iterationObservationDrainTimeoutSeconds: normalizedValues.IterationObservationDrainTimeoutSeconds,
|
|
1041
1363
|
minimumLogLevel: normalizedValues.MinimumLogLevel,
|
|
1042
1364
|
loggerConfig: normalizedValues.LoggerConfig,
|
|
1043
1365
|
reportingSinks: normalizedValues.ReportingSinks,
|
|
@@ -1051,6 +1373,8 @@ class LoadStrikeContext {
|
|
|
1051
1373
|
workerPlugins: normalizedValues.WorkerPlugins,
|
|
1052
1374
|
customSettings: normalizedValues.CustomSettings,
|
|
1053
1375
|
globalCustomSettings: normalizedValues.GlobalCustomSettings,
|
|
1376
|
+
clusterShardIndex: normalizedValues.ClusterShardIndex,
|
|
1377
|
+
clusterShardCount: normalizedValues.ClusterShardCount,
|
|
1054
1378
|
runArgs: this.runArgs.length ? [...this.runArgs] : undefined
|
|
1055
1379
|
};
|
|
1056
1380
|
}
|
|
@@ -1112,6 +1436,19 @@ class LoadStrikeContext {
|
|
|
1112
1436
|
DisplayConsoleMetrics(enable) {
|
|
1113
1437
|
return this.mergeValues({ ConsoleMetricsEnabled: Boolean(enable) });
|
|
1114
1438
|
}
|
|
1439
|
+
useLoadEngineV2() {
|
|
1440
|
+
return this.UseLoadEngineV2();
|
|
1441
|
+
}
|
|
1442
|
+
UseLoadEngineV2() {
|
|
1443
|
+
return this.mergeValues({ LoadEngineContractVersion: 2 });
|
|
1444
|
+
}
|
|
1445
|
+
withMaxInFlight(maxInFlight) {
|
|
1446
|
+
return this.WithMaxInFlight(maxInFlight);
|
|
1447
|
+
}
|
|
1448
|
+
WithMaxInFlight(maxInFlight) {
|
|
1449
|
+
validateV2MaxInFlight(this.values.LoadEngineContractVersion, maxInFlight);
|
|
1450
|
+
return this.mergeValues({ MaxInFlight: maxInFlight });
|
|
1451
|
+
}
|
|
1115
1452
|
/**
|
|
1116
1453
|
* Toggles local development cluster mode.
|
|
1117
1454
|
* Use this when you want to simulate coordinator and agent behavior on a single machine.
|
|
@@ -1178,6 +1515,26 @@ class LoadStrikeContext {
|
|
|
1178
1515
|
WithAgentGroup(agentGroup) {
|
|
1179
1516
|
return this.mergeValues({ AgentGroup: requireNonEmpty(agentGroup, "Agent group must be provided.") });
|
|
1180
1517
|
}
|
|
1518
|
+
/** Sets the stable identity required by a remote Load Engine V2 agent. */
|
|
1519
|
+
withAgentId(agentId) {
|
|
1520
|
+
return this.WithAgentId(agentId);
|
|
1521
|
+
}
|
|
1522
|
+
/** Sets the stable identity required by a remote Load Engine V2 agent. */
|
|
1523
|
+
WithAgentId(agentId) {
|
|
1524
|
+
return this.mergeValues({ AgentId: requireNonEmpty(agentId, "Agent id must be provided.") });
|
|
1525
|
+
}
|
|
1526
|
+
/** Sets the exact remote agent identities required by a Load Engine V2 coordinator. */
|
|
1527
|
+
withExpectedAgentIds(...agentIds) {
|
|
1528
|
+
return this.WithExpectedAgentIds(...agentIds);
|
|
1529
|
+
}
|
|
1530
|
+
/** Sets the exact remote agent identities required by a Load Engine V2 coordinator. */
|
|
1531
|
+
WithExpectedAgentIds(...agentIds) {
|
|
1532
|
+
const normalized = validateScenarioNames(agentIds);
|
|
1533
|
+
if (new Set(normalized).size !== normalized.length) {
|
|
1534
|
+
throw new Error("Expected agent ids must be unique.");
|
|
1535
|
+
}
|
|
1536
|
+
return this.mergeValues({ ExpectedAgentIds: normalized });
|
|
1537
|
+
}
|
|
1181
1538
|
/**
|
|
1182
1539
|
* Sets the requested agent count.
|
|
1183
1540
|
* Use this when a coordinator should fan work out across a specific number of agents.
|
|
@@ -2022,7 +2379,7 @@ function firstWebVitalViolation(...values) {
|
|
|
2022
2379
|
return "";
|
|
2023
2380
|
}
|
|
2024
2381
|
class LoadStrikeScenario {
|
|
2025
|
-
constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail, internalLicenseFeatures = []) {
|
|
2382
|
+
constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail, internalLicenseFeatures = [], declaredStepNames = []) {
|
|
2026
2383
|
this.name = name;
|
|
2027
2384
|
this.runHandler = runHandler;
|
|
2028
2385
|
this.initHandler = initHandler;
|
|
@@ -2036,6 +2393,7 @@ class LoadStrikeScenario {
|
|
|
2036
2393
|
this.weight = weight;
|
|
2037
2394
|
this.restartIterationOnFail = restartIterationOnFail;
|
|
2038
2395
|
this.internalLicenseFeatures = normalizeStringArray(internalLicenseFeatures);
|
|
2396
|
+
this.declaredStepNames = normalizeDeclaredStepNames(declaredStepNames);
|
|
2039
2397
|
}
|
|
2040
2398
|
static create(name, runHandler) {
|
|
2041
2399
|
const scenarioName = requireNonEmpty(name, "Scenario name must be provided.");
|
|
@@ -2078,7 +2436,7 @@ class LoadStrikeScenario {
|
|
|
2078
2436
|
if (typeof handler !== "function") {
|
|
2079
2437
|
throw new TypeError("Init handler must be provided.");
|
|
2080
2438
|
}
|
|
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);
|
|
2439
|
+
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
2440
|
}
|
|
2083
2441
|
/**
|
|
2084
2442
|
* Configures init async for this SDK object.
|
|
@@ -2095,7 +2453,7 @@ class LoadStrikeScenario {
|
|
|
2095
2453
|
if (typeof handler !== "function") {
|
|
2096
2454
|
throw new TypeError("Clean handler must be provided.");
|
|
2097
2455
|
}
|
|
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);
|
|
2456
|
+
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
2457
|
}
|
|
2100
2458
|
/**
|
|
2101
2459
|
* Configures clean async for this SDK object.
|
|
@@ -2112,14 +2470,14 @@ class LoadStrikeScenario {
|
|
|
2112
2470
|
if (!Number.isFinite(maxFailCount)) {
|
|
2113
2471
|
throw new RangeError("maxFailCount should be a finite number.");
|
|
2114
2472
|
}
|
|
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);
|
|
2473
|
+
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
2474
|
}
|
|
2117
2475
|
/**
|
|
2118
2476
|
* Configures out warm up for this SDK object.
|
|
2119
2477
|
* Use this when out warm up should be set explicitly before the run starts.
|
|
2120
2478
|
*/
|
|
2121
2479
|
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);
|
|
2480
|
+
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
2481
|
}
|
|
2124
2482
|
/**
|
|
2125
2483
|
* Configures warm up duration for this SDK object.
|
|
@@ -2129,7 +2487,7 @@ class LoadStrikeScenario {
|
|
|
2129
2487
|
if (!Number.isFinite(durationSeconds)) {
|
|
2130
2488
|
throw new RangeError("Warmup duration should be a finite number.");
|
|
2131
2489
|
}
|
|
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);
|
|
2490
|
+
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
2491
|
}
|
|
2134
2492
|
/**
|
|
2135
2493
|
* Configures weight for this SDK object.
|
|
@@ -2139,14 +2497,14 @@ class LoadStrikeScenario {
|
|
|
2139
2497
|
if (!Number.isFinite(weight)) {
|
|
2140
2498
|
throw new RangeError("Weight should be a finite number.");
|
|
2141
2499
|
}
|
|
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);
|
|
2500
|
+
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
2501
|
}
|
|
2144
2502
|
/**
|
|
2145
2503
|
* Configures restart iteration on fail for this SDK object.
|
|
2146
2504
|
* Use this when restart iteration on fail should be set explicitly before the run starts.
|
|
2147
2505
|
*/
|
|
2148
2506
|
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);
|
|
2507
|
+
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
2508
|
}
|
|
2151
2509
|
/**
|
|
2152
2510
|
* Configures cross platform tracking for this SDK object.
|
|
@@ -2171,7 +2529,7 @@ class LoadStrikeScenario {
|
|
|
2171
2529
|
if (isCorrelateExistingTrafficTracking(copied) && this.loadSimulations.length > 0) {
|
|
2172
2530
|
throw new Error("CorrelateExistingTraffic uses ForDuration and cannot be combined with WithLoadSimulations.");
|
|
2173
2531
|
}
|
|
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);
|
|
2532
|
+
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
2533
|
}
|
|
2176
2534
|
/**
|
|
2177
2535
|
* Configures load simulations for this SDK object.
|
|
@@ -2184,7 +2542,7 @@ class LoadStrikeScenario {
|
|
|
2184
2542
|
if (isCorrelateExistingTrafficTracking(this.trackingConfiguration)) {
|
|
2185
2543
|
throw new Error("CorrelateExistingTraffic uses ForDuration and cannot be combined with WithLoadSimulations.");
|
|
2186
2544
|
}
|
|
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);
|
|
2545
|
+
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
2546
|
}
|
|
2189
2547
|
/**
|
|
2190
2548
|
* Configures thresholds for this SDK object.
|
|
@@ -2194,7 +2552,7 @@ class LoadStrikeScenario {
|
|
|
2194
2552
|
if (!thresholds.length) {
|
|
2195
2553
|
throw new Error("At least one threshold should be provided.");
|
|
2196
2554
|
}
|
|
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);
|
|
2555
|
+
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
2556
|
}
|
|
2199
2557
|
/**
|
|
2200
2558
|
* Returns simulations.
|
|
@@ -2255,6 +2613,26 @@ class LoadStrikeScenario {
|
|
|
2255
2613
|
__loadStrikeInternalLicenseFeatures() {
|
|
2256
2614
|
return [...this.internalLicenseFeatures];
|
|
2257
2615
|
}
|
|
2616
|
+
/** Freezes the named step identities that may be reported by this scenario. */
|
|
2617
|
+
withDeclaredSteps(...stepNames) {
|
|
2618
|
+
if (!stepNames.length) {
|
|
2619
|
+
throw new Error("At least one declared step name should be provided.");
|
|
2620
|
+
}
|
|
2621
|
+
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);
|
|
2622
|
+
}
|
|
2623
|
+
/** Returns the immutable declared-step names in declaration order. */
|
|
2624
|
+
getDeclaredSteps() {
|
|
2625
|
+
return [...this.declaredStepNames];
|
|
2626
|
+
}
|
|
2627
|
+
__loadStrikeSetTrafficMixV2Metadata(metadata) {
|
|
2628
|
+
this.trafficMixV2Metadata = cloneLoadEngineV2TrafficMixMetadata(metadata);
|
|
2629
|
+
return this;
|
|
2630
|
+
}
|
|
2631
|
+
__loadStrikeTrafficMixV2Metadata() {
|
|
2632
|
+
return this.trafficMixV2Metadata
|
|
2633
|
+
? cloneLoadEngineV2TrafficMixMetadata(this.trafficMixV2Metadata)
|
|
2634
|
+
: undefined;
|
|
2635
|
+
}
|
|
2258
2636
|
__loadStrikeScenarioSourceAnalysis() {
|
|
2259
2637
|
const source = this.runHandler.toString();
|
|
2260
2638
|
const lines = source
|
|
@@ -2277,7 +2655,7 @@ class LoadStrikeScenario {
|
|
|
2277
2655
|
}
|
|
2278
2656
|
__loadStrikeWithInternalLicenseFeatures(...features) {
|
|
2279
2657
|
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);
|
|
2658
|
+
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
2659
|
}
|
|
2282
2660
|
async invokeInit(context) {
|
|
2283
2661
|
if (this.initHandler) {
|
|
@@ -2298,6 +2676,9 @@ class LoadStrikeScenario {
|
|
|
2298
2676
|
return normalizeReply(result);
|
|
2299
2677
|
}
|
|
2300
2678
|
catch (error) {
|
|
2679
|
+
if (error instanceof RuntimePolicyCallbackError) {
|
|
2680
|
+
throw error;
|
|
2681
|
+
}
|
|
2301
2682
|
return LoadStrikeResponse.fail("exception", resolveRuntimeErrorMessage(error, "scenario failed"), 0);
|
|
2302
2683
|
}
|
|
2303
2684
|
}
|
|
@@ -2343,6 +2724,10 @@ class LoadStrikeScenario {
|
|
|
2343
2724
|
WithLoadSimulations(...simulations) {
|
|
2344
2725
|
return this.withLoadSimulations(...simulations);
|
|
2345
2726
|
}
|
|
2727
|
+
/** Freezes the named step identities that may be reported by this scenario. */
|
|
2728
|
+
WithDeclaredSteps(...stepNames) {
|
|
2729
|
+
return this.withDeclaredSteps(...stepNames);
|
|
2730
|
+
}
|
|
2346
2731
|
/**
|
|
2347
2732
|
* Configures max fail count for this SDK object.
|
|
2348
2733
|
* Use this when max fail count should be set explicitly before the run starts.
|
|
@@ -2473,7 +2858,7 @@ class LoadStrikeTrafficMix {
|
|
|
2473
2858
|
return this.withScenarioMix(...scenarioMix);
|
|
2474
2859
|
}
|
|
2475
2860
|
expandScenarios() {
|
|
2476
|
-
return expandTrafficMixScenarios(this);
|
|
2861
|
+
return expandTrafficMixScenarios(this, 0);
|
|
2477
2862
|
}
|
|
2478
2863
|
ExpandScenarios() {
|
|
2479
2864
|
return this.expandScenarios();
|
|
@@ -2487,10 +2872,11 @@ class LoadStrikeTrafficMix {
|
|
|
2487
2872
|
}
|
|
2488
2873
|
exports.LoadStrikeTrafficMix = LoadStrikeTrafficMix;
|
|
2489
2874
|
class LoadStrikeRunner {
|
|
2490
|
-
constructor(scenarios, options, contextConfigurators = []) {
|
|
2875
|
+
constructor(scenarios, options, contextConfigurators = [], internalOptions = {}) {
|
|
2491
2876
|
this.scenarios = scenarios;
|
|
2492
2877
|
this.options = normalizeRunnerOptionCollectionShapes(options);
|
|
2493
2878
|
this.contextConfigurators = [...contextConfigurators];
|
|
2879
|
+
this.internalOptions = internalOptions;
|
|
2494
2880
|
}
|
|
2495
2881
|
/**
|
|
2496
2882
|
* Creates a new instance of this public SDK type.
|
|
@@ -2526,7 +2912,7 @@ class LoadStrikeRunner {
|
|
|
2526
2912
|
* Use this when one total load profile should be split across weighted scenario lanes.
|
|
2527
2913
|
*/
|
|
2528
2914
|
static registerTrafficMix(trafficMix) {
|
|
2529
|
-
return LoadStrikeRunner.registerScenarios(...expandTrafficMixScenarios(trafficMix));
|
|
2915
|
+
return LoadStrikeRunner.registerScenarios(...expandTrafficMixScenarios(trafficMix, 0));
|
|
2530
2916
|
}
|
|
2531
2917
|
/**
|
|
2532
2918
|
* Registers a traffic mix on a fresh runnable context.
|
|
@@ -2542,6 +2928,12 @@ class LoadStrikeRunner {
|
|
|
2542
2928
|
static DisplayConsoleMetrics(context, enable) {
|
|
2543
2929
|
return context.DisplayConsoleMetrics(enable);
|
|
2544
2930
|
}
|
|
2931
|
+
static UseLoadEngineV2(context) {
|
|
2932
|
+
return context.UseLoadEngineV2();
|
|
2933
|
+
}
|
|
2934
|
+
static WithMaxInFlight(context, maxInFlight) {
|
|
2935
|
+
return context.WithMaxInFlight(maxInFlight);
|
|
2936
|
+
}
|
|
2545
2937
|
/**
|
|
2546
2938
|
* Toggles local development cluster mode.
|
|
2547
2939
|
* Use this when you want to simulate coordinator and agent behavior on a single machine.
|
|
@@ -2577,6 +2969,12 @@ class LoadStrikeRunner {
|
|
|
2577
2969
|
static WithAgentGroup(context, agentGroup) {
|
|
2578
2970
|
return context.WithAgentGroup(agentGroup);
|
|
2579
2971
|
}
|
|
2972
|
+
static WithAgentId(context, agentId) {
|
|
2973
|
+
return context.WithAgentId(agentId);
|
|
2974
|
+
}
|
|
2975
|
+
static WithExpectedAgentIds(context, ...agentIds) {
|
|
2976
|
+
return context.WithExpectedAgentIds(...agentIds);
|
|
2977
|
+
}
|
|
2580
2978
|
/**
|
|
2581
2979
|
* Sets the requested agent count.
|
|
2582
2980
|
* Use this when a coordinator should fan work out across a specific number of agents.
|
|
@@ -2794,7 +3192,10 @@ class LoadStrikeRunner {
|
|
|
2794
3192
|
* Use this when one total load profile should be split across weighted scenario lanes.
|
|
2795
3193
|
*/
|
|
2796
3194
|
addTrafficMix(trafficMix) {
|
|
2797
|
-
this.scenarios = [
|
|
3195
|
+
this.scenarios = [
|
|
3196
|
+
...this.scenarios,
|
|
3197
|
+
...expandTrafficMixScenarios(trafficMix, nextTrafficMixDeclarationIndex(this.scenarios))
|
|
3198
|
+
];
|
|
2798
3199
|
return this;
|
|
2799
3200
|
}
|
|
2800
3201
|
/**
|
|
@@ -2927,6 +3328,19 @@ class LoadStrikeRunner {
|
|
|
2927
3328
|
WithReportingInterval(intervalSeconds) {
|
|
2928
3329
|
return this.withReportingInterval(intervalSeconds);
|
|
2929
3330
|
}
|
|
3331
|
+
useLoadEngineV2() {
|
|
3332
|
+
return this.configure({ loadEngineContractVersion: 2 });
|
|
3333
|
+
}
|
|
3334
|
+
UseLoadEngineV2() {
|
|
3335
|
+
return this.useLoadEngineV2();
|
|
3336
|
+
}
|
|
3337
|
+
withMaxInFlight(maxInFlight) {
|
|
3338
|
+
validateV2MaxInFlight(this.options.loadEngineContractVersion, maxInFlight);
|
|
3339
|
+
return this.configure({ maxInFlight });
|
|
3340
|
+
}
|
|
3341
|
+
WithMaxInFlight(maxInFlight) {
|
|
3342
|
+
return this.withMaxInFlight(maxInFlight);
|
|
3343
|
+
}
|
|
2930
3344
|
withReportingSinks(...sinks) {
|
|
2931
3345
|
if (!sinks.length) {
|
|
2932
3346
|
throw new Error("At least one reporting sink should be provided.");
|
|
@@ -3025,7 +3439,7 @@ class LoadStrikeRunner {
|
|
|
3025
3439
|
}
|
|
3026
3440
|
async run(args = []) {
|
|
3027
3441
|
if (this.contextConfigurators.length) {
|
|
3028
|
-
return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions()).run(args);
|
|
3442
|
+
return new LoadStrikeRunner(this.scenarios, this.buildContext().toRunnerOptions(), [], this.internalOptions).run(args);
|
|
3029
3443
|
}
|
|
3030
3444
|
if (args.length) {
|
|
3031
3445
|
return this.buildContext().run(args);
|
|
@@ -3043,8 +3457,8 @@ class LoadStrikeRunner {
|
|
|
3043
3457
|
}));
|
|
3044
3458
|
const sinkErrors = [];
|
|
3045
3459
|
const policyErrors = [];
|
|
3046
|
-
const sinkRetryCount =
|
|
3047
|
-
const sinkRetryBackoffMs =
|
|
3460
|
+
const sinkRetryCount = (0, sink_retry_policy_js_1.normalizeSinkRetryCount)(this.options.sinkRetryCount);
|
|
3461
|
+
const sinkRetryBackoffMs = (0, sink_retry_policy_js_1.normalizeSinkRetryBackoffMs)(this.options.sinkRetryBackoffMs);
|
|
3048
3462
|
const policies = this.options.runtimePolicies ?? [];
|
|
3049
3463
|
const runtimePolicyErrorMode = normalizedRuntimePolicyErrorMode(this.options.runtimePolicyErrorMode);
|
|
3050
3464
|
const plugins = this.options.reportingSinks === undefined && this.options.workerPlugins === undefined &&
|
|
@@ -3073,10 +3487,15 @@ class LoadStrikeRunner {
|
|
|
3073
3487
|
let licenseClient = null;
|
|
3074
3488
|
let licensePayload = null;
|
|
3075
3489
|
let licenseSession = null;
|
|
3490
|
+
let iterationObservationsFinalized = false;
|
|
3076
3491
|
const clusterMode = resolveClusterExecutionMode(this.options);
|
|
3077
3492
|
const selectedScenarios = clusterMode === "local-coordinator" || clusterMode === "nats-coordinator"
|
|
3078
3493
|
? await this.filterScenariosWithPolicies(this.scenarios, policies, policyErrors, runtimePolicyErrorMode)
|
|
3079
3494
|
: await this.selectScenarios(policies, policyErrors, runtimePolicyErrorMode);
|
|
3495
|
+
if (this.options.loadEngineContractVersion === 2
|
|
3496
|
+
&& (clusterMode === "nats-coordinator" || clusterMode === "nats-agent")) {
|
|
3497
|
+
validateLoadEngineV2ScenarioFeatures(selectedScenarios);
|
|
3498
|
+
}
|
|
3080
3499
|
if (clusterMode === "nats-agent") {
|
|
3081
3500
|
return this.runAgentWithNats(createdUtc, testInfo, nodeInfo);
|
|
3082
3501
|
}
|
|
@@ -3133,14 +3552,49 @@ class LoadStrikeRunner {
|
|
|
3133
3552
|
await init(baseContext, this.options.infraConfig ?? {});
|
|
3134
3553
|
}
|
|
3135
3554
|
}
|
|
3136
|
-
await this.initializeSinks(sinkStates, baseContext, this.options.infraConfig ?? {}, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3555
|
+
await this.initializeSinks(sinkStates, baseContext, this.options.infraConfig ?? {}, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
|
|
3137
3556
|
for (const plugin of plugins) {
|
|
3138
3557
|
const start = resolveWorkerPluginStart(plugin);
|
|
3139
3558
|
if (start) {
|
|
3140
3559
|
await start(sessionInfo);
|
|
3141
3560
|
}
|
|
3142
3561
|
}
|
|
3143
|
-
await this.startSinks(sinkStates, sessionInfo, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3562
|
+
await this.startSinks(sinkStates, sessionInfo, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
|
|
3563
|
+
const iterationObservationRunId = String(this.internalOptions.iterationObservationRunId
|
|
3564
|
+
?? sessionInfo.portalReportingRunId
|
|
3565
|
+
?? sessionInfo.PortalReportingRunId
|
|
3566
|
+
?? testInfo.sessionId);
|
|
3567
|
+
const iterationObservationResultOwnerId = String(nodeInfo.nodeType).toLowerCase() === "agent"
|
|
3568
|
+
? String(this.options.agentCommandId
|
|
3569
|
+
?? `${nodeInfo.machineName}:${Math.max(Math.trunc(this.options.clusterShardIndex ?? 0), 0)}`)
|
|
3570
|
+
: "";
|
|
3571
|
+
const iterationObservationProcessGroup = 0;
|
|
3572
|
+
const iterationObservationExpectedResultOwnerCount64 = Math.max(Math.trunc(this.options.clusterShardCount ?? 1), 1).toString();
|
|
3573
|
+
const initializedIterationObservationSinks = sinkStates
|
|
3574
|
+
.filter((state) => !state.disabled)
|
|
3575
|
+
.map((state) => ({
|
|
3576
|
+
name: state.name,
|
|
3577
|
+
iterationObservationPortalSink: Boolean(state.sink.iterationObservationPortalSink),
|
|
3578
|
+
iterationObservationShapeLimited: Boolean(state.sink.iterationObservationShapeLimited),
|
|
3579
|
+
saveIterationBatch: resolveSinkSaveIterationBatch(state.sink),
|
|
3580
|
+
completeIterationObservationStream: resolveSinkCompleteIterationObservationStream(state.sink)
|
|
3581
|
+
}));
|
|
3582
|
+
const reporterIterationObservationSinks = this.internalOptions.iterationObservationSinks
|
|
3583
|
+
?? (clusterMode === "local-coordinator" || clusterMode === "nats-coordinator"
|
|
3584
|
+
? []
|
|
3585
|
+
: initializedIterationObservationSinks);
|
|
3586
|
+
const iterationObservationReporter = new iteration_observations_js_1.IterationObservationReporter({
|
|
3587
|
+
runId: iterationObservationRunId,
|
|
3588
|
+
sessionId: testInfo.sessionId,
|
|
3589
|
+
resultOwnerId: iterationObservationResultOwnerId,
|
|
3590
|
+
expectedResultOwnerCount64: iterationObservationExpectedResultOwnerCount64,
|
|
3591
|
+
processGroup: iterationObservationProcessGroup,
|
|
3592
|
+
settings: resolveIterationObservationSettings(this.options),
|
|
3593
|
+
sinks: reporterIterationObservationSinks,
|
|
3594
|
+
sinkRetryCount,
|
|
3595
|
+
sinkRetryBackoffMs,
|
|
3596
|
+
logger: runLogger
|
|
3597
|
+
});
|
|
3144
3598
|
const emitRealtimeSnapshot = async () => {
|
|
3145
3599
|
if (realtimeInFlight) {
|
|
3146
3600
|
return;
|
|
@@ -3151,7 +3605,7 @@ class LoadStrikeRunner {
|
|
|
3151
3605
|
.map((value) => value.build(scenarioDurationsMs.get(value.scenarioName) ?? Math.max(Date.now() - started.getTime(), 0)))
|
|
3152
3606
|
.sort((left, right) => left.sortIndex - right.sortIndex);
|
|
3153
3607
|
const metricsSnapshot = collectMetricStats(allRegisteredMetrics, Date.now() - started.getTime());
|
|
3154
|
-
await this.emitRealtimeStats(sinkStates, snapshot, metricsSnapshot, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3608
|
+
await this.emitRealtimeStats(sinkStates, snapshot, metricsSnapshot, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
|
|
3155
3609
|
if (toBoolean(this.options.displayConsoleMetrics, true)) {
|
|
3156
3610
|
const requestCount = snapshot.reduce((sum, value) => sum + value.allRequestCount, 0);
|
|
3157
3611
|
const okCount = snapshot.reduce((sum, value) => sum + value.allOkCount, 0);
|
|
@@ -3207,21 +3661,30 @@ class LoadStrikeRunner {
|
|
|
3207
3661
|
let result;
|
|
3208
3662
|
let metricStats;
|
|
3209
3663
|
if (clusterMode === "local-coordinator") {
|
|
3210
|
-
const aggregated = await this.runCoordinatorWithLocalAgents(selectedScenarios, testInfo, nodeInfo, licenseClient, licenseSession);
|
|
3664
|
+
const aggregated = await this.runCoordinatorWithLocalAgents(selectedScenarios, testInfo, nodeInfo, licenseClient, licenseSession, iterationObservationRunId, initializedIterationObservationSinks);
|
|
3211
3665
|
metricStats = aggregated.metrics;
|
|
3212
3666
|
result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors, policyErrors);
|
|
3213
3667
|
}
|
|
3214
3668
|
else if (clusterMode === "nats-coordinator") {
|
|
3215
|
-
const aggregated = await this.runCoordinatorWithNats(selectedScenarios, testInfo, nodeInfo, licenseClient, licenseSession);
|
|
3669
|
+
const aggregated = await this.runCoordinatorWithNats(selectedScenarios, testInfo, nodeInfo, licenseClient, licenseSession, iterationObservationRunId, initializedIterationObservationSinks);
|
|
3216
3670
|
metricStats = aggregated.metrics;
|
|
3217
3671
|
result = toDetailedRunResultFromNodeStats(aggregated, started.toISOString(), sinkErrors, policyErrors);
|
|
3218
3672
|
}
|
|
3219
3673
|
else {
|
|
3220
3674
|
const testAbortController = new AbortController();
|
|
3221
3675
|
const stopTestState = { value: false, reason: undefined };
|
|
3222
|
-
|
|
3676
|
+
const loadEngineV2Budget = this.options.loadEngineContractVersion === 2
|
|
3677
|
+
? (this.options.loadEngineV2BudgetOverride
|
|
3678
|
+
?? new load_engine_v2_js_1.LoadEngineV2ExecutionBudget(this.options.maxInFlight ?? 10000))
|
|
3679
|
+
: undefined;
|
|
3680
|
+
const loadEngineV2Telemetry = loadEngineV2Budget
|
|
3681
|
+
? new LoadEngineV2Telemetry(loadEngineV2Budget)
|
|
3682
|
+
: undefined;
|
|
3683
|
+
const executeSelectedScenario = (scenario, selectedScenarioIndex) => executeScenarioRuntime({
|
|
3223
3684
|
scenario,
|
|
3224
|
-
scenarioIndex
|
|
3685
|
+
scenarioIndex: this.options.loadEngineContractVersion === 2
|
|
3686
|
+
? this.scenarios.indexOf(scenario)
|
|
3687
|
+
: selectedScenarioIndex,
|
|
3225
3688
|
scenarioCount: selectedScenarios.length,
|
|
3226
3689
|
options: this.options,
|
|
3227
3690
|
logger: runLogger,
|
|
@@ -3236,12 +3699,26 @@ class LoadStrikeRunner {
|
|
|
3236
3699
|
scenarioDurationsMs,
|
|
3237
3700
|
stopTestState,
|
|
3238
3701
|
testAbortController,
|
|
3702
|
+
loadEngineV2Budget,
|
|
3703
|
+
loadEngineV2Telemetry,
|
|
3704
|
+
iterationObservationReporter,
|
|
3705
|
+
iterationObservationRunId,
|
|
3706
|
+
iterationObservationResultOwnerId,
|
|
3707
|
+
iterationObservationProcessGroup,
|
|
3239
3708
|
executeScenarioInvocation: (targetScenario, context, operation) => this.executeScenarioInvocation(targetScenario, context, operation),
|
|
3240
3709
|
invokeBeforeScenario: (runtimePolicies, scenarioName) => this.invokeBeforeScenario(runtimePolicies, scenarioName, policyErrors, runtimePolicyErrorMode),
|
|
3241
3710
|
invokeAfterScenario: (runtimePolicies, scenarioName, stats) => this.invokeAfterScenario(runtimePolicies, scenarioName, stats, policyErrors, runtimePolicyErrorMode),
|
|
3242
3711
|
invokeBeforeStep: (runtimePolicies, scenarioName, stepName) => this.invokeBeforeStep(runtimePolicies, scenarioName, stepName, policyErrors, runtimePolicyErrorMode),
|
|
3243
3712
|
invokeAfterStep: (runtimePolicies, scenarioName, stepName, reply) => this.invokeAfterStep(runtimePolicies, scenarioName, stepName, reply, policyErrors, runtimePolicyErrorMode)
|
|
3244
|
-
})
|
|
3713
|
+
});
|
|
3714
|
+
if (this.options.loadEngineV2SegmentLifecycleOverride) {
|
|
3715
|
+
for (let scenarioIndex = 0; scenarioIndex < selectedScenarios.length; scenarioIndex += 1) {
|
|
3716
|
+
await executeSelectedScenario(selectedScenarios[scenarioIndex], scenarioIndex);
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
else {
|
|
3720
|
+
await Promise.all(selectedScenarios.map(executeSelectedScenario));
|
|
3721
|
+
}
|
|
3245
3722
|
nodeInfo.currentOperation = stopTestState.value ? "Stop" : "Complete";
|
|
3246
3723
|
const scenarioStatList = Array.from(scenarioAccumulators.values())
|
|
3247
3724
|
.map((value) => value.build(scenarioDurationsMs.get(value.scenarioName) ?? 0))
|
|
@@ -3281,17 +3758,46 @@ class LoadStrikeRunner {
|
|
|
3281
3758
|
reportFiles: [],
|
|
3282
3759
|
logFiles: [...loggerSetup.logFiles],
|
|
3283
3760
|
correlationRows: buildDetailedCorrelationRows(),
|
|
3284
|
-
failedCorrelationRows: buildDetailedFailedCorrelationRows()
|
|
3761
|
+
failedCorrelationRows: buildDetailedFailedCorrelationRows(),
|
|
3762
|
+
...(loadEngineV2Telemetry
|
|
3763
|
+
? {
|
|
3764
|
+
generatorWarnings: loadEngineV2Telemetry.buildWarnings(),
|
|
3765
|
+
schedulerSegments: loadEngineV2Telemetry.buildSegments(),
|
|
3766
|
+
schedulerStats: loadEngineV2Telemetry.buildStats(),
|
|
3767
|
+
[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: loadEngineV2Telemetry.buildSchedulerDistributions()
|
|
3768
|
+
}
|
|
3769
|
+
: {})
|
|
3285
3770
|
};
|
|
3286
3771
|
}
|
|
3287
3772
|
await stopRealtimeReporting();
|
|
3773
|
+
const observationDelivery = await iterationObservationReporter.sealAndDrain();
|
|
3774
|
+
iterationObservationsFinalized = true;
|
|
3775
|
+
if (clusterMode !== "local-coordinator"
|
|
3776
|
+
&& clusterMode !== "nats-coordinator") {
|
|
3777
|
+
result.observationDeliveryStats = {
|
|
3778
|
+
lastBatchSequence64: observationDelivery.lastBatchSequence64,
|
|
3779
|
+
capturedCount64: observationDelivery.capturedCount64,
|
|
3780
|
+
deliveredCount64: observationDelivery.deliveredCount64,
|
|
3781
|
+
droppedBufferCount64: observationDelivery.droppedBufferCount64,
|
|
3782
|
+
droppedSinkCount64: observationDelivery.droppedSinkCount64
|
|
3783
|
+
};
|
|
3784
|
+
result.reportingComplete = observationDelivery.reportingComplete;
|
|
3785
|
+
}
|
|
3786
|
+
else {
|
|
3787
|
+
result.observationDeliveryStats ?? (result.observationDeliveryStats = emptyObservationDeliveryStats());
|
|
3788
|
+
result.reportingComplete ?? (result.reportingComplete = observationDelivery.reportingComplete);
|
|
3789
|
+
}
|
|
3790
|
+
result.generatorWarnings = [
|
|
3791
|
+
...(result.generatorWarnings ?? []),
|
|
3792
|
+
...iterationObservationReporter.buildWarnings()
|
|
3793
|
+
];
|
|
3288
3794
|
result.pluginsData = mergePluginData(result.pluginsData, await this.collectPluginData(plugins, attachRunResultAliases(result), pluginLifecycleErrors));
|
|
3289
3795
|
const finalizedResult = attachRunResultAliases(result);
|
|
3290
3796
|
finalizedResult.logFiles = mergeStringArrays(finalizedResult.logFiles, loggerSetup.logFiles);
|
|
3291
3797
|
finalizedResult.reportFiles = this.writeReports(finalizedResult);
|
|
3292
3798
|
finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
|
|
3293
|
-
await this.emitRunResult(sinkStates, finalizedResult, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3294
|
-
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3799
|
+
await this.emitRunResult(sinkStates, finalizedResult, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
|
|
3800
|
+
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
|
|
3295
3801
|
sinksStopped = true;
|
|
3296
3802
|
finalizedResult.disabledSinks = sinkStates.filter((x) => x.disabled).map((x) => x.name);
|
|
3297
3803
|
finalizedResult.sinkErrors = sinkErrors
|
|
@@ -3300,8 +3806,11 @@ class LoadStrikeRunner {
|
|
|
3300
3806
|
}
|
|
3301
3807
|
finally {
|
|
3302
3808
|
await stopRealtimeReporting();
|
|
3809
|
+
if (!iterationObservationsFinalized) {
|
|
3810
|
+
await iterationObservationReporter.sealAndDrain().catch(() => { });
|
|
3811
|
+
}
|
|
3303
3812
|
if (!sinksStopped) {
|
|
3304
|
-
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors);
|
|
3813
|
+
await this.stopSinks(sinkStates, sinkRetryCount, sinkRetryBackoffMs, sinkErrors, testInfo.sessionId, runLogger);
|
|
3305
3814
|
}
|
|
3306
3815
|
if (!pluginsStopped) {
|
|
3307
3816
|
await this.stopPlugins(plugins, pluginLifecycleErrors, runLogger);
|
|
@@ -3336,7 +3845,7 @@ class LoadStrikeRunner {
|
|
|
3336
3845
|
}
|
|
3337
3846
|
return filtered;
|
|
3338
3847
|
}
|
|
3339
|
-
async runClusterChildNode(targetScenarios, nodeType, machineName, includeWorkerExtensions, overrides = {}) {
|
|
3848
|
+
async runClusterChildNode(targetScenarios, nodeType, machineName, includeWorkerExtensions, overrides = {}, internalOptions = {}) {
|
|
3340
3849
|
if (!targetScenarios.length) {
|
|
3341
3850
|
return buildEmptyNodeStats({
|
|
3342
3851
|
startedUtc: new Date().toISOString(),
|
|
@@ -3364,7 +3873,7 @@ class LoadStrikeRunner {
|
|
|
3364
3873
|
displayConsoleMetrics: false,
|
|
3365
3874
|
reportingSinks: includeWorkerExtensions ? this.options.reportingSinks : [],
|
|
3366
3875
|
workerPlugins: includeWorkerExtensions ? this.options.workerPlugins : []
|
|
3367
|
-
});
|
|
3876
|
+
}, [], internalOptions);
|
|
3368
3877
|
const childResult = await childRunner.run();
|
|
3369
3878
|
const childStats = detailedToNodeStats(childResult);
|
|
3370
3879
|
return {
|
|
@@ -3377,70 +3886,127 @@ class LoadStrikeRunner {
|
|
|
3377
3886
|
logFiles: [...(childResult.logFiles ?? [])]
|
|
3378
3887
|
};
|
|
3379
3888
|
}
|
|
3380
|
-
async runCoordinatorWithLocalAgents(scenarios, testInfo, nodeInfo, licenseClient, licenseSession) {
|
|
3889
|
+
async runCoordinatorWithLocalAgents(scenarios, testInfo, nodeInfo, licenseClient, licenseSession, iterationObservationRunId, iterationObservationSinks) {
|
|
3381
3890
|
if (!licenseClient) {
|
|
3382
3891
|
throw new Error("Coordinator agent execution authorization requires an initialized licensing client.");
|
|
3383
3892
|
}
|
|
3384
|
-
const controllerRunToken =
|
|
3893
|
+
const controllerRunToken = currentLicenseSessionRunToken(licenseSession);
|
|
3385
3894
|
if (!controllerRunToken) {
|
|
3386
3895
|
throw new Error("Coordinator agent execution authorization requires an active controller run token.");
|
|
3387
3896
|
}
|
|
3388
|
-
const assignments = planRuntimeClusterAssignments(scenarios, Math.max(this.options.agentsCount ?? 1, 1), this.options.targetScenarios ?? [], this.options.agentTargetScenarios ?? []);
|
|
3897
|
+
const assignments = planRuntimeClusterAssignments(scenarios, Math.max(this.options.agentsCount ?? 1, 1), this.options.targetScenarios ?? [], this.options.agentTargetScenarios ?? [], this.options.loadEngineContractVersion === 2, this.options.coordinatorTargetScenarios ?? []);
|
|
3898
|
+
const sharedV2Budget = this.options.loadEngineContractVersion === 2
|
|
3899
|
+
? new load_engine_v2_js_1.LoadEngineV2ExecutionBudget(this.options.maxInFlight ?? 10000)
|
|
3900
|
+
: undefined;
|
|
3389
3901
|
const nodeResults = await Promise.all(assignments.map(async (targetScenarios, index) => {
|
|
3390
3902
|
const commandId = (0, node_crypto_1.randomBytes)(16).toString("hex");
|
|
3391
|
-
const agentExecutionToken = await licenseClient.createAgentExecutionToken(
|
|
3903
|
+
const agentExecutionToken = await licenseClient.createAgentExecutionToken(currentLicenseSessionRunToken(licenseSession), testInfo.sessionId, commandId, index, assignments.length, targetScenarios);
|
|
3392
3904
|
return this.runClusterChildNode(targetScenarios, "Agent", `local-agent-${index + 1}`, false, {
|
|
3393
3905
|
sessionId: testInfo.sessionId,
|
|
3394
3906
|
testSuite: testInfo.testSuite,
|
|
3395
3907
|
testName: testInfo.testName,
|
|
3396
3908
|
agentCommandId: commandId,
|
|
3397
|
-
agentExecutionToken
|
|
3909
|
+
agentExecutionToken,
|
|
3910
|
+
clusterShardIndex: index,
|
|
3911
|
+
clusterShardCount: assignments.length,
|
|
3912
|
+
loadEngineV2BudgetOverride: sharedV2Budget
|
|
3913
|
+
}, {
|
|
3914
|
+
iterationObservationRunId,
|
|
3915
|
+
iterationObservationSinks
|
|
3398
3916
|
});
|
|
3399
3917
|
}));
|
|
3400
3918
|
const coordinatorTargets = [...(this.options.coordinatorTargetScenarios ?? [])];
|
|
3401
3919
|
if (coordinatorTargets.length) {
|
|
3402
|
-
nodeResults.push(await this.runClusterChildNode(coordinatorTargets, "Coordinator", nodeInfo.machineName, false
|
|
3920
|
+
nodeResults.push(await this.runClusterChildNode(coordinatorTargets, "Coordinator", nodeInfo.machineName, false, {}, {
|
|
3921
|
+
iterationObservationRunId,
|
|
3922
|
+
iterationObservationSinks
|
|
3923
|
+
}));
|
|
3403
3924
|
}
|
|
3404
|
-
return aggregateNodeStats(testInfo, { ...nodeInfo, nodeType: "Coordinator" }, nodeResults);
|
|
3925
|
+
return aggregateNodeStats(testInfo, { ...nodeInfo, nodeType: "Coordinator" }, nodeResults, this.options.loadEngineContractVersion === 2);
|
|
3405
3926
|
}
|
|
3406
|
-
async runCoordinatorWithNats(scenarios, testInfo, nodeInfo, licenseClient, licenseSession) {
|
|
3927
|
+
async runCoordinatorWithNats(scenarios, testInfo, nodeInfo, licenseClient, licenseSession, iterationObservationRunId, iterationObservationSinks) {
|
|
3407
3928
|
if (!licenseClient) {
|
|
3408
3929
|
throw new Error("Coordinator agent execution authorization requires an initialized licensing client.");
|
|
3409
3930
|
}
|
|
3410
|
-
const controllerRunToken =
|
|
3931
|
+
const controllerRunToken = currentLicenseSessionRunToken(licenseSession);
|
|
3411
3932
|
if (!controllerRunToken) {
|
|
3412
3933
|
throw new Error("Coordinator agent execution authorization requires an active controller run token.");
|
|
3413
3934
|
}
|
|
3414
|
-
const assignments = planRuntimeClusterAssignments(scenarios, Math.max(this.options.agentsCount ?? 1, 1), this.options.targetScenarios ?? [], this.options.agentTargetScenarios ?? []);
|
|
3935
|
+
const assignments = planRuntimeClusterAssignments(scenarios, Math.max(this.options.agentsCount ?? 1, 1), this.options.targetScenarios ?? [], this.options.agentTargetScenarios ?? [], this.options.loadEngineContractVersion === 2, this.options.coordinatorTargetScenarios ?? []);
|
|
3936
|
+
const expectedAgentIds = this.options.loadEngineContractVersion === 2
|
|
3937
|
+
? normalizeRequiredV2AgentIds(this.options.expectedAgentIds, assignments.length)
|
|
3938
|
+
: undefined;
|
|
3415
3939
|
const coordinator = new cluster_js_1.DistributedClusterCoordinator({
|
|
3416
3940
|
clusterId: this.options.clusterId ?? "local",
|
|
3417
3941
|
sessionId: testInfo.sessionId,
|
|
3418
3942
|
testSuite: testInfo.testSuite,
|
|
3419
3943
|
testName: testInfo.testName,
|
|
3420
3944
|
expectedAgentResults: assignments.length,
|
|
3945
|
+
expectedAgentIds,
|
|
3946
|
+
loadEngineContractVersion: this.options.loadEngineContractVersion ?? 1,
|
|
3421
3947
|
agentGroup: this.options.agentGroup,
|
|
3422
3948
|
commandTimeoutMs: Math.max(Math.trunc((this.options.clusterCommandTimeoutSeconds ?? 120) * 1000), 1),
|
|
3423
3949
|
nats: this.options.natsServerUrl
|
|
3424
3950
|
? { ServerUrl: this.options.natsServerUrl }
|
|
3425
3951
|
: undefined
|
|
3426
3952
|
});
|
|
3427
|
-
const
|
|
3428
|
-
const
|
|
3953
|
+
const tokenFactory = (command) => licenseClient.createAgentExecutionToken(currentLicenseSessionRunToken(licenseSession), testInfo.sessionId, command.commandId, command.agentIndex, command.agentCount, command.targetScenarios);
|
|
3954
|
+
const dispatch = this.options.loadEngineContractVersion === 2
|
|
3955
|
+
? await coordinator.dispatchV2(assignments, buildRuntimeLoadEngineV2Plan(scenarios, this.options, testInfo, expectedAgentIds), tokenFactory)
|
|
3956
|
+
: await coordinator.dispatch(assignments, tokenFactory);
|
|
3957
|
+
const nodes = dispatch.nodeResults.map((value) => clusterNodeResultToNodeStats(value, testInfo, { ...nodeInfo, nodeType: "Agent" }, this.options.loadEngineContractVersion === 2));
|
|
3429
3958
|
const coordinatorTargets = [...(this.options.coordinatorTargetScenarios ?? [])];
|
|
3430
3959
|
if (coordinatorTargets.length) {
|
|
3431
|
-
nodes.push(await this.runClusterChildNode(coordinatorTargets, "Coordinator", nodeInfo.machineName, false
|
|
3960
|
+
nodes.push(await this.runClusterChildNode(coordinatorTargets, "Coordinator", nodeInfo.machineName, false, {}, {
|
|
3961
|
+
iterationObservationRunId,
|
|
3962
|
+
iterationObservationSinks
|
|
3963
|
+
}));
|
|
3432
3964
|
}
|
|
3433
|
-
let aggregated = aggregateNodeStats(testInfo, { ...nodeInfo, nodeType: "Coordinator" }, nodes);
|
|
3965
|
+
let aggregated = aggregateNodeStats(testInfo, { ...nodeInfo, nodeType: "Coordinator" }, nodes, this.options.loadEngineContractVersion === 2);
|
|
3434
3966
|
if (dispatch.missingNodes > 0) {
|
|
3435
3967
|
aggregated = appendClusterPluginHint(aggregated, `Timed out waiting for ${dispatch.missingNodes} agent node result(s).`);
|
|
3968
|
+
aggregated = attachNodeStatsAliases({
|
|
3969
|
+
...aggregated,
|
|
3970
|
+
reportingComplete: false,
|
|
3971
|
+
schedulerSegments: [
|
|
3972
|
+
...(aggregated.schedulerSegments ?? []),
|
|
3973
|
+
...(dispatch.ownerLoss?.schedulerSegments ?? [])
|
|
3974
|
+
],
|
|
3975
|
+
generatorWarnings: [
|
|
3976
|
+
...aggregated.generatorWarnings,
|
|
3977
|
+
...(dispatch.ownerLoss?.generatorWarnings ?? []).map((warning) => ({
|
|
3978
|
+
code: warning.code,
|
|
3979
|
+
scenarioName: "cluster",
|
|
3980
|
+
simulationIndex: -1,
|
|
3981
|
+
count64: warning.count64,
|
|
3982
|
+
message: `Result owner ${warning.agentId} was lost after assignment; application failures were not fabricated.`,
|
|
3983
|
+
firstObservedUtcNs: "0",
|
|
3984
|
+
lastObservedUtcNs: "0"
|
|
3985
|
+
}))
|
|
3986
|
+
]
|
|
3987
|
+
});
|
|
3436
3988
|
}
|
|
3437
3989
|
return aggregated;
|
|
3438
3990
|
}
|
|
3439
3991
|
async runAgentWithNats(startedUtc, testInfo, nodeInfo) {
|
|
3992
|
+
const v2AgentId = this.options.loadEngineContractVersion === 2
|
|
3993
|
+
? requireNonEmpty(this.options.agentId ?? "", "Remote Load Engine V2 agents require an explicit stable AgentId.")
|
|
3994
|
+
: `${nodeInfo.machineName}-${generateRuntimeSessionId()}`;
|
|
3440
3995
|
const agent = new cluster_js_1.DistributedClusterAgent({
|
|
3441
3996
|
clusterId: this.options.clusterId ?? "local",
|
|
3442
3997
|
sessionId: testInfo.sessionId,
|
|
3443
|
-
agentId:
|
|
3998
|
+
agentId: v2AgentId,
|
|
3999
|
+
loadEngineContractVersion: this.options.loadEngineContractVersion ?? 1,
|
|
4000
|
+
validateV2Plan: this.options.loadEngineContractVersion === 2
|
|
4001
|
+
? (received) => {
|
|
4002
|
+
const local = buildRuntimeLoadEngineV2Plan(this.scenarios, this.options, { ...testInfo, sessionId: received.sessionId }, received.expectedAgentIds);
|
|
4003
|
+
local.runId = received.runId;
|
|
4004
|
+
local.registrationNonce = received.registrationNonce;
|
|
4005
|
+
if ((0, cluster_js_1.buildLoadEngineV2Plan)(local).hash !== (0, cluster_js_1.buildLoadEngineV2Plan)(received).hash) {
|
|
4006
|
+
throw new Error("Load Engine V2 signed plan descriptors do not match immutable local scenario declarations.");
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
: undefined,
|
|
3444
4010
|
agentGroup: this.options.agentGroup,
|
|
3445
4011
|
nats: this.options.natsServerUrl
|
|
3446
4012
|
? { ServerUrl: this.options.natsServerUrl }
|
|
@@ -3450,13 +4016,16 @@ class LoadStrikeRunner {
|
|
|
3450
4016
|
const deadline = Date.now() + Math.max(Math.trunc((this.options.clusterCommandTimeoutSeconds ?? 120) * 1000), 1);
|
|
3451
4017
|
while (Date.now() < deadline) {
|
|
3452
4018
|
let handledStats = null;
|
|
3453
|
-
const
|
|
4019
|
+
const execute = async (dispatch) => {
|
|
3454
4020
|
handledStats = await this.runClusterChildNode(dispatch.scenarioNames, "Agent", nodeInfo.machineName, true, {
|
|
3455
4021
|
sessionId: testInfo.sessionId,
|
|
3456
4022
|
testSuite: testInfo.testSuite,
|
|
3457
4023
|
testName: testInfo.testName,
|
|
3458
4024
|
agentCommandId: dispatch.commandId,
|
|
3459
|
-
agentExecutionToken: dispatch.agentRunToken
|
|
4025
|
+
agentExecutionToken: dispatch.agentRunToken,
|
|
4026
|
+
clusterShardIndex: dispatch.agentIndex ?? 0,
|
|
4027
|
+
clusterShardCount: dispatch.agentCount ?? 1,
|
|
4028
|
+
loadEngineV2SegmentLifecycleOverride: dispatch.segmentLifecycle
|
|
3460
4029
|
});
|
|
3461
4030
|
return {
|
|
3462
4031
|
nodeId: handledStats.nodeInfo.machineName,
|
|
@@ -3464,9 +4033,12 @@ class LoadStrikeRunner {
|
|
|
3464
4033
|
allRequestCount: handledStats.allRequestCount,
|
|
3465
4034
|
allOkCount: handledStats.allOkCount,
|
|
3466
4035
|
allFailCount: handledStats.allFailCount,
|
|
3467
|
-
stats: nodeStatsToClusterPayload(handledStats)
|
|
4036
|
+
stats: nodeStatsToClusterPayload(handledStats, this.scenarios.filter((scenario) => dispatch.scenarioNames.includes(scenario.name)), this.options.loadEngineContractVersion === 2)
|
|
3468
4037
|
};
|
|
3469
|
-
}
|
|
4038
|
+
};
|
|
4039
|
+
const handled = this.options.loadEngineContractVersion === 2
|
|
4040
|
+
? await agent.pollAndExecuteV2Once(execute)
|
|
4041
|
+
: await agent.pollAndExecuteOnce(execute);
|
|
3470
4042
|
if (handled && handledStats) {
|
|
3471
4043
|
return toDetailedRunResultFromNodeStats(handledStats, startedUtc, [], []);
|
|
3472
4044
|
}
|
|
@@ -3546,9 +4118,9 @@ class LoadStrikeRunner {
|
|
|
3546
4118
|
}
|
|
3547
4119
|
return filtered;
|
|
3548
4120
|
}
|
|
3549
|
-
async initializeSinks(sinkStates, context, infraConfig, retryCount, retryBackoffMs, sinkErrors) {
|
|
4121
|
+
async initializeSinks(sinkStates, context, infraConfig, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
|
|
3550
4122
|
for (const state of sinkStates) {
|
|
3551
|
-
await this.invokeSinkAction(state, "init", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
|
|
4123
|
+
await this.invokeSinkAction(state, "init", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, true, async () => {
|
|
3552
4124
|
const init = resolveSinkInit(state.sink);
|
|
3553
4125
|
if (init) {
|
|
3554
4126
|
await init(context, infraConfig);
|
|
@@ -3556,9 +4128,9 @@ class LoadStrikeRunner {
|
|
|
3556
4128
|
});
|
|
3557
4129
|
}
|
|
3558
4130
|
}
|
|
3559
|
-
async startSinks(sinkStates, session, retryCount, retryBackoffMs, sinkErrors) {
|
|
4131
|
+
async startSinks(sinkStates, session, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
|
|
3560
4132
|
for (const state of sinkStates) {
|
|
3561
|
-
await this.invokeSinkAction(state, "start", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
|
|
4133
|
+
await this.invokeSinkAction(state, "start", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, true, async () => {
|
|
3562
4134
|
const start = resolveSinkStart(state.sink);
|
|
3563
4135
|
if (start) {
|
|
3564
4136
|
await start(session);
|
|
@@ -3566,9 +4138,9 @@ class LoadStrikeRunner {
|
|
|
3566
4138
|
});
|
|
3567
4139
|
}
|
|
3568
4140
|
}
|
|
3569
|
-
async emitRealtimeStats(sinkStates, scenarioStats, metrics, retryCount, retryBackoffMs, sinkErrors) {
|
|
4141
|
+
async emitRealtimeStats(sinkStates, scenarioStats, metrics, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
|
|
3570
4142
|
for (const state of sinkStates) {
|
|
3571
|
-
await this.invokeSinkAction(state, "realtime", retryCount, retryBackoffMs, sinkErrors, false, true, async () => {
|
|
4143
|
+
await this.invokeSinkAction(state, "realtime", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, true, async () => {
|
|
3572
4144
|
const saveRealtimeStats = resolveSinkSaveRealtimeStats(state.sink);
|
|
3573
4145
|
if (saveRealtimeStats) {
|
|
3574
4146
|
await saveRealtimeStats(scenarioStats);
|
|
@@ -3580,10 +4152,9 @@ class LoadStrikeRunner {
|
|
|
3580
4152
|
});
|
|
3581
4153
|
}
|
|
3582
4154
|
}
|
|
3583
|
-
async stopSinks(sinkStates, retryCount, retryBackoffMs, sinkErrors) {
|
|
3584
|
-
const shutdownRetryCount = 0;
|
|
4155
|
+
async stopSinks(sinkStates, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
|
|
3585
4156
|
for (const state of sinkStates) {
|
|
3586
|
-
await this.invokeSinkAction(state, "stop",
|
|
4157
|
+
await this.invokeSinkAction(state, "stop", retryCount, retryBackoffMs, sinkErrors, runId, logger, true, true, async () => {
|
|
3587
4158
|
const stop = resolveSinkStop(state.sink);
|
|
3588
4159
|
if (stop) {
|
|
3589
4160
|
await stop();
|
|
@@ -3591,15 +4162,15 @@ class LoadStrikeRunner {
|
|
|
3591
4162
|
});
|
|
3592
4163
|
const dispose = resolveSinkDispose(state.sink);
|
|
3593
4164
|
if (dispose) {
|
|
3594
|
-
await this.invokeSinkAction(state, "dispose",
|
|
4165
|
+
await this.invokeSinkAction(state, "dispose", retryCount, retryBackoffMs, sinkErrors, runId, logger, true, true, async () => {
|
|
3595
4166
|
await dispose();
|
|
3596
4167
|
});
|
|
3597
4168
|
}
|
|
3598
4169
|
}
|
|
3599
4170
|
}
|
|
3600
|
-
async emitRunResult(sinkStates, result, retryCount, retryBackoffMs, sinkErrors) {
|
|
4171
|
+
async emitRunResult(sinkStates, result, retryCount, retryBackoffMs, sinkErrors, runId, logger) {
|
|
3601
4172
|
for (const state of sinkStates) {
|
|
3602
|
-
await this.invokeSinkAction(state, "run-result", retryCount, retryBackoffMs, sinkErrors, false, false, async () => {
|
|
4173
|
+
await this.invokeSinkAction(state, "run-result", retryCount, retryBackoffMs, sinkErrors, runId, logger, false, false, async () => {
|
|
3603
4174
|
const saveRunResult = resolveSinkSaveRunResult(state.sink);
|
|
3604
4175
|
if (saveRunResult) {
|
|
3605
4176
|
await saveRunResult(result);
|
|
@@ -3607,23 +4178,49 @@ class LoadStrikeRunner {
|
|
|
3607
4178
|
});
|
|
3608
4179
|
}
|
|
3609
4180
|
}
|
|
3610
|
-
async invokeSinkAction(state, phase, retryCount, retryBackoffMs, sinkErrors, ignoreDisabled, disableOnFailure, action) {
|
|
4181
|
+
async invokeSinkAction(state, phase, retryCount, retryBackoffMs, sinkErrors, runId, logger, ignoreDisabled, disableOnFailure, action) {
|
|
3611
4182
|
if (state.disabled && !ignoreDisabled) {
|
|
3612
4183
|
return;
|
|
3613
4184
|
}
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
4185
|
+
const maximumAttempts = (0, sink_retry_policy_js_1.normalizeSinkRetryCount)(retryCount) + 1;
|
|
4186
|
+
const backoffMs = (0, sink_retry_policy_js_1.normalizeSinkRetryBackoffMs)(retryBackoffMs);
|
|
4187
|
+
for (let attempts = 1; attempts <= maximumAttempts; attempts += 1) {
|
|
3617
4188
|
try {
|
|
3618
4189
|
await action();
|
|
4190
|
+
if (attempts > 1) {
|
|
4191
|
+
(0, iteration_observation_diagnostics_js_1.logIterationObservationRecovery)(logger, {
|
|
4192
|
+
sinkName: state.name,
|
|
4193
|
+
operation: "reporting-sink-action",
|
|
4194
|
+
phase,
|
|
4195
|
+
runId,
|
|
4196
|
+
resultOwnerId: "",
|
|
4197
|
+
observationCount: 0,
|
|
4198
|
+
attempt: attempts,
|
|
4199
|
+
maximumAttempts,
|
|
4200
|
+
nextDelayMs: 0
|
|
4201
|
+
});
|
|
4202
|
+
}
|
|
3619
4203
|
return;
|
|
3620
4204
|
}
|
|
3621
4205
|
catch (error) {
|
|
3622
|
-
|
|
4206
|
+
const exhausted = attempts >= maximumAttempts;
|
|
4207
|
+
const nextDelayMs = exhausted ? 0 : (0, sink_retry_policy_js_1.sinkRetryDelayMs)(backoffMs, attempts);
|
|
4208
|
+
(0, iteration_observation_diagnostics_js_1.logIterationObservationFailure)(logger, exhausted ? "error" : "warn", {
|
|
4209
|
+
sinkName: state.name,
|
|
4210
|
+
operation: "reporting-sink-action",
|
|
4211
|
+
phase,
|
|
4212
|
+
runId,
|
|
4213
|
+
resultOwnerId: "",
|
|
4214
|
+
observationCount: 0,
|
|
4215
|
+
attempt: attempts,
|
|
4216
|
+
maximumAttempts,
|
|
4217
|
+
nextDelayMs
|
|
4218
|
+
}, error);
|
|
4219
|
+
if (exhausted) {
|
|
3623
4220
|
sinkErrors.push({
|
|
3624
4221
|
sinkName: state.name,
|
|
3625
4222
|
phase,
|
|
3626
|
-
message:
|
|
4223
|
+
message: "The reporting sink action failed after retries.",
|
|
3627
4224
|
attempts
|
|
3628
4225
|
});
|
|
3629
4226
|
if (disableOnFailure) {
|
|
@@ -3631,9 +4228,7 @@ class LoadStrikeRunner {
|
|
|
3631
4228
|
}
|
|
3632
4229
|
return;
|
|
3633
4230
|
}
|
|
3634
|
-
|
|
3635
|
-
await sleep(retryBackoffMs * attempts);
|
|
3636
|
-
}
|
|
4231
|
+
await (0, sink_retry_policy_js_1.waitForSinkRetryDelay)(nextDelayMs);
|
|
3637
4232
|
}
|
|
3638
4233
|
}
|
|
3639
4234
|
}
|
|
@@ -3746,13 +4341,148 @@ function hasPluginRows(value) {
|
|
|
3746
4341
|
}
|
|
3747
4342
|
return value.tables.some((table) => Array.isArray(table.rows) && table.rows.length > 0);
|
|
3748
4343
|
}
|
|
4344
|
+
async function executeV2FixedArrivals(args) {
|
|
4345
|
+
const { rate, intervalNs, totalArrivals, budget, cancellationToken, shouldStopNow, nextInstanceInfo, runBombingInvocation, logger, scenarioName, deadlineOffsetsNs, tolerancesNs, ownedOrdinals, shardIndex = 0, shardCount = 1, telemetry, segment } = args;
|
|
4346
|
+
const segmentStartNs = process.hrtime.bigint();
|
|
4347
|
+
const toleranceNs = (0, load_engine_v2_js_1.loadEngineV2LatenessToleranceNs)(rate, intervalNs);
|
|
4348
|
+
const active = new Set();
|
|
4349
|
+
let schedulerLate = 0n;
|
|
4350
|
+
let maxInFlight = 0n;
|
|
4351
|
+
let executionError;
|
|
4352
|
+
const normalizedShardCount = Math.max(Math.trunc(shardCount), 1);
|
|
4353
|
+
const normalizedShardIndex = Math.min(Math.max(Math.trunc(shardIndex), 0), normalizedShardCount - 1);
|
|
4354
|
+
const ordinals = ownedOrdinals
|
|
4355
|
+
? [...ownedOrdinals]
|
|
4356
|
+
: (() => {
|
|
4357
|
+
const values = [];
|
|
4358
|
+
for (let ordinal = BigInt(normalizedShardIndex); ordinal < totalArrivals; ordinal += BigInt(normalizedShardCount)) {
|
|
4359
|
+
values.push(ordinal);
|
|
4360
|
+
}
|
|
4361
|
+
return values;
|
|
4362
|
+
})();
|
|
4363
|
+
if (ordinals.some((ordinal, index) => ordinal < 0n || ordinal >= totalArrivals
|
|
4364
|
+
|| (index > 0 && ordinal <= ordinals[index - 1]))) {
|
|
4365
|
+
throw new Error("Load Engine V2 owned arrival ordinals must be sorted, unique, and in range.");
|
|
4366
|
+
}
|
|
4367
|
+
if (segment) {
|
|
4368
|
+
segment.planned = BigInt(ordinals.length);
|
|
4369
|
+
}
|
|
4370
|
+
for (const ordinal of ordinals) {
|
|
4371
|
+
if (shouldStopNow())
|
|
4372
|
+
break;
|
|
4373
|
+
const index = Number(ordinal);
|
|
4374
|
+
const deadlineNs = segmentStartNs
|
|
4375
|
+
+ (deadlineOffsetsNs?.[index] ?? (0, load_engine_v2_js_1.loadEngineV2FixedDeadlineNs)(ordinal, rate, intervalNs));
|
|
4376
|
+
await delayUntilMonotonicDeadline(deadlineNs, cancellationToken);
|
|
4377
|
+
if (shouldStopNow()) {
|
|
4378
|
+
break;
|
|
4379
|
+
}
|
|
4380
|
+
const nowNs = process.hrtime.bigint();
|
|
4381
|
+
if (segment)
|
|
4382
|
+
segment.due += 1n;
|
|
4383
|
+
if (segment)
|
|
4384
|
+
telemetry?.recordDecisionLag(segment, nowNs - deadlineNs);
|
|
4385
|
+
if ((0, load_engine_v2_js_1.classifyLoadEngineV2Arrival)(nowNs, deadlineNs, tolerancesNs?.[index] ?? toleranceNs, true) === "scheduler_late") {
|
|
4386
|
+
schedulerLate += 1n;
|
|
4387
|
+
if (segment) {
|
|
4388
|
+
segment.dropped += 1n;
|
|
4389
|
+
incrementReason(segment.dropReasons, "scheduler_late");
|
|
4390
|
+
}
|
|
4391
|
+
continue;
|
|
4392
|
+
}
|
|
4393
|
+
const release = budget.tryAcquire();
|
|
4394
|
+
if (!release) {
|
|
4395
|
+
maxInFlight += 1n;
|
|
4396
|
+
if (segment) {
|
|
4397
|
+
segment.dropped += 1n;
|
|
4398
|
+
incrementReason(segment.dropReasons, "max_in_flight");
|
|
4399
|
+
}
|
|
4400
|
+
continue;
|
|
4401
|
+
}
|
|
4402
|
+
if (segment)
|
|
4403
|
+
segment.started += 1n;
|
|
4404
|
+
const instanceInfo = nextInstanceInfo();
|
|
4405
|
+
let task;
|
|
4406
|
+
task = (async () => {
|
|
4407
|
+
if (segment)
|
|
4408
|
+
telemetry?.recordStartLag(segment, process.hrtime.bigint() - deadlineNs);
|
|
4409
|
+
await runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, ordinal);
|
|
4410
|
+
})().catch((error) => {
|
|
4411
|
+
executionError ?? (executionError = error);
|
|
4412
|
+
}).finally(() => {
|
|
4413
|
+
if (segment)
|
|
4414
|
+
segment.completed += 1n;
|
|
4415
|
+
release();
|
|
4416
|
+
active.delete(task);
|
|
4417
|
+
});
|
|
4418
|
+
active.add(task);
|
|
4419
|
+
}
|
|
4420
|
+
while (active.size > 0) {
|
|
4421
|
+
await Promise.race(active);
|
|
4422
|
+
}
|
|
4423
|
+
if (segment) {
|
|
4424
|
+
segment.unreached = segment.planned > segment.due ? segment.planned - segment.due : 0n;
|
|
4425
|
+
segment.accountingComplete = segment.due + segment.unreached === segment.planned
|
|
4426
|
+
&& segment.started + segment.dropped === segment.due
|
|
4427
|
+
&& segment.completed === segment.started;
|
|
4428
|
+
}
|
|
4429
|
+
if (telemetry && segment) {
|
|
4430
|
+
telemetry.recordWarning("scheduler_late", segment, schedulerLate);
|
|
4431
|
+
telemetry.recordWarning("max_in_flight", segment, maxInFlight);
|
|
4432
|
+
}
|
|
4433
|
+
if (schedulerLate > 0n) {
|
|
4434
|
+
logger.warn(`Load Engine V2 warning scheduler_late: dropped ${schedulerLate.toString()} overdue arrivals for scenario ${scenarioName}.`);
|
|
4435
|
+
}
|
|
4436
|
+
if (maxInFlight > 0n) {
|
|
4437
|
+
logger.warn(`Load Engine V2 warning max_in_flight: dropped ${maxInFlight.toString()} arrivals for scenario ${scenarioName}; the process limit is ${budget.maxInFlight}.`);
|
|
4438
|
+
}
|
|
4439
|
+
if (executionError !== undefined) {
|
|
4440
|
+
throw executionError;
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
async function delayUntilMonotonicDeadline(deadlineNs, cancellationToken) {
|
|
4444
|
+
while (!cancellationToken.aborted) {
|
|
4445
|
+
const remainingNs = deadlineNs - process.hrtime.bigint();
|
|
4446
|
+
if (remainingNs <= 0n) {
|
|
4447
|
+
return;
|
|
4448
|
+
}
|
|
4449
|
+
const remainingMs = Number((remainingNs + 999999n) / 1000000n);
|
|
4450
|
+
await delayWithAbort(Math.max(1, Math.min(remainingMs, 50)), cancellationToken);
|
|
4451
|
+
}
|
|
4452
|
+
}
|
|
4453
|
+
function secondsToNanoseconds(seconds, name) {
|
|
4454
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
4455
|
+
throw new RangeError(`${name} must be greater than zero.`);
|
|
4456
|
+
}
|
|
4457
|
+
const nanoseconds = Math.trunc(seconds * 1000000000);
|
|
4458
|
+
if (!Number.isSafeInteger(nanoseconds) || nanoseconds <= 0) {
|
|
4459
|
+
throw new RangeError(`${name} is outside the supported nanosecond range.`);
|
|
4460
|
+
}
|
|
4461
|
+
return BigInt(nanoseconds);
|
|
4462
|
+
}
|
|
4463
|
+
function buildV2RampTolerances(offsets, durationNs) {
|
|
4464
|
+
return offsets.map((offset, index) => {
|
|
4465
|
+
const quantum = offsets.length === 1
|
|
4466
|
+
? durationNs
|
|
4467
|
+
: index === 0
|
|
4468
|
+
? offsets[1] - offset
|
|
4469
|
+
: offset - offsets[index - 1];
|
|
4470
|
+
return minBigInt(100000000n, maxBigInt(2000000n, maxBigInt(1n, quantum) * 4n));
|
|
4471
|
+
});
|
|
4472
|
+
}
|
|
4473
|
+
function minBigInt(left, right) {
|
|
4474
|
+
return left < right ? left : right;
|
|
4475
|
+
}
|
|
4476
|
+
function maxBigInt(left, right) {
|
|
4477
|
+
return left > right ? left : right;
|
|
4478
|
+
}
|
|
3749
4479
|
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;
|
|
4480
|
+
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
4481
|
const scenarioStartedMs = Date.now();
|
|
3752
4482
|
const scenarioContextData = {};
|
|
3753
4483
|
const registeredMetrics = [];
|
|
3754
4484
|
const runtime = ensureScenarioRuntime(scenarioRuntimes, scenario.name);
|
|
3755
|
-
const accumulator = new ScenarioStatsAccumulator(scenario.name, scenarioIndex);
|
|
4485
|
+
const accumulator = new ScenarioStatsAccumulator(scenario.name, scenarioIndex, options.loadEngineContractVersion === 2);
|
|
3756
4486
|
scenarioAccumulators.set(scenario.name, accumulator);
|
|
3757
4487
|
const scenarioAbortController = new AbortController();
|
|
3758
4488
|
const scenarioCancellationToken = combineAbortSignals(testAbortController.signal, scenarioAbortController.signal);
|
|
@@ -3761,12 +4491,25 @@ async function executeScenarioRuntime(args) {
|
|
|
3761
4491
|
? Date.now() + Math.trunc(scenarioCompletionTimeoutSeconds * 1000)
|
|
3762
4492
|
: Number.POSITIVE_INFINITY;
|
|
3763
4493
|
const scenarioPartition = attachScenarioPartitionAliases({
|
|
3764
|
-
number: 0,
|
|
3765
|
-
count: 1
|
|
4494
|
+
number: options.loadEngineContractVersion === 2 ? Math.max(Math.trunc(options.clusterShardIndex ?? 0), 0) : 0,
|
|
4495
|
+
count: options.loadEngineContractVersion === 2 ? Math.max(Math.trunc(options.clusterShardCount ?? 1), 1) : 1
|
|
3766
4496
|
});
|
|
4497
|
+
const trafficMixV2 = options.loadEngineContractVersion === 2
|
|
4498
|
+
? scenario.__loadStrikeTrafficMixV2Metadata()
|
|
4499
|
+
: undefined;
|
|
4500
|
+
if (trafficMixV2) {
|
|
4501
|
+
const expectedSeedId = (0, load_engine_v2_js_1.buildLoadEngineV2TrafficMixSeedId)(trafficMixV2.declarationIndex, trafficMixV2.name);
|
|
4502
|
+
if (trafficMixV2.seedId !== expectedSeedId) {
|
|
4503
|
+
throw new Error(`Load Engine V2 traffic-mix seed metadata differs for scenario ${scenario.name}.`);
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
3767
4506
|
let stopScenario = false;
|
|
3768
4507
|
let invocationNumber = 0;
|
|
3769
4508
|
let instanceCounter = 0;
|
|
4509
|
+
let nextV1ObservationOrdinal = 0n;
|
|
4510
|
+
let nextV2FallbackOrdinal = BigInt(scenarioPartition.number);
|
|
4511
|
+
let nextObservationStepSortIndex = 0;
|
|
4512
|
+
const observationStepSortIndexes = new Map();
|
|
3770
4513
|
const shouldStopNow = () => stopTestState.value
|
|
3771
4514
|
|| stopScenario
|
|
3772
4515
|
|| scenarioCancellationToken.aborted
|
|
@@ -3800,7 +4543,7 @@ async function executeScenarioRuntime(args) {
|
|
|
3800
4543
|
runtime.maxLatencyMs = Math.max(runtime.maxLatencyMs, latencyMs);
|
|
3801
4544
|
accumulator.recordScenario(reply, observedLatencyMs);
|
|
3802
4545
|
};
|
|
3803
|
-
const recordStepReply = (stepName, reply, observedLatencyMs) => {
|
|
4546
|
+
const recordStepReply = (stepName, reply, observedLatencyMs, sortIndex) => {
|
|
3804
4547
|
const key = `${scenario.name}::${stepName}`;
|
|
3805
4548
|
const stepRuntime = ensureStepRuntime(stepRuntimes, key, scenario.name, stepName);
|
|
3806
4549
|
if (reply.isSuccess) {
|
|
@@ -3823,11 +4566,35 @@ async function executeScenarioRuntime(args) {
|
|
|
3823
4566
|
stepRuntime.maxLatencyMs = Math.max(stepRuntime.maxLatencyMs, latencyMs);
|
|
3824
4567
|
const statusCode = normalizeStatusCode(reply.statusCode, reply.isSuccess);
|
|
3825
4568
|
stepRuntime.statusCodes[statusCode] = (stepRuntime.statusCodes[statusCode] ?? 0) + 1;
|
|
3826
|
-
accumulator.recordStep(stepName, reply, observedLatencyMs);
|
|
4569
|
+
return accumulator.recordStep(stepName, reply, observedLatencyMs, sortIndex);
|
|
4570
|
+
};
|
|
4571
|
+
const resolveObservationStepSortIndex = (stepName) => {
|
|
4572
|
+
const existing = observationStepSortIndexes.get(stepName);
|
|
4573
|
+
if (existing !== undefined) {
|
|
4574
|
+
return existing;
|
|
4575
|
+
}
|
|
4576
|
+
nextObservationStepSortIndex += 1;
|
|
4577
|
+
observationStepSortIndexes.set(stepName, nextObservationStepSortIndex);
|
|
4578
|
+
return nextObservationStepSortIndex;
|
|
4579
|
+
};
|
|
4580
|
+
const nextObservationOrdinal = (explicit) => {
|
|
4581
|
+
if (explicit !== undefined) {
|
|
4582
|
+
return explicit;
|
|
4583
|
+
}
|
|
4584
|
+
if (options.loadEngineContractVersion === 2) {
|
|
4585
|
+
const ordinal = nextV2FallbackOrdinal;
|
|
4586
|
+
nextV2FallbackOrdinal += BigInt(scenarioPartition.count);
|
|
4587
|
+
return ordinal;
|
|
4588
|
+
}
|
|
4589
|
+
const ordinal = nextV1ObservationOrdinal;
|
|
4590
|
+
nextV1ObservationOrdinal += 1n;
|
|
4591
|
+
return ordinal;
|
|
3827
4592
|
};
|
|
3828
4593
|
const runSingleInvocation = async (operation, instanceData, instanceNumber, instanceId, recordScenarioResult) => {
|
|
3829
4594
|
invocationNumber += 1;
|
|
3830
4595
|
const runtimeRandom = createRuntimeRandom();
|
|
4596
|
+
const attemptSteps = [];
|
|
4597
|
+
const recordedSteps = [];
|
|
3831
4598
|
const context = {
|
|
3832
4599
|
scenarioName: scenario.name,
|
|
3833
4600
|
data: scenarioContextData,
|
|
@@ -3856,7 +4623,12 @@ async function executeScenarioRuntime(args) {
|
|
|
3856
4623
|
testAbortController.abort(stopTestState.reason);
|
|
3857
4624
|
},
|
|
3858
4625
|
recordStep: (stepName, reply, observedLatencyMs) => {
|
|
3859
|
-
|
|
4626
|
+
const sortIndex = resolveObservationStepSortIndex(stepName);
|
|
4627
|
+
recordedSteps.push({ stepName, reply, observedLatencyMs, sortIndex });
|
|
4628
|
+
return sortIndex;
|
|
4629
|
+
},
|
|
4630
|
+
recordStepObservation: (observation) => {
|
|
4631
|
+
attemptSteps.push(observation);
|
|
3860
4632
|
},
|
|
3861
4633
|
shouldStopScenario: () => stopScenario || scenarioCancellationToken.aborted,
|
|
3862
4634
|
shouldStopTest: () => stopTestState.value || scenarioCancellationToken.aborted,
|
|
@@ -3864,28 +4636,100 @@ async function executeScenarioRuntime(args) {
|
|
|
3864
4636
|
invokeAfterStep: async (stepName, reply) => invokeAfterStep(policies, scenario.name, stepName, reply)
|
|
3865
4637
|
};
|
|
3866
4638
|
attachScenarioContextAliases(context);
|
|
3867
|
-
const
|
|
3868
|
-
const
|
|
3869
|
-
|
|
4639
|
+
const startedUtcNs = (0, iteration_observations_js_1.utcNowNs)();
|
|
4640
|
+
const startedAtNs = process.hrtime.bigint();
|
|
4641
|
+
let policyFailure;
|
|
4642
|
+
let reply;
|
|
4643
|
+
try {
|
|
4644
|
+
reply = await executeScenarioInvocation(scenario, context, operation);
|
|
4645
|
+
}
|
|
4646
|
+
catch (error) {
|
|
4647
|
+
if (!(error instanceof RuntimePolicyCallbackError)) {
|
|
4648
|
+
throw error;
|
|
4649
|
+
}
|
|
4650
|
+
policyFailure = error;
|
|
4651
|
+
reply = LoadStrikeResponse.fail("runtime_policy_error", "", 0);
|
|
4652
|
+
}
|
|
4653
|
+
const observedLatencyNs = maxBigInt(process.hrtime.bigint() - startedAtNs, 0n);
|
|
4654
|
+
const completedUtcNs = startedUtcNs + observedLatencyNs;
|
|
4655
|
+
const observedLatencyMs = Number(observedLatencyNs) / 1000000;
|
|
3870
4656
|
if (recordScenarioResult) {
|
|
3871
4657
|
recordScenarioReply(reply, observedLatencyMs);
|
|
3872
4658
|
}
|
|
3873
|
-
return {
|
|
4659
|
+
return {
|
|
4660
|
+
reply,
|
|
4661
|
+
observedLatencyMs,
|
|
4662
|
+
startedUtcNs,
|
|
4663
|
+
completedUtcNs,
|
|
4664
|
+
observedLatencyUs: observedLatencyNs / 1000n,
|
|
4665
|
+
reportedLatencyUs: normalizeRawObservationLatencyMicroseconds(resolveRecordedLatency(reply.customLatencyMs, observedLatencyMs)),
|
|
4666
|
+
steps: attemptSteps,
|
|
4667
|
+
recordedSteps,
|
|
4668
|
+
...(policyFailure ? { policyFailure } : {})
|
|
4669
|
+
};
|
|
4670
|
+
};
|
|
4671
|
+
const captureAttemptObservation = (operation, globalOrdinal, attemptIndex, isFinalAttempt, attempt, simulationIndex, simulationKind, iterationId, globalSecondaryOrdinal = 0n) => {
|
|
4672
|
+
if (!iterationObservationReporter?.enabled) {
|
|
4673
|
+
return;
|
|
4674
|
+
}
|
|
4675
|
+
iterationObservationReporter.capture((0, iteration_observations_js_1.createIterationObservation)({
|
|
4676
|
+
runId: iterationObservationRunId,
|
|
4677
|
+
sessionId: testInfo.sessionId,
|
|
4678
|
+
resultOwnerId: iterationObservationResultOwnerId,
|
|
4679
|
+
processGroup: iterationObservationProcessGroup,
|
|
4680
|
+
scenarioName: scenario.name,
|
|
4681
|
+
scenarioIndex,
|
|
4682
|
+
simulationIndex,
|
|
4683
|
+
simulationKind,
|
|
4684
|
+
phase: operation === "WarmUp" ? "warmup" : "bombing",
|
|
4685
|
+
globalOrdinal64: globalOrdinal,
|
|
4686
|
+
globalSecondaryOrdinal64: globalSecondaryOrdinal,
|
|
4687
|
+
...(iterationId ? { iterationId } : {}),
|
|
4688
|
+
shardIndex: scenarioPartition.number,
|
|
4689
|
+
shardCount: scenarioPartition.count,
|
|
4690
|
+
attemptIndex,
|
|
4691
|
+
isFinalAttempt,
|
|
4692
|
+
startedUtcNs: attempt.startedUtcNs,
|
|
4693
|
+
completedUtcNs: attempt.completedUtcNs,
|
|
4694
|
+
observedLatencyUs64: attempt.observedLatencyUs,
|
|
4695
|
+
reportedLatencyUs64: attempt.reportedLatencyUs,
|
|
4696
|
+
isSuccess: attempt.reply.isSuccess,
|
|
4697
|
+
statusCode: normalizeStatusCode(attempt.reply.statusCode, attempt.reply.isSuccess),
|
|
4698
|
+
sizeBytes64: normalizeHistogramInteger(Math.max(toNumber(attempt.reply.sizeBytes), 0), "Scenario response bytes"),
|
|
4699
|
+
steps: attempt.steps
|
|
4700
|
+
}));
|
|
3874
4701
|
};
|
|
3875
|
-
const runBombingInvocation = async (instanceData, instanceNumber, instanceId) => {
|
|
4702
|
+
const runBombingInvocation = async (instanceData, instanceNumber, instanceId, explicitGlobalOrdinal, simulationIndex = -1, simulationKind = "SingleInvocation", explicitSecondaryOrdinal = 0n) => {
|
|
3876
4703
|
if (shouldStopNow()) {
|
|
3877
4704
|
return;
|
|
3878
4705
|
}
|
|
4706
|
+
const globalOrdinal = nextObservationOrdinal(explicitGlobalOrdinal);
|
|
4707
|
+
const identityKind = canonicalLoadEngineV2InvocationIdentityKind(simulationKind);
|
|
4708
|
+
const iterationId = options.loadEngineContractVersion === 2
|
|
4709
|
+
&& explicitGlobalOrdinal !== undefined
|
|
4710
|
+
&& identityKind
|
|
4711
|
+
? (0, cluster_js_1.buildLoadEngineV2GlobalInvocationId)(iterationObservationRunId, scenarioIndex, simulationIndex, identityKind, globalOrdinal, explicitSecondaryOrdinal)
|
|
4712
|
+
: undefined;
|
|
3879
4713
|
let attempts = 0;
|
|
3880
4714
|
const maxAttempts = 1 + (scenario.shouldRestartIterationOnFail() ? restartIterationMaxAttempts : 0);
|
|
3881
4715
|
while (attempts < maxAttempts && !shouldStopNow()) {
|
|
3882
4716
|
attempts += 1;
|
|
3883
4717
|
const attempt = await runSingleInvocation("Bombing", instanceData, instanceNumber, instanceId, false);
|
|
3884
|
-
const shouldRetry = !attempt.
|
|
4718
|
+
const shouldRetry = !attempt.policyFailure
|
|
4719
|
+
&& !attempt.reply.isSuccess
|
|
3885
4720
|
&& scenario.shouldRestartIterationOnFail()
|
|
3886
4721
|
&& attempts < maxAttempts
|
|
3887
4722
|
&& !shouldStopNow();
|
|
4723
|
+
captureAttemptObservation("Bombing", globalOrdinal, attempts - 1, !shouldRetry, attempt, simulationIndex, simulationKind, iterationId, explicitSecondaryOrdinal);
|
|
4724
|
+
if (attempt.policyFailure) {
|
|
4725
|
+
stopScenario = true;
|
|
4726
|
+
scenarioAbortController.abort(attempt.policyFailure);
|
|
4727
|
+
throw attempt.policyFailure;
|
|
4728
|
+
}
|
|
3888
4729
|
if (!shouldRetry) {
|
|
4730
|
+
for (const step of attempt.recordedSteps) {
|
|
4731
|
+
recordStepReply(step.stepName, step.reply, step.observedLatencyMs, step.sortIndex);
|
|
4732
|
+
}
|
|
3889
4733
|
recordScenarioReply(attempt.reply, attempt.observedLatencyMs);
|
|
3890
4734
|
if (scenario.getMaxFailCount() > 0 && runtime.allFailCount >= scenario.getMaxFailCount()) {
|
|
3891
4735
|
stopScenario = true;
|
|
@@ -3905,30 +4749,287 @@ async function executeScenarioRuntime(args) {
|
|
|
3905
4749
|
const endMs = Date.now() + Math.trunc(warmUpDurationSeconds * 1000);
|
|
3906
4750
|
const instanceInfo = nextInstanceInfo();
|
|
3907
4751
|
while (Date.now() < endMs && !shouldStopNow()) {
|
|
3908
|
-
|
|
4752
|
+
const globalOrdinal = nextObservationOrdinal();
|
|
4753
|
+
const attempt = await runSingleInvocation("WarmUp", instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, false);
|
|
4754
|
+
captureAttemptObservation("WarmUp", globalOrdinal, 0, true, attempt, -1, "SingleInvocation");
|
|
4755
|
+
if (attempt.policyFailure) {
|
|
4756
|
+
stopScenario = true;
|
|
4757
|
+
scenarioAbortController.abort(attempt.policyFailure);
|
|
4758
|
+
throw attempt.policyFailure;
|
|
4759
|
+
}
|
|
3909
4760
|
}
|
|
3910
4761
|
};
|
|
3911
|
-
const
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
const
|
|
3916
|
-
const
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
4762
|
+
const executeV2TimedConstant = async (copies, durationNs, ramping, runSimulationInvocation, segment) => {
|
|
4763
|
+
if (!loadEngineV2Budget) {
|
|
4764
|
+
return;
|
|
4765
|
+
}
|
|
4766
|
+
const offsets = ramping ? (0, load_engine_v2_js_1.planRampingConstantDeadlines)(copies, durationNs) : Array(copies).fill(0n);
|
|
4767
|
+
const ownedWorkerSlots = trafficMixV2
|
|
4768
|
+
? (0, load_engine_v2_js_1.loadEngineV2TrafficMixOwnedUnits)(BigInt(copies), trafficMixV2.shareWeights, trafficMixV2.laneIndex, scenarioPartition.number, scenarioPartition.count)
|
|
4769
|
+
: offsets.flatMap((_offset, workerSlot) => workerSlot % scenarioPartition.count === scenarioPartition.number
|
|
4770
|
+
? [{ laneOrdinal: BigInt(workerSlot), globalRank: BigInt(workerSlot) }]
|
|
4771
|
+
: []);
|
|
4772
|
+
const quantum = ramping ? maxBigInt(1n, durationNs / BigInt(copies)) : 1n;
|
|
4773
|
+
const toleranceNs = minBigInt(100000000n, maxBigInt(2000000n, quantum * 4n));
|
|
4774
|
+
const startNs = process.hrtime.bigint();
|
|
4775
|
+
const endNs = startNs + durationNs;
|
|
4776
|
+
const active = new Set();
|
|
4777
|
+
let schedulerLate = 0;
|
|
4778
|
+
let unavailable = 0;
|
|
4779
|
+
let executionError;
|
|
4780
|
+
if (segment) {
|
|
4781
|
+
segment.requestedWorkers = BigInt(ownedWorkerSlots.length);
|
|
4782
|
+
}
|
|
4783
|
+
for (const unit of ownedWorkerSlots) {
|
|
4784
|
+
const offsetNs = offsets[Number(unit.globalRank)];
|
|
4785
|
+
await delayUntilMonotonicDeadline(startNs + offsetNs, scenarioCancellationToken);
|
|
4786
|
+
if (shouldStopNow()) {
|
|
4787
|
+
break;
|
|
4788
|
+
}
|
|
4789
|
+
const decisionNowNs = process.hrtime.bigint();
|
|
4790
|
+
if (segment)
|
|
4791
|
+
loadEngineV2Telemetry?.recordDecisionLag(segment, decisionNowNs - (startNs + offsetNs));
|
|
4792
|
+
if (decisionNowNs - (startNs + offsetNs) > toleranceNs) {
|
|
4793
|
+
schedulerLate += 1;
|
|
4794
|
+
if (segment) {
|
|
4795
|
+
segment.unavailableWorkers += 1n;
|
|
4796
|
+
incrementReason(segment.unavailableWorkerReasons, "scheduler_late");
|
|
4797
|
+
}
|
|
4798
|
+
continue;
|
|
4799
|
+
}
|
|
4800
|
+
const release = loadEngineV2Budget.tryAcquire();
|
|
4801
|
+
if (!release) {
|
|
4802
|
+
unavailable += 1;
|
|
4803
|
+
if (segment) {
|
|
4804
|
+
segment.unavailableWorkers += 1n;
|
|
4805
|
+
incrementReason(segment.unavailableWorkerReasons, "max_in_flight");
|
|
4806
|
+
}
|
|
4807
|
+
continue;
|
|
4808
|
+
}
|
|
4809
|
+
if (segment)
|
|
4810
|
+
segment.startedWorkers += 1n;
|
|
4811
|
+
const instanceInfo = nextInstanceInfo();
|
|
4812
|
+
let task;
|
|
4813
|
+
task = (async () => {
|
|
4814
|
+
if (segment) {
|
|
4815
|
+
loadEngineV2Telemetry?.recordStartLag(segment, process.hrtime.bigint() - (startNs + offsetNs));
|
|
4816
|
+
}
|
|
4817
|
+
let completedSinceYield = 0;
|
|
4818
|
+
let workerIterationIndex = 0n;
|
|
4819
|
+
while (process.hrtime.bigint() < endNs && !shouldStopNow()) {
|
|
4820
|
+
if (segment) {
|
|
4821
|
+
segment.planned += 1n;
|
|
4822
|
+
segment.due += 1n;
|
|
4823
|
+
segment.started += 1n;
|
|
4824
|
+
}
|
|
4825
|
+
await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, unit.globalRank, workerIterationIndex);
|
|
4826
|
+
workerIterationIndex += 1n;
|
|
4827
|
+
if (segment)
|
|
4828
|
+
segment.completed += 1n;
|
|
4829
|
+
completedSinceYield += 1;
|
|
4830
|
+
if (completedSinceYield >= 64) {
|
|
4831
|
+
completedSinceYield = 0;
|
|
4832
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
})().catch((error) => {
|
|
4836
|
+
executionError ?? (executionError = error);
|
|
4837
|
+
}).finally(() => {
|
|
4838
|
+
release();
|
|
4839
|
+
active.delete(task);
|
|
4840
|
+
});
|
|
4841
|
+
active.add(task);
|
|
4842
|
+
}
|
|
4843
|
+
while (active.size > 0) {
|
|
4844
|
+
await Promise.race(active);
|
|
4845
|
+
}
|
|
4846
|
+
if (segment) {
|
|
4847
|
+
segment.accountingComplete = segment.completed === segment.started
|
|
4848
|
+
&& segment.started === segment.due
|
|
4849
|
+
&& segment.due === segment.planned
|
|
4850
|
+
&& segment.startedWorkers + segment.unavailableWorkers === segment.requestedWorkers;
|
|
4851
|
+
loadEngineV2Telemetry?.recordWarning("scheduler_late", segment, BigInt(schedulerLate));
|
|
4852
|
+
loadEngineV2Telemetry?.recordWarning("max_in_flight", segment, BigInt(unavailable));
|
|
4853
|
+
}
|
|
4854
|
+
if (schedulerLate > 0) {
|
|
4855
|
+
logger.warn(`Load Engine V2 warning scheduler_late: ${schedulerLate} constant worker slots were unavailable for scenario ${scenario.name}.`);
|
|
4856
|
+
}
|
|
4857
|
+
if (unavailable > 0) {
|
|
4858
|
+
logger.warn(`Load Engine V2 warning max_in_flight: ${unavailable} constant worker slots were unavailable for scenario ${scenario.name}.`);
|
|
4859
|
+
}
|
|
4860
|
+
if (executionError !== undefined) {
|
|
4861
|
+
throw executionError;
|
|
4862
|
+
}
|
|
4863
|
+
};
|
|
4864
|
+
const executeV2IterationsConstant = async (copies, iterations, runSimulationInvocation, segment) => {
|
|
4865
|
+
if (!loadEngineV2Budget || iterations <= 0) {
|
|
4866
|
+
return;
|
|
4867
|
+
}
|
|
4868
|
+
let activeShardCount;
|
|
4869
|
+
let ownedWorkerSlots;
|
|
4870
|
+
let ownedIterations;
|
|
4871
|
+
if (trafficMixV2) {
|
|
4872
|
+
const laneCopies = (0, load_engine_v2_js_1.loadEngineV2TrafficMixLaneUnitCount)(BigInt(copies), trafficMixV2.shareWeights, trafficMixV2.laneIndex);
|
|
4873
|
+
const laneIterations = (0, load_engine_v2_js_1.loadEngineV2TrafficMixLaneUnitCount)(BigInt(iterations), trafficMixV2.shareWeights, trafficMixV2.laneIndex);
|
|
4874
|
+
if (laneIterations === 0n) {
|
|
4875
|
+
if (segment) {
|
|
4876
|
+
segment.requestedWorkers = 0n;
|
|
4877
|
+
segment.accountingComplete = true;
|
|
4878
|
+
}
|
|
4879
|
+
return;
|
|
4880
|
+
}
|
|
4881
|
+
if (laneCopies === 0n) {
|
|
4882
|
+
throw new Error("Load Engine V2 traffic-mix lane owns iterations but no constant worker slot.");
|
|
4883
|
+
}
|
|
4884
|
+
activeShardCount = Math.min(scenarioPartition.count, Number(laneCopies), Number(laneIterations));
|
|
4885
|
+
if (scenarioPartition.number >= activeShardCount) {
|
|
4886
|
+
if (segment) {
|
|
4887
|
+
segment.requestedWorkers = 0n;
|
|
4888
|
+
segment.accountingComplete = true;
|
|
4889
|
+
}
|
|
4890
|
+
return;
|
|
4891
|
+
}
|
|
4892
|
+
ownedWorkerSlots = (0, load_engine_v2_js_1.loadEngineV2TrafficMixOwnedUnits)(BigInt(copies), trafficMixV2.shareWeights, trafficMixV2.laneIndex, scenarioPartition.number, activeShardCount);
|
|
4893
|
+
ownedIterations = (0, load_engine_v2_js_1.loadEngineV2TrafficMixOwnedUnits)(BigInt(iterations), trafficMixV2.shareWeights, trafficMixV2.laneIndex, scenarioPartition.number, activeShardCount);
|
|
4894
|
+
}
|
|
4895
|
+
else {
|
|
4896
|
+
activeShardCount = Math.min(scenarioPartition.count, copies, iterations);
|
|
4897
|
+
if (scenarioPartition.number >= activeShardCount) {
|
|
4898
|
+
return;
|
|
4899
|
+
}
|
|
4900
|
+
ownedWorkerSlots = Array.from({ length: copies }, (_value, slot) => slot)
|
|
4901
|
+
.filter((slot) => slot % activeShardCount === scenarioPartition.number)
|
|
4902
|
+
.map((slot) => ({ laneOrdinal: BigInt(slot), globalRank: BigInt(slot) }));
|
|
4903
|
+
ownedIterations = Array.from({ length: iterations }, (_value, ordinal) => ordinal)
|
|
4904
|
+
.filter((ordinal) => ordinal % activeShardCount === scenarioPartition.number)
|
|
4905
|
+
.map((ordinal) => ({ laneOrdinal: BigInt(ordinal), globalRank: BigInt(ordinal) }));
|
|
4906
|
+
}
|
|
4907
|
+
if (scenarioPartition.number >= activeShardCount) {
|
|
4908
|
+
return;
|
|
4909
|
+
}
|
|
4910
|
+
const targetWorkers = Math.min(ownedWorkerSlots.length, ownedIterations.length);
|
|
4911
|
+
if (targetWorkers <= 0) {
|
|
4912
|
+
if (segment) {
|
|
4913
|
+
segment.requestedWorkers = 0n;
|
|
4914
|
+
segment.accountingComplete = true;
|
|
4915
|
+
}
|
|
4916
|
+
return;
|
|
4917
|
+
}
|
|
4918
|
+
if (segment) {
|
|
4919
|
+
segment.planned = BigInt(ownedIterations.length);
|
|
4920
|
+
segment.requestedWorkers = BigInt(targetWorkers);
|
|
4921
|
+
}
|
|
4922
|
+
const releases = [];
|
|
4923
|
+
let firstRelease;
|
|
4924
|
+
while (!firstRelease && !shouldStopNow()) {
|
|
4925
|
+
firstRelease = loadEngineV2Budget.tryAcquire();
|
|
4926
|
+
if (!firstRelease) {
|
|
4927
|
+
await delayWithAbort(1, scenarioCancellationToken);
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
if (!firstRelease) {
|
|
4931
|
+
if (segment) {
|
|
4932
|
+
segment.unreached = segment.planned;
|
|
4933
|
+
segment.unavailableWorkers = segment.requestedWorkers;
|
|
4934
|
+
if (segment.unavailableWorkers > 0n) {
|
|
4935
|
+
incrementReason(segment.unavailableWorkerReasons, "cancelled", segment.unavailableWorkers);
|
|
4936
|
+
}
|
|
4937
|
+
segment.accountingComplete = true;
|
|
4938
|
+
}
|
|
4939
|
+
return;
|
|
4940
|
+
}
|
|
4941
|
+
releases.push(firstRelease);
|
|
4942
|
+
for (let worker = 1; worker < targetWorkers; worker += 1) {
|
|
4943
|
+
const release = loadEngineV2Budget.tryAcquire();
|
|
4944
|
+
if (release) {
|
|
4945
|
+
releases.push(release);
|
|
4946
|
+
}
|
|
4947
|
+
}
|
|
4948
|
+
if (releases.length < targetWorkers) {
|
|
4949
|
+
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.`);
|
|
4950
|
+
}
|
|
4951
|
+
if (segment) {
|
|
4952
|
+
segment.startedWorkers = BigInt(releases.length);
|
|
4953
|
+
segment.unavailableWorkers = BigInt(targetWorkers - releases.length);
|
|
4954
|
+
if (segment.unavailableWorkers > 0n) {
|
|
4955
|
+
incrementReason(segment.unavailableWorkerReasons, "max_in_flight", segment.unavailableWorkers);
|
|
4956
|
+
loadEngineV2Telemetry?.recordWarning("max_in_flight", segment, segment.unavailableWorkers);
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
let nextIterationIndex = 0;
|
|
4960
|
+
const tasks = releases.map(async (release) => {
|
|
4961
|
+
const instanceInfo = nextInstanceInfo();
|
|
4962
|
+
try {
|
|
4963
|
+
while (!shouldStopNow()) {
|
|
4964
|
+
const unit = ownedIterations[nextIterationIndex];
|
|
4965
|
+
nextIterationIndex += 1;
|
|
4966
|
+
if (!unit) {
|
|
4967
|
+
break;
|
|
4968
|
+
}
|
|
4969
|
+
if (segment) {
|
|
4970
|
+
segment.due += 1n;
|
|
4971
|
+
segment.started += 1n;
|
|
4972
|
+
}
|
|
4973
|
+
await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId, unit.globalRank, 0n);
|
|
4974
|
+
if (segment)
|
|
4975
|
+
segment.completed += 1n;
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
finally {
|
|
4979
|
+
release();
|
|
4980
|
+
}
|
|
4981
|
+
});
|
|
4982
|
+
await waitForScenarioTasks(tasks, scenario.name, scenarioCompletionTimeoutSeconds, logger, scenarioCancellationToken);
|
|
4983
|
+
if (segment) {
|
|
4984
|
+
segment.unreached = segment.planned > segment.due ? segment.planned - segment.due : 0n;
|
|
4985
|
+
segment.accountingComplete = segment.due + segment.unreached === segment.planned
|
|
4986
|
+
&& segment.started === segment.due
|
|
4987
|
+
&& segment.completed === segment.started
|
|
4988
|
+
&& segment.startedWorkers + segment.unavailableWorkers === segment.requestedWorkers;
|
|
4989
|
+
}
|
|
4990
|
+
};
|
|
4991
|
+
const executeSimulationAsync = async (simulation, simulationIndex) => {
|
|
4992
|
+
const descriptor = trafficMixV2
|
|
4993
|
+
? trafficMixV2.originalSimulations[simulationIndex]
|
|
4994
|
+
: simulation;
|
|
4995
|
+
if (!descriptor) {
|
|
4996
|
+
throw new Error(`Load Engine V2 traffic-mix phase ${simulationIndex} is missing its global descriptor.`);
|
|
4997
|
+
}
|
|
4998
|
+
const kind = String(descriptor.Kind ?? "");
|
|
4999
|
+
const simulationKind = kind || "SingleInvocation";
|
|
5000
|
+
const runSimulationInvocation = (instanceData, instanceNumber, instanceId, explicitGlobalOrdinal, explicitSecondaryOrdinal = 0n) => runBombingInvocation(instanceData, instanceNumber, instanceId, explicitGlobalOrdinal, simulationIndex, simulationKind, explicitSecondaryOrdinal);
|
|
5001
|
+
if (options.loadEngineContractVersion === 2) {
|
|
5002
|
+
nextV2FallbackOrdinal = BigInt(scenarioPartition.number);
|
|
5003
|
+
}
|
|
5004
|
+
const weight = trafficMixV2 ? 1 : Math.max(scenario.getWeight(), 1);
|
|
5005
|
+
const rate = applyScenarioWeight(toInt(descriptor.Rate), weight);
|
|
5006
|
+
const minRate = applyScenarioWeight(toInt(descriptor.MinRate), weight);
|
|
5007
|
+
const maxRate = applyScenarioWeight(toInt(descriptor.MaxRate), weight);
|
|
5008
|
+
const copies = Math.max(applyScenarioWeight(Math.max(toInt(descriptor.Copies), 1), weight), 1);
|
|
5009
|
+
const iterations = applyScenarioWeight(Math.max(toInt(descriptor.Iterations), 0), weight);
|
|
5010
|
+
const intervalMs = Math.max(Math.trunc(Math.max(toNumber(descriptor.IntervalSeconds), 0) * 1000), 0);
|
|
5011
|
+
const duringMs = Math.max(Math.trunc(Math.max(toNumber(descriptor.DuringSeconds), 0) * 1000), 0);
|
|
5012
|
+
accumulator.setLoadSimulation(kind, resolveLoadSimulationValue(descriptor, weight));
|
|
3922
5013
|
accumulator.setCurrentOperation("Bombing");
|
|
5014
|
+
const schedulerSegment = options.loadEngineContractVersion === 2
|
|
5015
|
+
? loadEngineV2Telemetry?.createSegment(scenario.name, scenarioIndex, simulationIndex, kind, scenarioPartition.number, scenarioPartition.count)
|
|
5016
|
+
: undefined;
|
|
5017
|
+
const trafficMixOwnedOrdinals = (total) => trafficMixV2
|
|
5018
|
+
? (0, load_engine_v2_js_1.loadEngineV2TrafficMixOwnedUnits)(total, trafficMixV2.shareWeights, trafficMixV2.laneIndex, scenarioPartition.number, scenarioPartition.count).map((unit) => unit.globalRank)
|
|
5019
|
+
: undefined;
|
|
3923
5020
|
if (kind === "KeepConstant") {
|
|
3924
5021
|
if (duringMs <= 0) {
|
|
3925
5022
|
return;
|
|
3926
5023
|
}
|
|
5024
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5025
|
+
await executeV2TimedConstant(copies, secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "KeepConstant duration"), false, runSimulationInvocation, schedulerSegment);
|
|
5026
|
+
return;
|
|
5027
|
+
}
|
|
3927
5028
|
const endMs = Date.now() + duringMs;
|
|
3928
5029
|
const tasks = Array.from({ length: copies }, async () => {
|
|
3929
5030
|
const instanceInfo = nextInstanceInfo();
|
|
3930
5031
|
while (Date.now() < endMs && !shouldStopNow()) {
|
|
3931
|
-
await
|
|
5032
|
+
await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
|
|
3932
5033
|
}
|
|
3933
5034
|
});
|
|
3934
5035
|
await waitForScenarioTasks(tasks, scenario.name, scenarioCompletionTimeoutSeconds, logger, scenarioCancellationToken);
|
|
@@ -3938,6 +5039,10 @@ async function executeScenarioRuntime(args) {
|
|
|
3938
5039
|
if (duringMs <= 0) {
|
|
3939
5040
|
return;
|
|
3940
5041
|
}
|
|
5042
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5043
|
+
await executeV2TimedConstant(copies, secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "RampingConstant duration"), true, runSimulationInvocation, schedulerSegment);
|
|
5044
|
+
return;
|
|
5045
|
+
}
|
|
3941
5046
|
const tasks = [];
|
|
3942
5047
|
const endMs = Date.now() + duringMs;
|
|
3943
5048
|
const startIntervalMs = copies <= 1 ? 0 : Math.max(Math.trunc(duringMs / copies), 0);
|
|
@@ -3945,7 +5050,7 @@ async function executeScenarioRuntime(args) {
|
|
|
3945
5050
|
const instanceInfo = nextInstanceInfo();
|
|
3946
5051
|
tasks.push((async () => {
|
|
3947
5052
|
while (Date.now() < endMs && !shouldStopNow()) {
|
|
3948
|
-
await
|
|
5053
|
+
await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
|
|
3949
5054
|
}
|
|
3950
5055
|
})());
|
|
3951
5056
|
if (copy < copies && startIntervalMs > 0) {
|
|
@@ -3959,12 +5064,34 @@ async function executeScenarioRuntime(args) {
|
|
|
3959
5064
|
if (duringMs <= 0 || rate <= 0 || intervalMs <= 0) {
|
|
3960
5065
|
return;
|
|
3961
5066
|
}
|
|
5067
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5068
|
+
const intervalNs = secondsToNanoseconds(toNumber(descriptor.IntervalSeconds), "Inject interval");
|
|
5069
|
+
const durationNs = secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "Inject duration");
|
|
5070
|
+
await executeV2FixedArrivals({
|
|
5071
|
+
rate,
|
|
5072
|
+
intervalNs,
|
|
5073
|
+
totalArrivals: (0, load_engine_v2_js_1.loadEngineV2FixedArrivalCount)(rate, intervalNs, 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((0, load_engine_v2_js_1.loadEngineV2FixedArrivalCount)(rate, intervalNs, durationNs)),
|
|
5084
|
+
telemetry: loadEngineV2Telemetry,
|
|
5085
|
+
segment: schedulerSegment
|
|
5086
|
+
});
|
|
5087
|
+
return;
|
|
5088
|
+
}
|
|
3962
5089
|
const pending = [];
|
|
3963
5090
|
const endMs = Date.now() + duringMs;
|
|
3964
5091
|
while (Date.now() < endMs && !shouldStopNow()) {
|
|
3965
5092
|
for (let index = 0; index < rate; index += 1) {
|
|
3966
5093
|
const instanceInfo = nextInstanceInfo();
|
|
3967
|
-
pending.push(
|
|
5094
|
+
pending.push(runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
|
|
3968
5095
|
}
|
|
3969
5096
|
await delayWithAbort(intervalMs, scenarioCancellationToken);
|
|
3970
5097
|
}
|
|
@@ -3975,6 +5102,31 @@ async function executeScenarioRuntime(args) {
|
|
|
3975
5102
|
if (duringMs <= 0 || rate <= 0 || intervalMs <= 0) {
|
|
3976
5103
|
return;
|
|
3977
5104
|
}
|
|
5105
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5106
|
+
const intervalNs = secondsToNanoseconds(toNumber(descriptor.IntervalSeconds), "RampingInject interval");
|
|
5107
|
+
const durationNs = secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "RampingInject duration");
|
|
5108
|
+
const offsets = (0, load_engine_v2_js_1.planRampingInjectionDeadlines)(rate, intervalNs, durationNs);
|
|
5109
|
+
await executeV2FixedArrivals({
|
|
5110
|
+
rate,
|
|
5111
|
+
intervalNs,
|
|
5112
|
+
totalArrivals: BigInt(offsets.length),
|
|
5113
|
+
deadlineOffsetsNs: offsets,
|
|
5114
|
+
tolerancesNs: buildV2RampTolerances(offsets, durationNs),
|
|
5115
|
+
budget: loadEngineV2Budget,
|
|
5116
|
+
cancellationToken: scenarioCancellationToken,
|
|
5117
|
+
shouldStopNow,
|
|
5118
|
+
nextInstanceInfo,
|
|
5119
|
+
runBombingInvocation: runSimulationInvocation,
|
|
5120
|
+
logger,
|
|
5121
|
+
scenarioName: scenario.name,
|
|
5122
|
+
shardIndex: scenarioPartition.number,
|
|
5123
|
+
shardCount: scenarioPartition.count,
|
|
5124
|
+
ownedOrdinals: trafficMixOwnedOrdinals(BigInt(offsets.length)),
|
|
5125
|
+
telemetry: loadEngineV2Telemetry,
|
|
5126
|
+
segment: schedulerSegment
|
|
5127
|
+
});
|
|
5128
|
+
return;
|
|
5129
|
+
}
|
|
3978
5130
|
const pending = [];
|
|
3979
5131
|
const startedAtMs = Date.now();
|
|
3980
5132
|
const endMs = startedAtMs + duringMs;
|
|
@@ -3984,7 +5136,7 @@ async function executeScenarioRuntime(args) {
|
|
|
3984
5136
|
const currentRate = Math.max(1, Math.ceil(rate * progress));
|
|
3985
5137
|
for (let index = 0; index < currentRate; index += 1) {
|
|
3986
5138
|
const instanceInfo = nextInstanceInfo();
|
|
3987
|
-
pending.push(
|
|
5139
|
+
pending.push(runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
|
|
3988
5140
|
}
|
|
3989
5141
|
await delayWithAbort(intervalMs, scenarioCancellationToken);
|
|
3990
5142
|
}
|
|
@@ -3995,6 +5147,37 @@ async function executeScenarioRuntime(args) {
|
|
|
3995
5147
|
if (duringMs <= 0 || maxRate <= 0 || intervalMs <= 0) {
|
|
3996
5148
|
return;
|
|
3997
5149
|
}
|
|
5150
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5151
|
+
const intervalNs = secondsToNanoseconds(toNumber(descriptor.IntervalSeconds), "InjectRandom interval");
|
|
5152
|
+
const durationNs = secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "InjectRandom duration");
|
|
5153
|
+
const descriptorSeed = (0, load_engine_v2_js_1.fnv1a32)(trafficMixV2
|
|
5154
|
+
? `traffic-mix\n${trafficMixV2.seedId}\n${simulationIndex}`
|
|
5155
|
+
: `${scenario.name}\n${simulationIndex}`);
|
|
5156
|
+
const rotated = ((descriptorSeed << 13) | (descriptorSeed >>> 19)) >>> 0;
|
|
5157
|
+
const seed = ((0, load_engine_v2_js_1.fnv1a32)(testInfo.sessionId) ^ rotated) >>> 0;
|
|
5158
|
+
const offsets = (0, load_engine_v2_js_1.planRandomInjectionDeadlines)(Math.max(0, minRate), maxRate, intervalNs, durationNs, seed);
|
|
5159
|
+
const tolerance = (0, load_engine_v2_js_1.loadEngineV2LatenessToleranceNs)(Math.max(1, maxRate), intervalNs);
|
|
5160
|
+
await executeV2FixedArrivals({
|
|
5161
|
+
rate: Math.max(1, maxRate),
|
|
5162
|
+
intervalNs,
|
|
5163
|
+
totalArrivals: BigInt(offsets.length),
|
|
5164
|
+
deadlineOffsetsNs: offsets,
|
|
5165
|
+
tolerancesNs: offsets.map(() => tolerance),
|
|
5166
|
+
budget: loadEngineV2Budget,
|
|
5167
|
+
cancellationToken: scenarioCancellationToken,
|
|
5168
|
+
shouldStopNow,
|
|
5169
|
+
nextInstanceInfo,
|
|
5170
|
+
runBombingInvocation: runSimulationInvocation,
|
|
5171
|
+
logger,
|
|
5172
|
+
scenarioName: scenario.name,
|
|
5173
|
+
shardIndex: scenarioPartition.number,
|
|
5174
|
+
shardCount: scenarioPartition.count,
|
|
5175
|
+
ownedOrdinals: trafficMixOwnedOrdinals(BigInt(offsets.length)),
|
|
5176
|
+
telemetry: loadEngineV2Telemetry,
|
|
5177
|
+
segment: schedulerSegment
|
|
5178
|
+
});
|
|
5179
|
+
return;
|
|
5180
|
+
}
|
|
3998
5181
|
const pending = [];
|
|
3999
5182
|
const normalizedMinRate = Math.max(1, minRate);
|
|
4000
5183
|
const normalizedMaxRate = Math.max(normalizedMinRate, maxRate);
|
|
@@ -4003,7 +5186,7 @@ async function executeScenarioRuntime(args) {
|
|
|
4003
5186
|
const currentRate = randomIntInclusive(normalizedMinRate, normalizedMaxRate);
|
|
4004
5187
|
for (let index = 0; index < currentRate; index += 1) {
|
|
4005
5188
|
const instanceInfo = nextInstanceInfo();
|
|
4006
|
-
pending.push(
|
|
5189
|
+
pending.push(runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
|
|
4007
5190
|
}
|
|
4008
5191
|
await delayWithAbort(intervalMs, scenarioCancellationToken);
|
|
4009
5192
|
}
|
|
@@ -4014,6 +5197,10 @@ async function executeScenarioRuntime(args) {
|
|
|
4014
5197
|
if (iterations <= 0) {
|
|
4015
5198
|
return;
|
|
4016
5199
|
}
|
|
5200
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5201
|
+
await executeV2IterationsConstant(copies, iterations, runSimulationInvocation, schedulerSegment);
|
|
5202
|
+
return;
|
|
5203
|
+
}
|
|
4017
5204
|
let remaining = iterations;
|
|
4018
5205
|
const tasks = Array.from({ length: copies }, async () => {
|
|
4019
5206
|
const instanceInfo = nextInstanceInfo();
|
|
@@ -4022,7 +5209,7 @@ async function executeScenarioRuntime(args) {
|
|
|
4022
5209
|
if (remaining < 0) {
|
|
4023
5210
|
break;
|
|
4024
5211
|
}
|
|
4025
|
-
await
|
|
5212
|
+
await runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
|
|
4026
5213
|
}
|
|
4027
5214
|
});
|
|
4028
5215
|
await waitForScenarioTasks(tasks, scenario.name, scenarioCompletionTimeoutSeconds, logger, scenarioCancellationToken);
|
|
@@ -4032,13 +5219,33 @@ async function executeScenarioRuntime(args) {
|
|
|
4032
5219
|
if (iterations <= 0 || rate <= 0 || intervalMs <= 0) {
|
|
4033
5220
|
return;
|
|
4034
5221
|
}
|
|
5222
|
+
if (options.loadEngineContractVersion === 2 && loadEngineV2Budget) {
|
|
5223
|
+
await executeV2FixedArrivals({
|
|
5224
|
+
rate,
|
|
5225
|
+
intervalNs: secondsToNanoseconds(toNumber(descriptor.IntervalSeconds), "IterationsForInject interval"),
|
|
5226
|
+
totalArrivals: BigInt(iterations),
|
|
5227
|
+
budget: loadEngineV2Budget,
|
|
5228
|
+
cancellationToken: scenarioCancellationToken,
|
|
5229
|
+
shouldStopNow,
|
|
5230
|
+
nextInstanceInfo,
|
|
5231
|
+
runBombingInvocation: runSimulationInvocation,
|
|
5232
|
+
logger,
|
|
5233
|
+
scenarioName: scenario.name,
|
|
5234
|
+
shardIndex: scenarioPartition.number,
|
|
5235
|
+
shardCount: scenarioPartition.count,
|
|
5236
|
+
ownedOrdinals: trafficMixOwnedOrdinals(BigInt(iterations)),
|
|
5237
|
+
telemetry: loadEngineV2Telemetry,
|
|
5238
|
+
segment: schedulerSegment
|
|
5239
|
+
});
|
|
5240
|
+
return;
|
|
5241
|
+
}
|
|
4035
5242
|
const pending = [];
|
|
4036
5243
|
let remaining = iterations;
|
|
4037
5244
|
while (remaining > 0 && !shouldStopNow()) {
|
|
4038
5245
|
const count = Math.min(rate, remaining);
|
|
4039
5246
|
for (let index = 0; index < count; index += 1) {
|
|
4040
5247
|
const instanceInfo = nextInstanceInfo();
|
|
4041
|
-
pending.push(
|
|
5248
|
+
pending.push(runSimulationInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId));
|
|
4042
5249
|
}
|
|
4043
5250
|
remaining -= count;
|
|
4044
5251
|
if (remaining > 0) {
|
|
@@ -4050,10 +5257,19 @@ async function executeScenarioRuntime(args) {
|
|
|
4050
5257
|
}
|
|
4051
5258
|
if (kind === "Pause") {
|
|
4052
5259
|
if (duringMs > 0) {
|
|
4053
|
-
|
|
5260
|
+
if (options.loadEngineContractVersion === 2) {
|
|
5261
|
+
await delayUntilMonotonicDeadline(process.hrtime.bigint() + secondsToNanoseconds(toNumber(descriptor.DuringSeconds), "Pause duration"), scenarioCancellationToken);
|
|
5262
|
+
}
|
|
5263
|
+
else {
|
|
5264
|
+
await delayWithAbort(duringMs, scenarioCancellationToken);
|
|
5265
|
+
}
|
|
4054
5266
|
}
|
|
5267
|
+
if (schedulerSegment)
|
|
5268
|
+
schedulerSegment.accountingComplete = true;
|
|
4055
5269
|
return;
|
|
4056
5270
|
}
|
|
5271
|
+
if (schedulerSegment)
|
|
5272
|
+
schedulerSegment.accountingComplete = true;
|
|
4057
5273
|
};
|
|
4058
5274
|
const initContext = {
|
|
4059
5275
|
customSettings: { ...(options.customSettings ?? {}) },
|
|
@@ -4085,15 +5301,33 @@ async function executeScenarioRuntime(args) {
|
|
|
4085
5301
|
accumulator.setCurrentOperation("Bombing");
|
|
4086
5302
|
const simulations = scenario.getSimulations();
|
|
4087
5303
|
if (!simulations.length) {
|
|
4088
|
-
|
|
4089
|
-
|
|
5304
|
+
if (options.loadEngineContractVersion === 2) {
|
|
5305
|
+
await options.loadEngineV2SegmentLifecycleOverride?.beforeSegment(scenarioIndex, 0);
|
|
5306
|
+
try {
|
|
5307
|
+
await executeSimulationAsync(LoadStrikeSimulation.iterationsForConstant(1, 1), 0);
|
|
5308
|
+
}
|
|
5309
|
+
finally {
|
|
5310
|
+
await options.loadEngineV2SegmentLifecycleOverride?.afterSegment(scenarioIndex, 0);
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5313
|
+
else {
|
|
5314
|
+
const instanceInfo = nextInstanceInfo();
|
|
5315
|
+
await runBombingInvocation(instanceInfo.instanceData, instanceInfo.instanceNumber, instanceInfo.instanceId);
|
|
5316
|
+
}
|
|
4090
5317
|
}
|
|
4091
5318
|
else {
|
|
4092
|
-
for (
|
|
5319
|
+
for (let simulationIndex = 0; simulationIndex < simulations.length; simulationIndex += 1) {
|
|
5320
|
+
const simulation = simulations[simulationIndex];
|
|
4093
5321
|
if (shouldStopNow()) {
|
|
4094
5322
|
break;
|
|
4095
5323
|
}
|
|
4096
|
-
await
|
|
5324
|
+
await options.loadEngineV2SegmentLifecycleOverride?.beforeSegment(scenarioIndex, simulationIndex);
|
|
5325
|
+
try {
|
|
5326
|
+
await executeSimulationAsync(simulation, simulationIndex);
|
|
5327
|
+
}
|
|
5328
|
+
finally {
|
|
5329
|
+
await options.loadEngineV2SegmentLifecycleOverride?.afterSegment(scenarioIndex, simulationIndex);
|
|
5330
|
+
}
|
|
4097
5331
|
}
|
|
4098
5332
|
}
|
|
4099
5333
|
accumulator.setCurrentOperation(stopScenario || stopTestState.value ? "Stop" : "Complete");
|
|
@@ -4182,18 +5416,36 @@ async function waitForScenarioTasks(tasks, scenarioName, timeoutSeconds, logger,
|
|
|
4182
5416
|
if (!tasks.length) {
|
|
4183
5417
|
return;
|
|
4184
5418
|
}
|
|
4185
|
-
const
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
5419
|
+
const settled = Promise.allSettled(tasks);
|
|
5420
|
+
const throwPolicyFailure = (results) => {
|
|
5421
|
+
const failure = results.find((result) => result.status === "rejected" && result.reason instanceof RuntimePolicyCallbackError);
|
|
5422
|
+
if (failure) {
|
|
5423
|
+
throw failure.reason;
|
|
5424
|
+
}
|
|
5425
|
+
};
|
|
5426
|
+
if (timeoutSeconds <= 0) {
|
|
5427
|
+
throwPolicyFailure(await settled);
|
|
5428
|
+
return;
|
|
5429
|
+
}
|
|
5430
|
+
const timeoutController = new AbortController();
|
|
5431
|
+
let completed;
|
|
5432
|
+
try {
|
|
5433
|
+
completed = await Promise.race([
|
|
5434
|
+
settled.then(() => true),
|
|
5435
|
+
delayWithAbort(Math.trunc(timeoutSeconds * 1000), combineAbortSignals(signal, timeoutController.signal)).then(() => false)
|
|
5436
|
+
]);
|
|
5437
|
+
}
|
|
5438
|
+
finally {
|
|
5439
|
+
timeoutController.abort();
|
|
4189
5440
|
}
|
|
4190
|
-
const completed = await Promise.race([
|
|
4191
|
-
all.then(() => true),
|
|
4192
|
-
delayWithAbort(Math.trunc(timeoutSeconds * 1000), signal).then(() => false)
|
|
4193
|
-
]);
|
|
4194
5441
|
if (!completed) {
|
|
5442
|
+
if (signal.reason instanceof RuntimePolicyCallbackError) {
|
|
5443
|
+
throw signal.reason;
|
|
5444
|
+
}
|
|
4195
5445
|
logger.warn(`Scenario ${scenarioName} timed out while waiting for completion (${timeoutSeconds}s).`);
|
|
5446
|
+
return;
|
|
4196
5447
|
}
|
|
5448
|
+
throwPolicyFailure(await settled);
|
|
4197
5449
|
}
|
|
4198
5450
|
function randomIntInclusive(minValue, maxValue) {
|
|
4199
5451
|
const min = Math.trunc(Math.min(minValue, maxValue));
|
|
@@ -4420,6 +5672,9 @@ function normalizeDataTransferStatsValue(value) {
|
|
|
4420
5672
|
const source = asAliasRecord(value);
|
|
4421
5673
|
return {
|
|
4422
5674
|
allBytes: pickAliasNumber(source, "allBytes", "AllBytes"),
|
|
5675
|
+
...(hasAliasValue(source, "allBytes64", "AllBytes64")
|
|
5676
|
+
? { allBytes64: pickAliasString(source, "allBytes64", "AllBytes64") }
|
|
5677
|
+
: {}),
|
|
4423
5678
|
maxBytes: pickAliasNumber(source, "maxBytes", "MaxBytes"),
|
|
4424
5679
|
meanBytes: pickAliasNumber(source, "meanBytes", "MeanBytes"),
|
|
4425
5680
|
minBytes: pickAliasNumber(source, "minBytes", "MinBytes"),
|
|
@@ -4427,6 +5682,9 @@ function normalizeDataTransferStatsValue(value) {
|
|
|
4427
5682
|
percent75: pickAliasNumber(source, "percent75", "Percent75"),
|
|
4428
5683
|
percent95: pickAliasNumber(source, "percent95", "Percent95"),
|
|
4429
5684
|
percent99: pickAliasNumber(source, "percent99", "Percent99"),
|
|
5685
|
+
...(hasAliasValue(source, "percent100", "Percent100")
|
|
5686
|
+
? { percent100: pickAliasNumber(source, "percent100", "Percent100") }
|
|
5687
|
+
: {}),
|
|
4430
5688
|
stdDev: pickAliasNumber(source, "stdDev", "StdDev")
|
|
4431
5689
|
};
|
|
4432
5690
|
}
|
|
@@ -4449,6 +5707,9 @@ function normalizeLatencyStatsValue(value) {
|
|
|
4449
5707
|
percent75: pickAliasNumber(source, "percent75", "Percent75"),
|
|
4450
5708
|
percent95: pickAliasNumber(source, "percent95", "Percent95"),
|
|
4451
5709
|
percent99: pickAliasNumber(source, "percent99", "Percent99"),
|
|
5710
|
+
...(hasAliasValue(source, "percent100", "Percent100")
|
|
5711
|
+
? { percent100: pickAliasNumber(source, "percent100", "Percent100") }
|
|
5712
|
+
: {}),
|
|
4452
5713
|
stdDev: pickAliasNumber(source, "stdDev", "StdDev")
|
|
4453
5714
|
};
|
|
4454
5715
|
}
|
|
@@ -4464,13 +5725,42 @@ function normalizeStatusCodeStatsValue(value) {
|
|
|
4464
5725
|
}
|
|
4465
5726
|
function normalizeMeasurementStatsValue(value) {
|
|
4466
5727
|
const source = asAliasRecord(value);
|
|
4467
|
-
|
|
5728
|
+
const projected = {
|
|
5729
|
+
...(hasAliasValue(source, "count64", "Count64")
|
|
5730
|
+
? { count64: pickAliasString(source, "count64", "Count64") }
|
|
5731
|
+
: {}),
|
|
5732
|
+
...(hasAliasValue(source, "distributionMode", "DistributionMode")
|
|
5733
|
+
? { distributionMode: pickAliasString(source, "distributionMode", "DistributionMode") }
|
|
5734
|
+
: {}),
|
|
5735
|
+
...(hasAliasValue(source, "maxRelativeError", "MaxRelativeError")
|
|
5736
|
+
? { maxRelativeError: pickAliasNumber(source, "maxRelativeError", "MaxRelativeError") }
|
|
5737
|
+
: {}),
|
|
4468
5738
|
dataTransfer: normalizeDataTransferStatsValue(pickAliasValue(source, "dataTransfer", "DataTransfer")),
|
|
4469
5739
|
latency: normalizeLatencyStatsValue(pickAliasValue(source, "latency", "Latency")),
|
|
4470
5740
|
request: normalizeRequestStatsValue(pickAliasValue(source, "request", "Request")),
|
|
4471
5741
|
statusCodes: pickAliasArray(source, "statusCodes", "StatusCodes")
|
|
4472
5742
|
.map((entry) => normalizeStatusCodeStatsValue(entry))
|
|
4473
5743
|
};
|
|
5744
|
+
const sidecarValue = pickAliasValue(source, "histogramSidecar", "HistogramSidecar");
|
|
5745
|
+
if (sidecarValue && typeof sidecarValue === "object" && !Array.isArray(sidecarValue)) {
|
|
5746
|
+
const sidecar = sidecarValue;
|
|
5747
|
+
if (sidecar?.latency && sidecar.size) {
|
|
5748
|
+
Object.defineProperty(projected, "histogramSidecar", {
|
|
5749
|
+
value: {
|
|
5750
|
+
latency: load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(sidecar.latency).toSidecar(),
|
|
5751
|
+
size: load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(sidecar.size).toSidecar(),
|
|
5752
|
+
allBytes64: String(sidecar.allBytes64 ?? "0"),
|
|
5753
|
+
lessOrEq80064: String(sidecar.lessOrEq80064 ?? "0"),
|
|
5754
|
+
more800Less120064: String(sidecar.more800Less120064 ?? "0"),
|
|
5755
|
+
moreOrEq120064: String(sidecar.moreOrEq120064 ?? "0")
|
|
5756
|
+
},
|
|
5757
|
+
enumerable: false,
|
|
5758
|
+
configurable: false,
|
|
5759
|
+
writable: false
|
|
5760
|
+
});
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
return projected;
|
|
4474
5764
|
}
|
|
4475
5765
|
function normalizeLoadSimulationStatsValue(value) {
|
|
4476
5766
|
const source = asAliasRecord(value);
|
|
@@ -4570,6 +5860,9 @@ function normalizeStepStatsValue(value, index = 0) {
|
|
|
4570
5860
|
statusCodes: normalizeAliasNumberRecord(pickAliasValue(source, "statusCodes", "StatusCodes")),
|
|
4571
5861
|
ok: normalizeMeasurementStatsValue(pickAliasValue(source, "ok", "Ok")),
|
|
4572
5862
|
fail: normalizeMeasurementStatsValue(pickAliasValue(source, "fail", "Fail")),
|
|
5863
|
+
...(hasAliasValue(source, "allMeasurement", "AllMeasurement")
|
|
5864
|
+
? { allMeasurement: normalizeMeasurementStatsValue(pickAliasValue(source, "allMeasurement", "AllMeasurement")) }
|
|
5865
|
+
: {}),
|
|
4573
5866
|
sortIndex: hasAliasValue(source, "sortIndex", "SortIndex")
|
|
4574
5867
|
? pickAliasNumber(source, "sortIndex", "SortIndex")
|
|
4575
5868
|
: index
|
|
@@ -4609,6 +5902,9 @@ function normalizeScenarioStatsValue(value, index = 0) {
|
|
|
4609
5902
|
durationMs: pickAliasNumber(source, "durationMs", "DurationMs", "Duration"),
|
|
4610
5903
|
ok: normalizeMeasurementStatsValue(pickAliasValue(source, "ok", "Ok")),
|
|
4611
5904
|
fail: normalizeMeasurementStatsValue(pickAliasValue(source, "fail", "Fail")),
|
|
5905
|
+
...(hasAliasValue(source, "allMeasurement", "AllMeasurement")
|
|
5906
|
+
? { allMeasurement: normalizeMeasurementStatsValue(pickAliasValue(source, "allMeasurement", "AllMeasurement")) }
|
|
5907
|
+
: {}),
|
|
4612
5908
|
loadSimulationStats: normalizeLoadSimulationStatsValue(pickAliasValue(source, "loadSimulationStats", "LoadSimulationStats")),
|
|
4613
5909
|
sortIndex: hasAliasValue(source, "sortIndex", "SortIndex")
|
|
4614
5910
|
? pickAliasNumber(source, "sortIndex", "SortIndex")
|
|
@@ -4695,7 +5991,7 @@ function attachSessionStartInfoAliases(session) {
|
|
|
4695
5991
|
return session;
|
|
4696
5992
|
}
|
|
4697
5993
|
function attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, licenseSession) {
|
|
4698
|
-
const runToken =
|
|
5994
|
+
const runToken = currentLicenseSessionRunToken(licenseSession);
|
|
4699
5995
|
if (!runToken || !licenseClient) {
|
|
4700
5996
|
return;
|
|
4701
5997
|
}
|
|
@@ -4705,9 +6001,17 @@ function attachPortalReportingSession(sinkSession, sessionInfo, licenseClient, l
|
|
|
4705
6001
|
sinkSession.portalReportingIngestUrl = ingestUrl;
|
|
4706
6002
|
sinkSession.portalReportingRunId = runId;
|
|
4707
6003
|
sessionInfo.runToken = runToken;
|
|
6004
|
+
Object.defineProperty(sessionInfo, PORTAL_RUN_TOKEN_PROVIDER, {
|
|
6005
|
+
configurable: true,
|
|
6006
|
+
enumerable: false,
|
|
6007
|
+
value: () => currentLicenseSessionRunToken(licenseSession)
|
|
6008
|
+
});
|
|
4708
6009
|
sessionInfo.portalReportingIngestUrl = ingestUrl;
|
|
4709
6010
|
sessionInfo.portalReportingRunId = runId;
|
|
4710
6011
|
}
|
|
6012
|
+
function currentLicenseSessionRunToken(licenseSession) {
|
|
6013
|
+
return stringValueOrDefault(licenseSession?.runToken, "").trim();
|
|
6014
|
+
}
|
|
4711
6015
|
function buildPortalReportingRunId(sessionId) {
|
|
4712
6016
|
const sessionPart = String(sessionId ?? "")
|
|
4713
6017
|
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
@@ -4792,6 +6096,7 @@ function attachDataTransferStatsAliases(stats) {
|
|
|
4792
6096
|
const projected = normalizeDataTransferStatsValue(stats);
|
|
4793
6097
|
return attachAliasMap(projected, {
|
|
4794
6098
|
AllBytes: "allBytes",
|
|
6099
|
+
AllBytes64: "allBytes64",
|
|
4795
6100
|
MaxBytes: "maxBytes",
|
|
4796
6101
|
MeanBytes: "meanBytes",
|
|
4797
6102
|
MinBytes: "minBytes",
|
|
@@ -4799,6 +6104,7 @@ function attachDataTransferStatsAliases(stats) {
|
|
|
4799
6104
|
Percent75: "percent75",
|
|
4800
6105
|
Percent95: "percent95",
|
|
4801
6106
|
Percent99: "percent99",
|
|
6107
|
+
Percent100: "percent100",
|
|
4802
6108
|
StdDev: "stdDev"
|
|
4803
6109
|
});
|
|
4804
6110
|
}
|
|
@@ -4822,6 +6128,7 @@ function attachLatencyStatsAliases(stats) {
|
|
|
4822
6128
|
Percent75: "percent75",
|
|
4823
6129
|
Percent95: "percent95",
|
|
4824
6130
|
Percent99: "percent99",
|
|
6131
|
+
Percent100: "percent100",
|
|
4825
6132
|
StdDev: "stdDev"
|
|
4826
6133
|
});
|
|
4827
6134
|
return projected;
|
|
@@ -4843,6 +6150,9 @@ function attachMeasurementStatsAliases(stats) {
|
|
|
4843
6150
|
projected.latency = attachLatencyStatsAliases(projected.latency);
|
|
4844
6151
|
projected.statusCodes = projected.statusCodes.map((value) => attachStatusCodeStatsAliases(value));
|
|
4845
6152
|
attachAliasMap(projected, {
|
|
6153
|
+
Count64: "count64",
|
|
6154
|
+
DistributionMode: "distributionMode",
|
|
6155
|
+
MaxRelativeError: "maxRelativeError",
|
|
4846
6156
|
Request: "request",
|
|
4847
6157
|
DataTransfer: "dataTransfer",
|
|
4848
6158
|
Latency: "latency",
|
|
@@ -4904,6 +6214,8 @@ function attachStepStatsAliases(step) {
|
|
|
4904
6214
|
const projected = normalizeStepStatsValue(step);
|
|
4905
6215
|
projected.ok = attachMeasurementStatsAliases(projected.ok);
|
|
4906
6216
|
projected.fail = attachMeasurementStatsAliases(projected.fail);
|
|
6217
|
+
if (projected.allMeasurement)
|
|
6218
|
+
projected.allMeasurement = attachMeasurementStatsAliases(projected.allMeasurement);
|
|
4907
6219
|
attachAliasMap(projected, {
|
|
4908
6220
|
ScenarioName: "scenarioName",
|
|
4909
6221
|
StepName: "stepName",
|
|
@@ -4918,6 +6230,7 @@ function attachStepStatsAliases(step) {
|
|
|
4918
6230
|
StatusCodes: "statusCodes",
|
|
4919
6231
|
Ok: "ok",
|
|
4920
6232
|
Fail: "fail",
|
|
6233
|
+
AllMeasurement: "allMeasurement",
|
|
4921
6234
|
SortIndex: "sortIndex"
|
|
4922
6235
|
});
|
|
4923
6236
|
return projected;
|
|
@@ -4956,7 +6269,20 @@ function attachLoadSimulationProjection(simulation) {
|
|
|
4956
6269
|
});
|
|
4957
6270
|
return simulation;
|
|
4958
6271
|
}
|
|
4959
|
-
function
|
|
6272
|
+
function cloneLoadEngineV2TrafficMixMetadata(metadata) {
|
|
6273
|
+
return {
|
|
6274
|
+
...metadata,
|
|
6275
|
+
shareWeights: [...metadata.shareWeights],
|
|
6276
|
+
originalSimulations: metadata.originalSimulations.map((simulation) => attachLoadSimulationProjection({ ...simulation }))
|
|
6277
|
+
};
|
|
6278
|
+
}
|
|
6279
|
+
function nextTrafficMixDeclarationIndex(scenarios) {
|
|
6280
|
+
return scenarios.reduce((next, scenario) => {
|
|
6281
|
+
const metadata = scenario.__loadStrikeTrafficMixV2Metadata();
|
|
6282
|
+
return metadata ? Math.max(next, metadata.declarationIndex + 1) : next;
|
|
6283
|
+
}, 0);
|
|
6284
|
+
}
|
|
6285
|
+
function expandTrafficMixScenarios(trafficMix, declarationIndex = 0) {
|
|
4960
6286
|
if (!(trafficMix instanceof LoadStrikeTrafficMix)) {
|
|
4961
6287
|
throw new TypeError("Traffic mix must be provided.");
|
|
4962
6288
|
}
|
|
@@ -4969,16 +6295,30 @@ function expandTrafficMixScenarios(trafficMix) {
|
|
|
4969
6295
|
throw new Error("Traffic mix scenario shares must be configured before registration.");
|
|
4970
6296
|
}
|
|
4971
6297
|
const weights = scenarioMix.map((share) => share.weight);
|
|
6298
|
+
const seedId = (0, load_engine_v2_js_1.buildLoadEngineV2TrafficMixSeedId)(declarationIndex, trafficMix.name);
|
|
4972
6299
|
return scenarioMix.map((share, index) => {
|
|
4973
|
-
const splitSimulations = totalLoad
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
6300
|
+
const splitSimulations = totalLoad.map((simulation) => splitTrafficSimulation(simulation, weights, index)
|
|
6301
|
+
?? trafficMixNoWorkSimulation(simulation));
|
|
6302
|
+
return share.scenario
|
|
6303
|
+
.withLoadSimulations(...splitSimulations)
|
|
6304
|
+
.__loadStrikeWithInternalLicenseFeatures(TRAFFIC_MIX_FEATURE)
|
|
6305
|
+
.__loadStrikeSetTrafficMixV2Metadata({
|
|
6306
|
+
declarationIndex,
|
|
6307
|
+
name: trafficMix.name,
|
|
6308
|
+
laneIndex: index,
|
|
6309
|
+
shareWeight: share.weight,
|
|
6310
|
+
shareWeights: weights,
|
|
6311
|
+
seedId,
|
|
6312
|
+
originalSimulations: totalLoad
|
|
6313
|
+
});
|
|
4980
6314
|
});
|
|
4981
6315
|
}
|
|
6316
|
+
function trafficMixNoWorkSimulation(simulation) {
|
|
6317
|
+
const kind = String(simulation.Kind ?? "");
|
|
6318
|
+
return kind === "IterationsForInject" || kind === "IterationsForConstant"
|
|
6319
|
+
? LoadStrikeSimulation.pause(0)
|
|
6320
|
+
: LoadStrikeSimulation.pause(Math.max(readFiniteSimulationNumber(simulation, "DuringSeconds"), 0));
|
|
6321
|
+
}
|
|
4982
6322
|
function splitTrafficSimulation(simulation, weights, index) {
|
|
4983
6323
|
const kind = String(simulation.Kind ?? "");
|
|
4984
6324
|
const duringSeconds = readFiniteSimulationNumber(simulation, "DuringSeconds");
|
|
@@ -5065,6 +6405,8 @@ function attachScenarioStatsAliases(scenario) {
|
|
|
5065
6405
|
const normalized = normalizeScenarioStatsValue(scenario);
|
|
5066
6406
|
normalized.ok = attachMeasurementStatsAliases(normalized.ok);
|
|
5067
6407
|
normalized.fail = attachMeasurementStatsAliases(normalized.fail);
|
|
6408
|
+
if (normalized.allMeasurement)
|
|
6409
|
+
normalized.allMeasurement = attachMeasurementStatsAliases(normalized.allMeasurement);
|
|
5068
6410
|
normalized.loadSimulationStats = attachLoadSimulationStatsAliases(normalized.loadSimulationStats);
|
|
5069
6411
|
normalized.stepStats = normalized.stepStats.map((value) => attachStepStatsAliases(value));
|
|
5070
6412
|
const findStepStats = scenario.findStepStats ?? ((stepName) => normalized.stepStats.find((value) => value.stepName === stepName));
|
|
@@ -5098,6 +6440,7 @@ function attachScenarioStatsAliases(scenario) {
|
|
|
5098
6440
|
DurationMs: "durationMs",
|
|
5099
6441
|
Ok: "ok",
|
|
5100
6442
|
Fail: "fail",
|
|
6443
|
+
AllMeasurement: "allMeasurement",
|
|
5101
6444
|
LoadSimulationStats: "loadSimulationStats",
|
|
5102
6445
|
SortIndex: "sortIndex",
|
|
5103
6446
|
StepStats: "stepStats"
|
|
@@ -5118,6 +6461,10 @@ function attachNodeStatsAliases(stats) {
|
|
|
5118
6461
|
: stats.scenarioStats.flatMap((value) => value.stepStats)).map((value) => attachStepStatsAliases(value));
|
|
5119
6462
|
stats.pluginsData = stats.pluginsData.map((value) => normalizePluginData(value.pluginName ?? value.PluginName ?? "", value));
|
|
5120
6463
|
stats.sinkErrors = stats.sinkErrors.map((value) => attachSinkErrorAliases(value));
|
|
6464
|
+
stats.generatorWarnings = (stats.generatorWarnings ?? []).map(attachGeneratorWarningAliases);
|
|
6465
|
+
stats.schedulerSegments = (stats.schedulerSegments ?? []).map((value) => normalizeSchedulerSegment(value));
|
|
6466
|
+
stats.observationDeliveryStats = normalizeObservationDeliveryStats(stats.observationDeliveryStats ?? emptyObservationDeliveryStats());
|
|
6467
|
+
stats.reportingComplete ?? (stats.reportingComplete = false);
|
|
5121
6468
|
const findScenarioStats = stats.findScenarioStats ?? ((scenarioName) => stats.scenarioStats.find((value) => value.scenarioName === scenarioName));
|
|
5122
6469
|
const getScenarioStats = stats.getScenarioStats ?? ((scenarioName) => {
|
|
5123
6470
|
const value = findScenarioStats(scenarioName);
|
|
@@ -5152,7 +6499,11 @@ function attachNodeStatsAliases(stats) {
|
|
|
5152
6499
|
DisabledSinks: "disabledSinks",
|
|
5153
6500
|
SinkErrors: "sinkErrors",
|
|
5154
6501
|
ReportFiles: "reportFiles",
|
|
5155
|
-
LogFiles: "logFiles"
|
|
6502
|
+
LogFiles: "logFiles",
|
|
6503
|
+
GeneratorWarnings: "generatorWarnings",
|
|
6504
|
+
SchedulerSegments: "schedulerSegments",
|
|
6505
|
+
ObservationDeliveryStats: "observationDeliveryStats",
|
|
6506
|
+
ReportingComplete: "reportingComplete"
|
|
5156
6507
|
});
|
|
5157
6508
|
defineAliasProperty(projected, "StartedUtc", () => parseAliasDate(stats.startedUtc));
|
|
5158
6509
|
defineAliasProperty(projected, "CompletedUtc", () => parseAliasDate(stats.completedUtc));
|
|
@@ -5208,11 +6559,43 @@ function attachRunResultAliases(result) {
|
|
|
5208
6559
|
.map((value) => ({ ...asAliasRecord(value) })),
|
|
5209
6560
|
failedCorrelationRows: pickAliasArray(source, "failedCorrelationRows", "FailedCorrelationRows")
|
|
5210
6561
|
.map((value) => ({ ...asAliasRecord(value) })),
|
|
6562
|
+
...(hasAliasValue(source, "generatorWarnings", "GeneratorWarnings")
|
|
6563
|
+
? {
|
|
6564
|
+
generatorWarnings: pickAliasArray(source, "generatorWarnings", "GeneratorWarnings")
|
|
6565
|
+
.map(attachGeneratorWarningAliases)
|
|
6566
|
+
}
|
|
6567
|
+
: {}),
|
|
6568
|
+
...(hasAliasValue(source, "schedulerSegments", "SchedulerSegments")
|
|
6569
|
+
? {
|
|
6570
|
+
schedulerSegments: pickAliasArray(source, "schedulerSegments", "SchedulerSegments")
|
|
6571
|
+
.map(normalizeSchedulerSegment)
|
|
6572
|
+
}
|
|
6573
|
+
: {}),
|
|
6574
|
+
...(hasAliasValue(source, "schedulerStats", "SchedulerStats")
|
|
6575
|
+
? { schedulerStats: normalizeSchedulerStats(pickAliasValue(source, "schedulerStats", "SchedulerStats")) }
|
|
6576
|
+
: {}),
|
|
6577
|
+
...(hasAliasValue(source, "observationDeliveryStats", "ObservationDeliveryStats")
|
|
6578
|
+
? {
|
|
6579
|
+
observationDeliveryStats: normalizeObservationDeliveryStats(pickAliasValue(source, "observationDeliveryStats", "ObservationDeliveryStats"))
|
|
6580
|
+
}
|
|
6581
|
+
: {}),
|
|
6582
|
+
...(hasAliasValue(source, "reportingComplete", "ReportingComplete")
|
|
6583
|
+
? { reportingComplete: pickAliasBoolean(source, "reportingComplete", "ReportingComplete") }
|
|
6584
|
+
: {}),
|
|
5211
6585
|
findScenarioStats,
|
|
5212
6586
|
getScenarioStats,
|
|
5213
6587
|
FindScenarioStats: findScenarioStats,
|
|
5214
6588
|
GetScenarioStats: getScenarioStats
|
|
5215
6589
|
};
|
|
6590
|
+
const schedulerDistributions = result[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS];
|
|
6591
|
+
if (schedulerDistributions) {
|
|
6592
|
+
Object.defineProperty(projected, LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS, {
|
|
6593
|
+
value: schedulerDistributions.map(cloneLoadEngineV2DistributionRecord),
|
|
6594
|
+
enumerable: false,
|
|
6595
|
+
configurable: false,
|
|
6596
|
+
writable: false
|
|
6597
|
+
});
|
|
6598
|
+
}
|
|
5216
6599
|
attachAliasMap(projected, {
|
|
5217
6600
|
AllBytes: "allBytes",
|
|
5218
6601
|
AllRequestCount: "allRequestCount",
|
|
@@ -5236,7 +6619,12 @@ function attachRunResultAliases(result) {
|
|
|
5236
6619
|
ReportFiles: "reportFiles",
|
|
5237
6620
|
LogFiles: "logFiles",
|
|
5238
6621
|
CorrelationRows: "correlationRows",
|
|
5239
|
-
FailedCorrelationRows: "failedCorrelationRows"
|
|
6622
|
+
FailedCorrelationRows: "failedCorrelationRows",
|
|
6623
|
+
GeneratorWarnings: "generatorWarnings",
|
|
6624
|
+
SchedulerSegments: "schedulerSegments",
|
|
6625
|
+
SchedulerStats: "schedulerStats",
|
|
6626
|
+
ObservationDeliveryStats: "observationDeliveryStats",
|
|
6627
|
+
ReportingComplete: "reportingComplete"
|
|
5240
6628
|
});
|
|
5241
6629
|
defineAliasProperty(projected, "StartedUtc", () => parseAliasDate(projected.startedUtc));
|
|
5242
6630
|
defineAliasProperty(projected, "CompletedUtc", () => parseAliasDate(projected.completedUtc));
|
|
@@ -5844,6 +7232,14 @@ function detailedToNodeStats(result, metricStats) {
|
|
|
5844
7232
|
sinkErrors: (result.sinkErrors ?? []).map((sinkError) => ({ ...sinkError })),
|
|
5845
7233
|
reportFiles: [...(result.reportFiles ?? [])],
|
|
5846
7234
|
logFiles: [...(result.logFiles ?? [])],
|
|
7235
|
+
generatorWarnings: (result.generatorWarnings ?? []).map(attachGeneratorWarningAliases),
|
|
7236
|
+
schedulerSegments: result.schedulerSegments?.map((segment) => ({ ...segment })),
|
|
7237
|
+
schedulerStats: result.schedulerStats
|
|
7238
|
+
? normalizeSchedulerStats(result.schedulerStats)
|
|
7239
|
+
: undefined,
|
|
7240
|
+
[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: result[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]?.map(cloneLoadEngineV2DistributionRecord),
|
|
7241
|
+
observationDeliveryStats: normalizeObservationDeliveryStats(result.observationDeliveryStats ?? emptyObservationDeliveryStats()),
|
|
7242
|
+
reportingComplete: result.reportingComplete ?? true,
|
|
5847
7243
|
findScenarioStats: (scenarioName) => scenarioStats.find((scenario) => scenario.scenarioName === scenarioName),
|
|
5848
7244
|
getScenarioStats: (scenarioName) => {
|
|
5849
7245
|
const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
|
|
@@ -5911,22 +7307,35 @@ function buildEmptyNodeStats(args) {
|
|
|
5911
7307
|
sinkErrors: [],
|
|
5912
7308
|
reportFiles: [],
|
|
5913
7309
|
logFiles: [],
|
|
7310
|
+
generatorWarnings: [],
|
|
7311
|
+
observationDeliveryStats: emptyObservationDeliveryStats(),
|
|
7312
|
+
reportingComplete: true,
|
|
5914
7313
|
findScenarioStats: (scenarioName) => undefined,
|
|
5915
7314
|
getScenarioStats: (scenarioName) => {
|
|
5916
7315
|
throw new Error(`Scenario stats not found: ${scenarioName}`);
|
|
5917
7316
|
}
|
|
5918
7317
|
});
|
|
5919
7318
|
}
|
|
5920
|
-
function planRuntimeClusterAssignments(scenarios, agentsCount, targetScenarios, agentTargetScenarios) {
|
|
7319
|
+
function planRuntimeClusterAssignments(scenarios, agentsCount, targetScenarios, agentTargetScenarios, globalV2 = false, coordinatorTargetScenarios = []) {
|
|
5921
7320
|
const resolvedAgentCount = Math.max(agentsCount, 1);
|
|
5922
|
-
|
|
7321
|
+
let selectedScenarios = targetScenarios.length
|
|
5923
7322
|
? scenarios.filter((scenario) => targetScenarios.includes(scenario.name))
|
|
5924
7323
|
: [...scenarios];
|
|
7324
|
+
if (globalV2 && coordinatorTargetScenarios.length) {
|
|
7325
|
+
const coordinatorNames = new Set(coordinatorTargetScenarios);
|
|
7326
|
+
selectedScenarios = selectedScenarios.filter((scenario) => !coordinatorNames.has(scenario.name));
|
|
7327
|
+
}
|
|
5925
7328
|
if (!selectedScenarios.length) {
|
|
5926
7329
|
return Array.from({ length: resolvedAgentCount }, () => []);
|
|
5927
7330
|
}
|
|
5928
7331
|
if (agentTargetScenarios.length) {
|
|
5929
|
-
|
|
7332
|
+
const coordinatorNames = new Set(coordinatorTargetScenarios);
|
|
7333
|
+
const names = agentTargetScenarios.filter((name) => !coordinatorNames.has(name));
|
|
7334
|
+
return Array.from({ length: resolvedAgentCount }, () => [...names]);
|
|
7335
|
+
}
|
|
7336
|
+
if (globalV2) {
|
|
7337
|
+
const scenarioNames = selectedScenarios.map((scenario) => scenario.name);
|
|
7338
|
+
return Array.from({ length: resolvedAgentCount }, () => [...scenarioNames]);
|
|
5930
7339
|
}
|
|
5931
7340
|
const weightedNames = [];
|
|
5932
7341
|
for (const scenario of selectedScenarios) {
|
|
@@ -5946,7 +7355,320 @@ function planRuntimeClusterAssignments(scenarios, agentsCount, targetScenarios,
|
|
|
5946
7355
|
}
|
|
5947
7356
|
return assignments.map((entry) => [...entry]);
|
|
5948
7357
|
}
|
|
5949
|
-
function
|
|
7358
|
+
function buildRuntimeLoadEngineV2Plan(scenarios, options, testInfo, expectedAgentIds) {
|
|
7359
|
+
validateLoadEngineV2ScenarioFeatures(scenarios);
|
|
7360
|
+
const kindToken = (value) => {
|
|
7361
|
+
const normalized = String(value ?? "").replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
7362
|
+
const tokens = {
|
|
7363
|
+
inject: "inject", injectrandom: "inject-random", rampinginject: "ramping-inject",
|
|
7364
|
+
keepconstant: "keep-constant", rampingconstant: "ramping-constant",
|
|
7365
|
+
iterationsforinject: "iterations-for-inject", iterationsforconstant: "iterations-for-constant",
|
|
7366
|
+
pause: "pause"
|
|
7367
|
+
};
|
|
7368
|
+
const token = tokens[normalized];
|
|
7369
|
+
if (!token)
|
|
7370
|
+
throw new Error(`Load Engine V2 simulation kind is unsupported: ${String(value ?? "")}`);
|
|
7371
|
+
return token;
|
|
7372
|
+
};
|
|
7373
|
+
const integer = (value) => Math.max(Math.trunc(toNumber(value)), 0).toString();
|
|
7374
|
+
const ns = (value) => Math.max(Math.round(toNumber(value) * 1000000000), 0).toString();
|
|
7375
|
+
const coordinatorNames = new Set(options.coordinatorTargetScenarios ?? []);
|
|
7376
|
+
const explicitAgentNames = new Set(options.agentTargetScenarios ?? []);
|
|
7377
|
+
const selectedNames = new Set(options.targetScenarios ?? []);
|
|
7378
|
+
const plannedScenarios = scenarios
|
|
7379
|
+
.map((scenario, declarationIndex) => ({ scenario, declarationIndex }))
|
|
7380
|
+
.filter(({ scenario }) => (selectedNames.size === 0 || selectedNames.has(scenario.name))
|
|
7381
|
+
&& (explicitAgentNames.size > 0
|
|
7382
|
+
? explicitAgentNames.has(scenario.name)
|
|
7383
|
+
: !coordinatorNames.has(scenario.name)));
|
|
7384
|
+
const implicitSingleInvocation = {
|
|
7385
|
+
simulationIndex64: "0",
|
|
7386
|
+
kind: "iterations-for-constant",
|
|
7387
|
+
rate64: "0",
|
|
7388
|
+
minRate64: "0",
|
|
7389
|
+
maxRate64: "0",
|
|
7390
|
+
copies64: "1",
|
|
7391
|
+
iterations64: "1",
|
|
7392
|
+
intervalNs64: "0",
|
|
7393
|
+
durationNs64: "0"
|
|
7394
|
+
};
|
|
7395
|
+
return {
|
|
7396
|
+
runId: testInfo.sessionId,
|
|
7397
|
+
sessionId: testInfo.sessionId,
|
|
7398
|
+
registrationNonce: (0, node_crypto_1.randomBytes)(16).toString("hex"),
|
|
7399
|
+
maxInFlight64: Math.max(options.maxInFlight ?? 10000, 1).toString(),
|
|
7400
|
+
reportingIntervalNs64: ns(options.reportingIntervalSeconds ?? 1),
|
|
7401
|
+
schedulerVisibleProcessorCount64: Math.max(node_os_1.default.cpus().length, 1).toString(),
|
|
7402
|
+
expectedAgentIds: [...expectedAgentIds],
|
|
7403
|
+
scenarios: plannedScenarios.map(({ scenario, declarationIndex }) => ({
|
|
7404
|
+
scenarioIndex64: declarationIndex.toString(),
|
|
7405
|
+
scenarioName: scenario.name,
|
|
7406
|
+
target: "agent",
|
|
7407
|
+
callbackExecutionMode: "async",
|
|
7408
|
+
declaredStepNames: scenario.getDeclaredSteps(),
|
|
7409
|
+
simulations: scenario.getSimulations().length
|
|
7410
|
+
? scenario.getSimulations().map((simulation, simulationIndex) => ({
|
|
7411
|
+
simulationIndex64: simulationIndex.toString(),
|
|
7412
|
+
kind: kindToken(simulation.Kind ?? simulation.kind),
|
|
7413
|
+
rate64: integer(simulation.Rate ?? simulation.rate),
|
|
7414
|
+
minRate64: integer(simulation.MinRate ?? simulation.minRate),
|
|
7415
|
+
maxRate64: integer(simulation.MaxRate ?? simulation.maxRate),
|
|
7416
|
+
copies64: integer(simulation.Copies ?? simulation.copies),
|
|
7417
|
+
iterations64: integer(simulation.Iterations ?? simulation.iterations),
|
|
7418
|
+
intervalNs64: ns(simulation.IntervalSeconds ?? simulation.intervalSeconds),
|
|
7419
|
+
durationNs64: ns(simulation.DuringSeconds ?? simulation.duringSeconds)
|
|
7420
|
+
}))
|
|
7421
|
+
: [{ ...implicitSingleInvocation }]
|
|
7422
|
+
}))
|
|
7423
|
+
};
|
|
7424
|
+
}
|
|
7425
|
+
function normalizeRequiredV2AgentIds(values, expectedCount) {
|
|
7426
|
+
const ids = normalizeOptionalStringArray(values) ?? [];
|
|
7427
|
+
if (ids.length !== expectedCount || new Set(ids).size !== ids.length) {
|
|
7428
|
+
throw new Error("Remote Load Engine V2 coordinators require an exact unique ExpectedAgentIds set matching AgentsCount.");
|
|
7429
|
+
}
|
|
7430
|
+
return ids;
|
|
7431
|
+
}
|
|
7432
|
+
function validateLoadEngineV2ScenarioFeatures(scenarios) {
|
|
7433
|
+
for (const scenario of scenarios) {
|
|
7434
|
+
if (scenario.getTrackingConfiguration()) {
|
|
7435
|
+
throw new Error(`Load Engine V2 correlation is not available in the supported non-correlation profile. Scenario=${scenario.name}.`);
|
|
7436
|
+
}
|
|
7437
|
+
}
|
|
7438
|
+
}
|
|
7439
|
+
function canonicalLoadEngineV2InvocationIdentityKind(simulationKind) {
|
|
7440
|
+
const normalized = simulationKind.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
7441
|
+
if (["inject", "injectrandom", "rampinginject", "iterationsforinject"].includes(normalized)) {
|
|
7442
|
+
return "arrival";
|
|
7443
|
+
}
|
|
7444
|
+
if (["keepconstant", "rampingconstant"].includes(normalized)) {
|
|
7445
|
+
return "worker-iteration";
|
|
7446
|
+
}
|
|
7447
|
+
if (normalized === "iterationsforconstant") {
|
|
7448
|
+
return "constant-iteration";
|
|
7449
|
+
}
|
|
7450
|
+
return "";
|
|
7451
|
+
}
|
|
7452
|
+
function buildRuntimeLoadEngineV2HistogramArtifact(result, scenarioDeclarations) {
|
|
7453
|
+
const emptyHistogram = () => new load_engine_v2_js_1.LoadStrikeHistogramV1().toSidecar();
|
|
7454
|
+
const distributions = [];
|
|
7455
|
+
const measurementSummaries = [];
|
|
7456
|
+
const statusBody = (measurement, outcome) => {
|
|
7457
|
+
const named = measurement.statusCodes
|
|
7458
|
+
.filter((row) => Boolean(row.statusCode || row.message))
|
|
7459
|
+
.map((row) => {
|
|
7460
|
+
const key = (0, cluster_js_1.buildLoadEngineV2StatusIdentityKey)(row.statusCode, row.message);
|
|
7461
|
+
if (!key)
|
|
7462
|
+
throw new Error("Load Engine V2 status identity unexpectedly resolved empty.");
|
|
7463
|
+
return {
|
|
7464
|
+
statusIdentityKeyHex: key.identity.toString("hex"),
|
|
7465
|
+
display: key.display,
|
|
7466
|
+
count64: Math.max(Math.trunc(row.count), 0).toString(),
|
|
7467
|
+
aggregatedObservationCount64: "0",
|
|
7468
|
+
hasAggregatedIdentities: false
|
|
7469
|
+
};
|
|
7470
|
+
})
|
|
7471
|
+
.sort((left, right) => Buffer.compare(Buffer.from(left.statusIdentityKeyHex, "hex"), Buffer.from(right.statusIdentityKeyHex, "hex")));
|
|
7472
|
+
let statuses = named;
|
|
7473
|
+
if (named.length > 64) {
|
|
7474
|
+
const retained = named.slice(0, 63);
|
|
7475
|
+
const aggregated = named.slice(63).reduce((sum, row) => sum + BigInt(row.count64), 0n);
|
|
7476
|
+
retained.push({
|
|
7477
|
+
statusIdentityKeyHex: Buffer.concat([Buffer.from("LS-ID1\n", "ascii"), Buffer.from([0x0d])]).toString("hex"),
|
|
7478
|
+
display: "<other>", count64: aggregated.toString(),
|
|
7479
|
+
aggregatedObservationCount64: aggregated.toString(), hasAggregatedIdentities: aggregated > 0n
|
|
7480
|
+
});
|
|
7481
|
+
statuses = retained;
|
|
7482
|
+
}
|
|
7483
|
+
return {
|
|
7484
|
+
outcome,
|
|
7485
|
+
statusObservationCount64: statuses.reduce((sum, row) => sum + BigInt(row.count64), 0n).toString(),
|
|
7486
|
+
statuses
|
|
7487
|
+
};
|
|
7488
|
+
};
|
|
7489
|
+
const appendMeasurement = (seriesKind, scenarioIndex64, scenarioName, identity, display, ok, fail, all, reservedOther) => {
|
|
7490
|
+
const emptyMeasurement = () => ({
|
|
7491
|
+
count64: "0",
|
|
7492
|
+
histogramSidecar: {
|
|
7493
|
+
latency: emptyHistogram(), size: emptyHistogram(), allBytes64: "0",
|
|
7494
|
+
lessOrEq80064: "0", more800Less120064: "0", moreOrEq120064: "0"
|
|
7495
|
+
},
|
|
7496
|
+
request: { count: 0, percent: 0, rps: 0 },
|
|
7497
|
+
dataTransfer: { allBytes: 0, minBytes: 0, maxBytes: 0, meanBytes: 0, percent50: 0,
|
|
7498
|
+
percent75: 0, percent95: 0, percent99: 0, percent100: 0, stdDev: 0 },
|
|
7499
|
+
latency: { latencyCount: { lessOrEq800: 0, more800Less1200: 0, moreOrEq1200: 0 },
|
|
7500
|
+
minMs: 0, maxMs: 0, meanMs: 0, percent50: 0, percent75: 0, percent95: 0,
|
|
7501
|
+
percent99: 0, stdDev: 0 },
|
|
7502
|
+
statusCodes: []
|
|
7503
|
+
});
|
|
7504
|
+
const okValue = ok ?? emptyMeasurement();
|
|
7505
|
+
const failValue = fail ?? emptyMeasurement();
|
|
7506
|
+
const allValue = all ?? emptyMeasurement();
|
|
7507
|
+
for (const [outcome, measurement] of [
|
|
7508
|
+
["ok", okValue], ["fail", failValue], ["all", allValue]
|
|
7509
|
+
]) {
|
|
7510
|
+
const sidecar = measurement.histogramSidecar;
|
|
7511
|
+
if (!sidecar)
|
|
7512
|
+
throw new Error("Load Engine V2 assigned measurement is missing histogram state.");
|
|
7513
|
+
distributions.push({
|
|
7514
|
+
seriesKind, scenarioIndex64, scenarioName, identityKeyHex: identity.toString("hex"),
|
|
7515
|
+
outcome, unit: "microseconds", histogram: sidecar.latency,
|
|
7516
|
+
exactTotalDecimalOrEmpty: sidecar.latency.exactTotal64,
|
|
7517
|
+
bands: [
|
|
7518
|
+
{ bandId64: "0", count64: sidecar.lessOrEq80064 },
|
|
7519
|
+
{ bandId64: "1", count64: sidecar.more800Less120064 },
|
|
7520
|
+
{ bandId64: "2", count64: sidecar.moreOrEq120064 }
|
|
7521
|
+
]
|
|
7522
|
+
});
|
|
7523
|
+
distributions.push({
|
|
7524
|
+
seriesKind, scenarioIndex64, scenarioName, identityKeyHex: identity.toString("hex"),
|
|
7525
|
+
outcome, unit: "bytes", histogram: sidecar.size,
|
|
7526
|
+
exactTotalDecimalOrEmpty: sidecar.size.exactTotal64
|
|
7527
|
+
});
|
|
7528
|
+
}
|
|
7529
|
+
const observation = BigInt(allValue.count64 ?? allValue.histogramSidecar?.latency.count64 ?? "0");
|
|
7530
|
+
const success = BigInt(okValue.count64 ?? okValue.histogramSidecar?.latency.count64 ?? "0");
|
|
7531
|
+
const failure = BigInt(failValue.count64 ?? failValue.histogramSidecar?.latency.count64 ?? "0");
|
|
7532
|
+
measurementSummaries.push({
|
|
7533
|
+
seriesKind, scenarioIndex64, scenarioName, identityKeyHex: identity.toString("hex"), display,
|
|
7534
|
+
observationCount64: observation.toString(), successCount64: success.toString(),
|
|
7535
|
+
failureCount64: failure.toString(),
|
|
7536
|
+
aggregatedObservationCount64: reservedOther ? observation.toString() : "0",
|
|
7537
|
+
hasAggregatedIdentities: reservedOther && observation > 0n,
|
|
7538
|
+
outcomes: [statusBody(okValue, "ok"), statusBody(failValue, "fail")]
|
|
7539
|
+
});
|
|
7540
|
+
};
|
|
7541
|
+
for (const scenario of result.scenarioStats) {
|
|
7542
|
+
const scenarioIndex64 = Math.max(Math.trunc(scenario.sortIndex), 0).toString();
|
|
7543
|
+
appendMeasurement("scenario", scenarioIndex64, scenario.scenarioName, (0, cluster_js_1.buildLoadEngineV2ScenarioIdentityKey)(scenarioIndex64), scenario.scenarioName, scenario.ok, scenario.fail, scenario.allMeasurement, false);
|
|
7544
|
+
const declaration = scenarioDeclarations?.find((value) => value.name === scenario.scenarioName);
|
|
7545
|
+
const declaredNames = declaration?.getDeclaredSteps()
|
|
7546
|
+
?? (scenarioDeclarations ? [] : undefined);
|
|
7547
|
+
if (declaredNames === undefined) {
|
|
7548
|
+
for (const step of scenario.stepStats) {
|
|
7549
|
+
const key = (0, cluster_js_1.buildLoadEngineV2StepIdentityKey)(scenarioIndex64, step.stepName);
|
|
7550
|
+
appendMeasurement("step", scenarioIndex64, scenario.scenarioName, key.identity, key.display, step.ok, step.fail, step.allMeasurement, false);
|
|
7551
|
+
}
|
|
7552
|
+
}
|
|
7553
|
+
else {
|
|
7554
|
+
const requiredStepAllMeasurement = (step) => {
|
|
7555
|
+
if (!step.allMeasurement) {
|
|
7556
|
+
throw new Error("Load Engine V2 assigned step is missing its all-outcome histogram state.");
|
|
7557
|
+
}
|
|
7558
|
+
return step.allMeasurement;
|
|
7559
|
+
};
|
|
7560
|
+
const groups = new Map();
|
|
7561
|
+
for (const declaredName of declaredNames) {
|
|
7562
|
+
const key = (0, cluster_js_1.buildLoadEngineV2StepIdentityKey)(scenarioIndex64, declaredName);
|
|
7563
|
+
groups.set(key.identity.toString("hex"), {
|
|
7564
|
+
identity: key.identity, display: key.display, routedToOther: false, steps: []
|
|
7565
|
+
});
|
|
7566
|
+
}
|
|
7567
|
+
for (const step of scenario.stepStats) {
|
|
7568
|
+
const resolved = (0, cluster_js_1.resolveLoadEngineV2StepIdentityKey)(scenarioIndex64, step.stepName, declaredNames);
|
|
7569
|
+
const key = resolved.identity.toString("hex");
|
|
7570
|
+
const group = groups.get(key) ?? {
|
|
7571
|
+
identity: resolved.identity,
|
|
7572
|
+
display: resolved.display,
|
|
7573
|
+
routedToOther: resolved.routedToOther,
|
|
7574
|
+
steps: []
|
|
7575
|
+
};
|
|
7576
|
+
group.steps.push(step);
|
|
7577
|
+
groups.set(key, group);
|
|
7578
|
+
}
|
|
7579
|
+
const reservedHex = (0, cluster_js_1.buildLoadEngineV2ReservedStepOtherIdentityKey)(scenarioIndex64).toString("hex");
|
|
7580
|
+
const declaredGroups = [...groups.entries()]
|
|
7581
|
+
.filter(([key]) => key !== reservedHex)
|
|
7582
|
+
.map(([, value]) => value)
|
|
7583
|
+
.sort((left, right) => Buffer.compare(left.identity, right.identity));
|
|
7584
|
+
for (const group of declaredGroups) {
|
|
7585
|
+
const ok = group.steps.length
|
|
7586
|
+
? aggregateMeasurementStats(group.steps.map((step) => step.ok), scenario.allRequestCount, scenario.durationMs, true)
|
|
7587
|
+
: undefined;
|
|
7588
|
+
const fail = group.steps.length
|
|
7589
|
+
? aggregateMeasurementStats(group.steps.map((step) => step.fail), scenario.allRequestCount, scenario.durationMs, true)
|
|
7590
|
+
: undefined;
|
|
7591
|
+
const all = group.steps.length
|
|
7592
|
+
? aggregateMeasurementStats(group.steps.map(requiredStepAllMeasurement), scenario.allRequestCount, scenario.durationMs, true)
|
|
7593
|
+
: undefined;
|
|
7594
|
+
appendMeasurement("step", scenarioIndex64, scenario.scenarioName, group.identity, group.display, ok, fail, all, false);
|
|
7595
|
+
}
|
|
7596
|
+
const other = groups.get(reservedHex);
|
|
7597
|
+
if (other?.steps.length) {
|
|
7598
|
+
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);
|
|
7599
|
+
continue;
|
|
7600
|
+
}
|
|
7601
|
+
}
|
|
7602
|
+
appendMeasurement("step", scenarioIndex64, scenario.scenarioName, (0, cluster_js_1.buildLoadEngineV2ReservedStepOtherIdentityKey)(scenarioIndex64), "<other>", undefined, undefined, undefined, true);
|
|
7603
|
+
}
|
|
7604
|
+
for (const segment of result.schedulerSegments ?? []) {
|
|
7605
|
+
const scenarioIndex64 = segment.scenarioIndex.toString();
|
|
7606
|
+
const simulationIndex64 = segment.simulationIndex.toString();
|
|
7607
|
+
for (const kind of ["decision", "start"]) {
|
|
7608
|
+
const seriesKind = kind === "decision" ? "scheduler-decision-lag" : "scheduler-start-lag";
|
|
7609
|
+
const identityKeyHex = (0, cluster_js_1.buildLoadEngineV2SchedulerIdentityKey)(kind, scenarioIndex64, simulationIndex64).toString("hex");
|
|
7610
|
+
const signed = result[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]?.find((record) => record.seriesKind === seriesKind
|
|
7611
|
+
&& record.scenarioIndex64 === scenarioIndex64
|
|
7612
|
+
&& record.identityKeyHex === identityKeyHex
|
|
7613
|
+
&& record.outcome === "none"
|
|
7614
|
+
&& record.unit === "microseconds");
|
|
7615
|
+
if (!distributions.some((record) => record.seriesKind === seriesKind
|
|
7616
|
+
&& record.scenarioIndex64 === scenarioIndex64
|
|
7617
|
+
&& record.identityKeyHex === identityKeyHex)) {
|
|
7618
|
+
distributions.push(signed ? cloneLoadEngineV2DistributionRecord(signed) : {
|
|
7619
|
+
seriesKind,
|
|
7620
|
+
scenarioIndex64, scenarioName: segment.scenarioName,
|
|
7621
|
+
identityKeyHex,
|
|
7622
|
+
outcome: "none", unit: "microseconds", histogram: emptyHistogram(),
|
|
7623
|
+
exactTotalDecimalOrEmpty: "0"
|
|
7624
|
+
});
|
|
7625
|
+
}
|
|
7626
|
+
}
|
|
7627
|
+
}
|
|
7628
|
+
return (0, load_engine_v2_js_1.serializeLoadEngineV2HistogramArtifact)({ distributions, measurementSummaries });
|
|
7629
|
+
}
|
|
7630
|
+
function cloneLoadEngineV2DistributionRecord(record) {
|
|
7631
|
+
return {
|
|
7632
|
+
...record,
|
|
7633
|
+
histogram: {
|
|
7634
|
+
...record.histogram,
|
|
7635
|
+
exactSamples64: [...record.histogram.exactSamples64],
|
|
7636
|
+
buckets: record.histogram.buckets.map((bucket) => ({ ...bucket }))
|
|
7637
|
+
},
|
|
7638
|
+
bands: record.bands?.map((band) => ({ ...band }))
|
|
7639
|
+
};
|
|
7640
|
+
}
|
|
7641
|
+
function mergeLoadEngineV2SchedulerDistributions(records) {
|
|
7642
|
+
const merged = new Map();
|
|
7643
|
+
for (const input of records) {
|
|
7644
|
+
if (input.seriesKind !== "scheduler-decision-lag" && input.seriesKind !== "scheduler-start-lag") {
|
|
7645
|
+
continue;
|
|
7646
|
+
}
|
|
7647
|
+
const key = [input.seriesKind, input.scenarioIndex64, input.identityKeyHex, input.outcome, input.unit].join("\0");
|
|
7648
|
+
const current = merged.get(key);
|
|
7649
|
+
if (!current) {
|
|
7650
|
+
merged.set(key, cloneLoadEngineV2DistributionRecord(input));
|
|
7651
|
+
continue;
|
|
7652
|
+
}
|
|
7653
|
+
if (current.scenarioName !== input.scenarioName) {
|
|
7654
|
+
throw new Error("Load Engine V2 scheduler histogram identities disagree across agents.");
|
|
7655
|
+
}
|
|
7656
|
+
const histogram = load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(current.histogram);
|
|
7657
|
+
histogram.merge(load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(input.histogram));
|
|
7658
|
+
current.histogram = histogram.toSidecar();
|
|
7659
|
+
current.exactTotalDecimalOrEmpty = histogram.toSidecar().exactTotal64;
|
|
7660
|
+
const bands = new Map();
|
|
7661
|
+
for (const band of [...(current.bands ?? []), ...(input.bands ?? [])]) {
|
|
7662
|
+
bands.set(band.bandId64, (bands.get(band.bandId64) ?? 0n) + BigInt(band.count64));
|
|
7663
|
+
}
|
|
7664
|
+
current.bands = Array.from(bands, ([bandId64, count]) => ({ bandId64, count64: count.toString() }));
|
|
7665
|
+
}
|
|
7666
|
+
return Array.from(merged.values());
|
|
7667
|
+
}
|
|
7668
|
+
function nodeStatsToClusterPayload(result, scenarioDeclarations, requireLoadEngineV2Histogram = false) {
|
|
7669
|
+
const histogramArtifactBase64 = requireLoadEngineV2Histogram
|
|
7670
|
+
? buildRuntimeLoadEngineV2HistogramArtifact(result, scenarioDeclarations).toString("base64")
|
|
7671
|
+
: undefined;
|
|
5950
7672
|
return {
|
|
5951
7673
|
allBytes: result.allBytes,
|
|
5952
7674
|
allRequestCount: result.allRequestCount,
|
|
@@ -5959,7 +7681,13 @@ function nodeStatsToClusterPayload(result) {
|
|
|
5959
7681
|
pluginsData: result.pluginsData,
|
|
5960
7682
|
nodeInfo: result.nodeInfo,
|
|
5961
7683
|
testInfo: result.testInfo,
|
|
5962
|
-
logFiles: [...(result.logFiles ?? [])]
|
|
7684
|
+
logFiles: [...(result.logFiles ?? [])],
|
|
7685
|
+
generatorWarnings: result.generatorWarnings,
|
|
7686
|
+
observationDeliveryStats: result.observationDeliveryStats,
|
|
7687
|
+
schedulerSegments: result.schedulerSegments,
|
|
7688
|
+
schedulerStats: result.schedulerStats,
|
|
7689
|
+
...(histogramArtifactBase64 ? { histogramArtifactBase64 } : {}),
|
|
7690
|
+
reportingComplete: result.reportingComplete
|
|
5963
7691
|
};
|
|
5964
7692
|
}
|
|
5965
7693
|
function toDetailedRunResultFromNodeStats(result, startedUtc, sinkErrors, policyErrors = []) {
|
|
@@ -5987,6 +7715,14 @@ function toDetailedRunResultFromNodeStats(result, startedUtc, sinkErrors, policy
|
|
|
5987
7715
|
policyErrors: policyErrors.map((value) => attachRuntimePolicyErrorAliases({ ...value })),
|
|
5988
7716
|
reportFiles: [...result.reportFiles],
|
|
5989
7717
|
logFiles: [...(result.logFiles ?? [])],
|
|
7718
|
+
generatorWarnings: (result.generatorWarnings ?? []).map(attachGeneratorWarningAliases),
|
|
7719
|
+
schedulerSegments: result.schedulerSegments?.map((segment) => ({ ...segment })),
|
|
7720
|
+
schedulerStats: result.schedulerStats
|
|
7721
|
+
? normalizeSchedulerStats(result.schedulerStats)
|
|
7722
|
+
: undefined,
|
|
7723
|
+
[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: result[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]?.map(cloneLoadEngineV2DistributionRecord),
|
|
7724
|
+
observationDeliveryStats: normalizeObservationDeliveryStats(result.observationDeliveryStats ?? emptyObservationDeliveryStats()),
|
|
7725
|
+
reportingComplete: result.reportingComplete ?? false,
|
|
5990
7726
|
correlationRows: buildDetailedCorrelationRows(),
|
|
5991
7727
|
failedCorrelationRows: buildDetailedFailedCorrelationRows()
|
|
5992
7728
|
});
|
|
@@ -6009,7 +7745,198 @@ function flattenMetricValues(metricStats) {
|
|
|
6009
7745
|
}))
|
|
6010
7746
|
];
|
|
6011
7747
|
}
|
|
6012
|
-
function
|
|
7748
|
+
function projectLoadEngineV2MeasurementsFromArtifact(sourceScenarios, artifact) {
|
|
7749
|
+
const distributions = new Map();
|
|
7750
|
+
for (const distribution of artifact.distributions) {
|
|
7751
|
+
const key = loadEngineV2DistributionProjectionKey(distribution.seriesKind, distribution.scenarioIndex64, distribution.identityKeyHex, distribution.outcome, distribution.unit);
|
|
7752
|
+
if (distributions.has(key)) {
|
|
7753
|
+
throw new Error("Load Engine V2 histogram artifact contains a duplicate distribution identity.");
|
|
7754
|
+
}
|
|
7755
|
+
distributions.set(key, distribution);
|
|
7756
|
+
}
|
|
7757
|
+
const scenarioSummaries = artifact.measurementSummaries
|
|
7758
|
+
.filter((summary) => summary.seriesKind === "scenario")
|
|
7759
|
+
.sort((left, right) => compareLoadEngineV2Decimal(left.scenarioIndex64, right.scenarioIndex64));
|
|
7760
|
+
if (scenarioSummaries.length !== sourceScenarios.length) {
|
|
7761
|
+
throw new Error("Load Engine V2 histogram artifact scenario summaries do not reconcile with the scheduler snapshot.");
|
|
7762
|
+
}
|
|
7763
|
+
const sourceByName = new Map();
|
|
7764
|
+
for (const scenario of sourceScenarios) {
|
|
7765
|
+
if (!scenario.scenarioName || sourceByName.has(scenario.scenarioName)) {
|
|
7766
|
+
throw new Error("Load Engine V2 scheduler snapshot scenario identities are empty or duplicated.");
|
|
7767
|
+
}
|
|
7768
|
+
sourceByName.set(scenario.scenarioName, scenario);
|
|
7769
|
+
}
|
|
7770
|
+
return scenarioSummaries.map((summary) => {
|
|
7771
|
+
const source = sourceByName.get(summary.scenarioName);
|
|
7772
|
+
if (!source) {
|
|
7773
|
+
throw new Error("Load Engine V2 histogram artifact scenario identity is absent from the scheduler snapshot.");
|
|
7774
|
+
}
|
|
7775
|
+
const expectedIdentity = (0, cluster_js_1.buildLoadEngineV2ScenarioIdentityKey)(summary.scenarioIndex64).toString("hex");
|
|
7776
|
+
if (summary.identityKeyHex !== expectedIdentity) {
|
|
7777
|
+
throw new Error("Load Engine V2 histogram artifact scenario identity is not canonical.");
|
|
7778
|
+
}
|
|
7779
|
+
const observationCount = loadEngineV2SafeNumber(summary.observationCount64, "scenario observation count");
|
|
7780
|
+
const successCount = loadEngineV2SafeNumber(summary.successCount64, "scenario success count");
|
|
7781
|
+
const failureCount = loadEngineV2SafeNumber(summary.failureCount64, "scenario failure count");
|
|
7782
|
+
if (source.allRequestCount !== observationCount
|
|
7783
|
+
|| source.allOkCount !== successCount
|
|
7784
|
+
|| source.allFailCount !== failureCount) {
|
|
7785
|
+
throw new Error("Load Engine V2 histogram artifact scenario counts do not reconcile with the scheduler snapshot.");
|
|
7786
|
+
}
|
|
7787
|
+
const ok = projectLoadEngineV2Measurement(summary, "ok", distributions, observationCount, source.durationMs, [source.ok]);
|
|
7788
|
+
const fail = projectLoadEngineV2Measurement(summary, "fail", distributions, observationCount, source.durationMs, [source.fail]);
|
|
7789
|
+
const allMeasurement = projectLoadEngineV2Measurement(summary, "all", distributions, observationCount, source.durationMs, [source.ok, source.fail]);
|
|
7790
|
+
const stepSummaries = artifact.measurementSummaries
|
|
7791
|
+
.filter((candidate) => candidate.seriesKind === "step"
|
|
7792
|
+
&& candidate.scenarioIndex64 === summary.scenarioIndex64
|
|
7793
|
+
&& candidate.scenarioName === summary.scenarioName)
|
|
7794
|
+
.sort((left, right) => Buffer.compare(Buffer.from(left.identityKeyHex, "hex"), Buffer.from(right.identityKeyHex, "hex")));
|
|
7795
|
+
const stepIdentitySet = new Set(stepSummaries.map((candidate) => candidate.identityKeyHex));
|
|
7796
|
+
const reservedOtherIdentity = (0, cluster_js_1.buildLoadEngineV2ReservedStepOtherIdentityKey)(summary.scenarioIndex64).toString("hex");
|
|
7797
|
+
const stepStats = stepSummaries.map((stepSummary, sortIndex) => {
|
|
7798
|
+
const matchingSourceSteps = source.stepStats.filter((step) => {
|
|
7799
|
+
const observedIdentity = (0, cluster_js_1.buildLoadEngineV2StepIdentityKey)(summary.scenarioIndex64, step.stepName).identity.toString("hex");
|
|
7800
|
+
return stepSummary.identityKeyHex === reservedOtherIdentity
|
|
7801
|
+
? !stepIdentitySet.has(observedIdentity)
|
|
7802
|
+
: observedIdentity === stepSummary.identityKeyHex;
|
|
7803
|
+
});
|
|
7804
|
+
const stepObservationCount = loadEngineV2SafeNumber(stepSummary.observationCount64, "step observation count");
|
|
7805
|
+
const stepOk = projectLoadEngineV2Measurement(stepSummary, "ok", distributions, observationCount, source.durationMs, matchingSourceSteps.map((step) => step.ok));
|
|
7806
|
+
const stepFail = projectLoadEngineV2Measurement(stepSummary, "fail", distributions, observationCount, source.durationMs, matchingSourceSteps.map((step) => step.fail));
|
|
7807
|
+
const stepAll = projectLoadEngineV2Measurement(stepSummary, "all", distributions, observationCount, source.durationMs, matchingSourceSteps.flatMap((step) => [step.ok, step.fail]));
|
|
7808
|
+
const totalLatencyMs = stepAll.latency.meanMs * stepObservationCount;
|
|
7809
|
+
return {
|
|
7810
|
+
scenarioName: summary.scenarioName,
|
|
7811
|
+
stepName: stepSummary.display,
|
|
7812
|
+
okCount: stepOk.request.count,
|
|
7813
|
+
failCount: stepFail.request.count,
|
|
7814
|
+
requestCount: stepObservationCount,
|
|
7815
|
+
totalBytes: stepAll.dataTransfer.allBytes,
|
|
7816
|
+
totalLatencyMs,
|
|
7817
|
+
avgLatencyMs: stepObservationCount > 0 ? totalLatencyMs / stepObservationCount : 0,
|
|
7818
|
+
minLatencyMs: stepAll.latency.minMs,
|
|
7819
|
+
maxLatencyMs: stepAll.latency.maxMs,
|
|
7820
|
+
statusCodes: aggregateStatusCodeCounts(stepOk.statusCodes, stepFail.statusCodes),
|
|
7821
|
+
ok: stepOk,
|
|
7822
|
+
fail: stepFail,
|
|
7823
|
+
allMeasurement: stepAll,
|
|
7824
|
+
sortIndex
|
|
7825
|
+
};
|
|
7826
|
+
});
|
|
7827
|
+
const totalLatencyMs = allMeasurement.latency.meanMs * observationCount;
|
|
7828
|
+
const projected = {
|
|
7829
|
+
scenarioName: summary.scenarioName,
|
|
7830
|
+
allRequestCount: observationCount,
|
|
7831
|
+
allOkCount: successCount,
|
|
7832
|
+
allFailCount: failureCount,
|
|
7833
|
+
totalBytes: allMeasurement.dataTransfer.allBytes,
|
|
7834
|
+
totalLatencyMs,
|
|
7835
|
+
avgLatencyMs: observationCount > 0 ? totalLatencyMs / observationCount : 0,
|
|
7836
|
+
minLatencyMs: allMeasurement.latency.minMs,
|
|
7837
|
+
maxLatencyMs: allMeasurement.latency.maxMs,
|
|
7838
|
+
statusCodes: aggregateStatusCodeCounts(ok.statusCodes, fail.statusCodes),
|
|
7839
|
+
allMeasurement,
|
|
7840
|
+
allBytes: allMeasurement.dataTransfer.allBytes,
|
|
7841
|
+
currentOperation: source.currentOperation,
|
|
7842
|
+
durationMs: source.durationMs,
|
|
7843
|
+
ok,
|
|
7844
|
+
fail,
|
|
7845
|
+
loadSimulationStats: { ...source.loadSimulationStats },
|
|
7846
|
+
sortIndex: loadEngineV2SafeNumber(summary.scenarioIndex64, "scenario index"),
|
|
7847
|
+
stepStats,
|
|
7848
|
+
findStepStats: (stepName) => stepStats.find((step) => step.stepName === stepName),
|
|
7849
|
+
getStepStats: (stepName) => {
|
|
7850
|
+
const step = stepStats.find((candidate) => candidate.stepName === stepName);
|
|
7851
|
+
if (!step)
|
|
7852
|
+
throw new Error(`Step stats not found: ${stepName}`);
|
|
7853
|
+
return step;
|
|
7854
|
+
}
|
|
7855
|
+
};
|
|
7856
|
+
return attachScenarioStatsAliases(projected);
|
|
7857
|
+
});
|
|
7858
|
+
}
|
|
7859
|
+
function projectLoadEngineV2Measurement(summary, outcome, distributions, allRequestCount, durationMs, sourceMeasurements) {
|
|
7860
|
+
const expectedCount64 = outcome === "ok"
|
|
7861
|
+
? summary.successCount64
|
|
7862
|
+
: outcome === "fail"
|
|
7863
|
+
? summary.failureCount64
|
|
7864
|
+
: summary.observationCount64;
|
|
7865
|
+
const latency = requireLoadEngineV2MeasurementDistribution(summary, outcome, "microseconds", distributions);
|
|
7866
|
+
const size = requireLoadEngineV2MeasurementDistribution(summary, outcome, "bytes", distributions);
|
|
7867
|
+
if (latency.histogram.count64 !== expectedCount64 || size.histogram.count64 !== expectedCount64) {
|
|
7868
|
+
throw new Error("Load Engine V2 histogram distribution counts do not reconcile with its measurement summary.");
|
|
7869
|
+
}
|
|
7870
|
+
const bands = new Map();
|
|
7871
|
+
for (const band of latency.bands ?? []) {
|
|
7872
|
+
if (bands.has(band.bandId64)) {
|
|
7873
|
+
throw new Error("Load Engine V2 latency distribution contains a duplicate band.");
|
|
7874
|
+
}
|
|
7875
|
+
bands.set(band.bandId64, BigInt(band.count64));
|
|
7876
|
+
}
|
|
7877
|
+
if (bands.size !== 3 || !bands.has("0") || !bands.has("1") || !bands.has("2")
|
|
7878
|
+
|| [...bands.values()].reduce((sum, value) => sum + value, 0n) !== BigInt(expectedCount64)) {
|
|
7879
|
+
throw new Error("Load Engine V2 latency distribution bands do not reconcile with its count.");
|
|
7880
|
+
}
|
|
7881
|
+
const sourceStatuses = new Map();
|
|
7882
|
+
for (const measurement of sourceMeasurements) {
|
|
7883
|
+
for (const status of measurement.statusCodes) {
|
|
7884
|
+
const identity = (0, cluster_js_1.buildLoadEngineV2StatusIdentityKey)(status.statusCode, status.message);
|
|
7885
|
+
if (identity)
|
|
7886
|
+
sourceStatuses.set(identity.identity.toString("hex"), status);
|
|
7887
|
+
}
|
|
7888
|
+
}
|
|
7889
|
+
const statusCodes = new Map();
|
|
7890
|
+
const outcomeSummaries = outcome === "all"
|
|
7891
|
+
? summary.outcomes
|
|
7892
|
+
: summary.outcomes.filter((candidate) => candidate.outcome === outcome);
|
|
7893
|
+
for (const outcomeSummary of outcomeSummaries) {
|
|
7894
|
+
for (const status of outcomeSummary.statuses) {
|
|
7895
|
+
const source = sourceStatuses.get(status.statusIdentityKeyHex);
|
|
7896
|
+
statusCodes.set(`${outcomeSummary.outcome}\0${status.statusIdentityKeyHex}`, {
|
|
7897
|
+
statusCode: source?.statusCode ?? status.display,
|
|
7898
|
+
message: source?.message ?? "",
|
|
7899
|
+
isError: source?.isError ?? outcomeSummary.outcome === "fail",
|
|
7900
|
+
count: loadEngineV2SafeNumber(status.count64, "status count")
|
|
7901
|
+
});
|
|
7902
|
+
}
|
|
7903
|
+
}
|
|
7904
|
+
const expectedCount = loadEngineV2SafeNumber(expectedCount64, "measurement count");
|
|
7905
|
+
const sizeHistogram = load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(size.histogram);
|
|
7906
|
+
return buildHistogramMeasurement({
|
|
7907
|
+
count: expectedCount,
|
|
7908
|
+
allBytes: loadEngineV2SafeNumber(sizeHistogram.exactTotal.toString(), "measurement byte total"),
|
|
7909
|
+
latency: load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(latency.histogram),
|
|
7910
|
+
size: sizeHistogram,
|
|
7911
|
+
statusCodes,
|
|
7912
|
+
lessOrEq800: loadEngineV2SafeNumber((bands.get("0") ?? 0n).toString(), "latency band count"),
|
|
7913
|
+
more800Less1200: loadEngineV2SafeNumber((bands.get("1") ?? 0n).toString(), "latency band count"),
|
|
7914
|
+
moreOrEq1200: loadEngineV2SafeNumber((bands.get("2") ?? 0n).toString(), "latency band count")
|
|
7915
|
+
}, allRequestCount, durationMs);
|
|
7916
|
+
}
|
|
7917
|
+
function requireLoadEngineV2MeasurementDistribution(summary, outcome, unit, distributions) {
|
|
7918
|
+
const distribution = distributions.get(loadEngineV2DistributionProjectionKey(summary.seriesKind, summary.scenarioIndex64, summary.identityKeyHex, outcome, unit));
|
|
7919
|
+
if (!distribution || distribution.scenarioName !== summary.scenarioName) {
|
|
7920
|
+
throw new Error("Load Engine V2 measurement summary is missing its canonical histogram distribution.");
|
|
7921
|
+
}
|
|
7922
|
+
return distribution;
|
|
7923
|
+
}
|
|
7924
|
+
function loadEngineV2DistributionProjectionKey(seriesKind, scenarioIndex64, identityKeyHex, outcome, unit) {
|
|
7925
|
+
return [seriesKind, scenarioIndex64, identityKeyHex, outcome, unit].join("\0");
|
|
7926
|
+
}
|
|
7927
|
+
function compareLoadEngineV2Decimal(left, right) {
|
|
7928
|
+
const a = BigInt(left);
|
|
7929
|
+
const b = BigInt(right);
|
|
7930
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
7931
|
+
}
|
|
7932
|
+
function loadEngineV2SafeNumber(value, field) {
|
|
7933
|
+
const parsed = BigInt(value);
|
|
7934
|
+
if (parsed < 0n || parsed > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
7935
|
+
throw new Error(`Load Engine V2 ${field} exceeds the JavaScript safe integer range.`);
|
|
7936
|
+
}
|
|
7937
|
+
return Number(parsed);
|
|
7938
|
+
}
|
|
7939
|
+
function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo, requireHistogramArtifact = false) {
|
|
6013
7940
|
const completedUtc = new Date().toISOString();
|
|
6014
7941
|
if (!result.success || !result.stats) {
|
|
6015
7942
|
return attachNodeStatsAliases({
|
|
@@ -6042,12 +7969,12 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
|
|
|
6042
7969
|
isFailed: true,
|
|
6043
7970
|
errorCount: 1,
|
|
6044
7971
|
exceptionMessage: result.error ?? "Agent execution failed."
|
|
6045
|
-
}]
|
|
7972
|
+
}],
|
|
7973
|
+
reportingComplete: false
|
|
6046
7974
|
});
|
|
6047
7975
|
}
|
|
6048
7976
|
const metrics = normalizeMetricStatsPayload(result.stats.metrics, result.stats.durationMs ?? 0);
|
|
6049
|
-
|
|
6050
|
-
const stepStats = scenarioStats.flatMap((value) => value.stepStats);
|
|
7977
|
+
let scenarioStats = normalizeScenarioStatsPayload(result.stats.scenarioStats);
|
|
6051
7978
|
const thresholds = normalizeThresholdPayload(result.stats.thresholds);
|
|
6052
7979
|
const pluginsData = normalizePluginsPayload(result.stats.pluginsData);
|
|
6053
7980
|
const nodeInfo = {
|
|
@@ -6060,6 +7987,17 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
|
|
|
6060
7987
|
...testInfo,
|
|
6061
7988
|
...(result.stats.testInfo ?? {})
|
|
6062
7989
|
};
|
|
7990
|
+
const histogramArtifactBase64 = String(result.stats.histogramArtifactBase64 ?? "");
|
|
7991
|
+
if (requireHistogramArtifact && !histogramArtifactBase64) {
|
|
7992
|
+
throw new Error("Load Engine V2 result omits the mandatory LS-H1 histogram artifact.");
|
|
7993
|
+
}
|
|
7994
|
+
const histogramArtifact = histogramArtifactBase64
|
|
7995
|
+
? (0, load_engine_v2_js_1.parseLoadEngineV2HistogramArtifact)(Buffer.from(histogramArtifactBase64, "base64"))
|
|
7996
|
+
: undefined;
|
|
7997
|
+
if (histogramArtifact) {
|
|
7998
|
+
scenarioStats = projectLoadEngineV2MeasurementsFromArtifact(scenarioStats, histogramArtifact);
|
|
7999
|
+
}
|
|
8000
|
+
const stepStats = scenarioStats.flatMap((value) => value.stepStats);
|
|
6063
8001
|
return attachNodeStatsAliases({
|
|
6064
8002
|
startedUtc: testInfo.createdUtc,
|
|
6065
8003
|
completedUtc,
|
|
@@ -6082,6 +8020,17 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
|
|
|
6082
8020
|
sinkErrors: [],
|
|
6083
8021
|
reportFiles: [],
|
|
6084
8022
|
logFiles: normalizeAliasStringArray(result.stats.logFiles),
|
|
8023
|
+
generatorWarnings: (result.stats.generatorWarnings ?? []).map(attachGeneratorWarningAliases),
|
|
8024
|
+
schedulerSegments: (result.stats.schedulerSegments ?? []).map(normalizeSchedulerSegment),
|
|
8025
|
+
schedulerStats: result.stats.schedulerStats
|
|
8026
|
+
? normalizeSchedulerStats(result.stats.schedulerStats)
|
|
8027
|
+
: undefined,
|
|
8028
|
+
[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: histogramArtifact?.distributions
|
|
8029
|
+
.filter((record) => record.seriesKind === "scheduler-decision-lag"
|
|
8030
|
+
|| record.seriesKind === "scheduler-start-lag")
|
|
8031
|
+
.map(cloneLoadEngineV2DistributionRecord),
|
|
8032
|
+
observationDeliveryStats: normalizeObservationDeliveryStats(result.stats.observationDeliveryStats ?? emptyObservationDeliveryStats()),
|
|
8033
|
+
reportingComplete: result.stats.reportingComplete ?? false,
|
|
6085
8034
|
findScenarioStats: (scenarioName) => scenarioStats.find((value) => value.scenarioName === scenarioName),
|
|
6086
8035
|
getScenarioStats: (scenarioName) => {
|
|
6087
8036
|
const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
|
|
@@ -6092,7 +8041,46 @@ function clusterNodeResultToNodeStats(result, testInfo, fallbackNodeInfo) {
|
|
|
6092
8041
|
}
|
|
6093
8042
|
});
|
|
6094
8043
|
}
|
|
6095
|
-
function
|
|
8044
|
+
function emptyObservationDeliveryStats() {
|
|
8045
|
+
return normalizeObservationDeliveryStats({
|
|
8046
|
+
lastBatchSequence64: "-1",
|
|
8047
|
+
capturedCount64: "0",
|
|
8048
|
+
deliveredCount64: "0",
|
|
8049
|
+
droppedBufferCount64: "0",
|
|
8050
|
+
droppedSinkCount64: "0"
|
|
8051
|
+
});
|
|
8052
|
+
}
|
|
8053
|
+
function aggregateObservationDeliveryStats(nodes) {
|
|
8054
|
+
let lastBatchSequence = -1n;
|
|
8055
|
+
let captured = 0n;
|
|
8056
|
+
let delivered = 0n;
|
|
8057
|
+
let droppedBuffer = 0n;
|
|
8058
|
+
let droppedSink = 0n;
|
|
8059
|
+
for (const node of nodes) {
|
|
8060
|
+
const stats = node.observationDeliveryStats ?? emptyObservationDeliveryStats();
|
|
8061
|
+
lastBatchSequence = maxBigInt(lastBatchSequence, parseObservationDecimal(stats.lastBatchSequence64, -1n));
|
|
8062
|
+
captured += parseObservationDecimal(stats.capturedCount64);
|
|
8063
|
+
delivered += parseObservationDecimal(stats.deliveredCount64);
|
|
8064
|
+
droppedBuffer += parseObservationDecimal(stats.droppedBufferCount64);
|
|
8065
|
+
droppedSink += parseObservationDecimal(stats.droppedSinkCount64);
|
|
8066
|
+
}
|
|
8067
|
+
return normalizeObservationDeliveryStats({
|
|
8068
|
+
lastBatchSequence64: lastBatchSequence.toString(),
|
|
8069
|
+
capturedCount64: captured.toString(),
|
|
8070
|
+
deliveredCount64: delivered.toString(),
|
|
8071
|
+
droppedBufferCount64: droppedBuffer.toString(),
|
|
8072
|
+
droppedSinkCount64: droppedSink.toString()
|
|
8073
|
+
});
|
|
8074
|
+
}
|
|
8075
|
+
function parseObservationDecimal(value, fallback = 0n) {
|
|
8076
|
+
try {
|
|
8077
|
+
return BigInt(value);
|
|
8078
|
+
}
|
|
8079
|
+
catch {
|
|
8080
|
+
return fallback;
|
|
8081
|
+
}
|
|
8082
|
+
}
|
|
8083
|
+
function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes, requireHistograms = false) {
|
|
6096
8084
|
if (!nodes.length) {
|
|
6097
8085
|
return buildEmptyNodeStats({
|
|
6098
8086
|
startedUtc: testInfo.createdUtc,
|
|
@@ -6101,12 +8089,22 @@ function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes) {
|
|
|
6101
8089
|
testInfo
|
|
6102
8090
|
});
|
|
6103
8091
|
}
|
|
6104
|
-
const scenarioStats = aggregateScenarioStats(nodes);
|
|
8092
|
+
const scenarioStats = aggregateScenarioStats(nodes, requireHistograms);
|
|
6105
8093
|
const stepStats = scenarioStats.flatMap((value) => value.stepStats);
|
|
6106
8094
|
const metrics = aggregateMetricStats(nodes);
|
|
6107
8095
|
const thresholds = aggregateThresholds(nodes);
|
|
6108
8096
|
const pluginsData = aggregatePluginsData(nodes);
|
|
6109
8097
|
const completedUtc = new Date().toISOString();
|
|
8098
|
+
const schedulerSegments = nodes.flatMap((value) => value.schedulerSegments ?? []);
|
|
8099
|
+
const schedulerStatsRows = nodes.flatMap((value) => value.schedulerStats ? [value.schedulerStats] : []);
|
|
8100
|
+
const schedulerStats = schedulerSegments.length || schedulerStatsRows.length
|
|
8101
|
+
? {
|
|
8102
|
+
configuredMaxInFlight: schedulerStatsRows.reduce((maximum, value) => Math.max(maximum, value.configuredMaxInFlight), 0),
|
|
8103
|
+
maxInFlightObserved: schedulerStatsRows.reduce((maximum, value) => Math.max(maximum, value.maxInFlightObserved), 0),
|
|
8104
|
+
currentInFlight: schedulerStatsRows.reduce((sum, value) => sum + value.currentInFlight, 0),
|
|
8105
|
+
segments: schedulerSegments
|
|
8106
|
+
}
|
|
8107
|
+
: undefined;
|
|
6110
8108
|
return attachNodeStatsAliases({
|
|
6111
8109
|
startedUtc: testInfo.createdUtc,
|
|
6112
8110
|
completedUtc,
|
|
@@ -6129,6 +8127,12 @@ function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes) {
|
|
|
6129
8127
|
sinkErrors: [],
|
|
6130
8128
|
reportFiles: [],
|
|
6131
8129
|
logFiles: mergeStringArrays(...nodes.map((value) => value.logFiles ?? [])),
|
|
8130
|
+
generatorWarnings: nodes.flatMap((value) => value.generatorWarnings ?? []),
|
|
8131
|
+
...(schedulerSegments.length ? { schedulerSegments } : {}),
|
|
8132
|
+
...(schedulerStats ? { schedulerStats } : {}),
|
|
8133
|
+
[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS]: mergeLoadEngineV2SchedulerDistributions(nodes.flatMap((value) => value[LOAD_ENGINE_V2_SCHEDULER_DISTRIBUTIONS] ?? [])),
|
|
8134
|
+
observationDeliveryStats: aggregateObservationDeliveryStats(nodes),
|
|
8135
|
+
reportingComplete: nodes.every((value) => value.reportingComplete ?? false),
|
|
6132
8136
|
findScenarioStats: (scenarioName) => scenarioStats.find((value) => value.scenarioName === scenarioName),
|
|
6133
8137
|
getScenarioStats: (scenarioName) => {
|
|
6134
8138
|
const value = scenarioStats.find((scenario) => scenario.scenarioName === scenarioName);
|
|
@@ -6139,7 +8143,7 @@ function aggregateNodeStats(testInfo, coordinatorNodeInfo, nodes) {
|
|
|
6139
8143
|
}
|
|
6140
8144
|
});
|
|
6141
8145
|
}
|
|
6142
|
-
function aggregateScenarioStats(nodes) {
|
|
8146
|
+
function aggregateScenarioStats(nodes, requireHistograms = false) {
|
|
6143
8147
|
const grouped = new Map();
|
|
6144
8148
|
for (const node of nodes) {
|
|
6145
8149
|
for (const scenario of node.scenarioStats) {
|
|
@@ -6153,9 +8157,12 @@ function aggregateScenarioStats(nodes) {
|
|
|
6153
8157
|
.map((items) => {
|
|
6154
8158
|
const allRequestCount = items.reduce((sum, value) => sum + value.allRequestCount, 0);
|
|
6155
8159
|
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
|
|
8160
|
+
const ok = aggregateMeasurementStats(items.map((value) => value.ok), allRequestCount, durationMs, requireHistograms);
|
|
8161
|
+
const fail = aggregateMeasurementStats(items.map((value) => value.fail), allRequestCount, durationMs, requireHistograms);
|
|
8162
|
+
const allMeasurement = requireHistograms
|
|
8163
|
+
? aggregateMeasurementStats([ok, fail], allRequestCount, durationMs, true)
|
|
8164
|
+
: undefined;
|
|
8165
|
+
const stepStats = aggregateStepStats(items, requireHistograms);
|
|
6159
8166
|
const scenarioName = items[0]?.scenarioName ?? "";
|
|
6160
8167
|
const currentOperation = selectScenarioOperation(items.map((value) => value.currentOperation));
|
|
6161
8168
|
const loadSimulationStats = items.find((value) => value.loadSimulationStats.simulationName)?.loadSimulationStats ?? {
|
|
@@ -6180,6 +8187,7 @@ function aggregateScenarioStats(nodes) {
|
|
|
6180
8187
|
durationMs,
|
|
6181
8188
|
ok,
|
|
6182
8189
|
fail,
|
|
8190
|
+
...(allMeasurement ? { allMeasurement } : {}),
|
|
6183
8191
|
loadSimulationStats,
|
|
6184
8192
|
sortIndex: Math.min(...items.map((value) => value.sortIndex)),
|
|
6185
8193
|
stepStats,
|
|
@@ -6195,7 +8203,7 @@ function aggregateScenarioStats(nodes) {
|
|
|
6195
8203
|
return attachScenarioStatsAliases(scenario);
|
|
6196
8204
|
});
|
|
6197
8205
|
}
|
|
6198
|
-
function aggregateStepStats(scenarios) {
|
|
8206
|
+
function aggregateStepStats(scenarios, requireHistograms = false) {
|
|
6199
8207
|
const grouped = new Map();
|
|
6200
8208
|
for (const scenario of scenarios) {
|
|
6201
8209
|
for (const step of scenario.stepStats) {
|
|
@@ -6209,8 +8217,11 @@ function aggregateStepStats(scenarios) {
|
|
|
6209
8217
|
return Array.from(grouped.values())
|
|
6210
8218
|
.sort((left, right) => Math.min(...left.map((value) => value.sortIndex)) - Math.min(...right.map((value) => value.sortIndex)))
|
|
6211
8219
|
.map((items) => {
|
|
6212
|
-
const ok = aggregateMeasurementStats(items.map((value) => value.ok), allScenarioRequests, scenarioDurationMs);
|
|
6213
|
-
const fail = aggregateMeasurementStats(items.map((value) => value.fail), allScenarioRequests, scenarioDurationMs);
|
|
8220
|
+
const ok = aggregateMeasurementStats(items.map((value) => value.ok), allScenarioRequests, scenarioDurationMs, requireHistograms);
|
|
8221
|
+
const fail = aggregateMeasurementStats(items.map((value) => value.fail), allScenarioRequests, scenarioDurationMs, requireHistograms);
|
|
8222
|
+
const allMeasurement = requireHistograms
|
|
8223
|
+
? aggregateMeasurementStats([ok, fail], allScenarioRequests, scenarioDurationMs, true)
|
|
8224
|
+
: undefined;
|
|
6214
8225
|
const requestCount = ok.request.count + fail.request.count;
|
|
6215
8226
|
return {
|
|
6216
8227
|
scenarioName: items[0]?.scenarioName ?? "",
|
|
@@ -6226,14 +8237,53 @@ function aggregateStepStats(scenarios) {
|
|
|
6226
8237
|
statusCodes: aggregateStatusCodeCounts(ok.statusCodes, fail.statusCodes),
|
|
6227
8238
|
ok,
|
|
6228
8239
|
fail,
|
|
8240
|
+
...(allMeasurement ? { allMeasurement } : {}),
|
|
6229
8241
|
sortIndex: Math.min(...items.map((value) => value.sortIndex))
|
|
6230
8242
|
};
|
|
6231
8243
|
});
|
|
6232
8244
|
}
|
|
6233
|
-
function aggregateMeasurementStats(measurements, allRequestCount, durationMs) {
|
|
8245
|
+
function aggregateMeasurementStats(measurements, allRequestCount, durationMs, requireHistograms = false) {
|
|
6234
8246
|
if (!measurements.length) {
|
|
6235
8247
|
return buildMeasurementPlaceholder(0, allRequestCount, durationMs);
|
|
6236
8248
|
}
|
|
8249
|
+
if (measurements.every((measurement) => measurement.histogramSidecar)) {
|
|
8250
|
+
const latency = load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(measurements[0].histogramSidecar.latency);
|
|
8251
|
+
const size = load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(measurements[0].histogramSidecar.size);
|
|
8252
|
+
for (const measurement of measurements.slice(1)) {
|
|
8253
|
+
latency.merge(load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(measurement.histogramSidecar.latency));
|
|
8254
|
+
size.merge(load_engine_v2_js_1.LoadStrikeHistogramV1.fromSidecar(measurement.histogramSidecar.size));
|
|
8255
|
+
}
|
|
8256
|
+
const statusCodes = new Map();
|
|
8257
|
+
for (const measurement of measurements) {
|
|
8258
|
+
for (const status of measurement.statusCodes) {
|
|
8259
|
+
const key = `${status.statusCode}|${status.message}|${status.isError ? "1" : "0"}`;
|
|
8260
|
+
const current = statusCodes.get(key);
|
|
8261
|
+
if (current)
|
|
8262
|
+
current.count += status.count;
|
|
8263
|
+
else
|
|
8264
|
+
statusCodes.set(key, {
|
|
8265
|
+
statusCode: status.statusCode,
|
|
8266
|
+
message: status.message,
|
|
8267
|
+
isError: status.isError,
|
|
8268
|
+
count: status.count
|
|
8269
|
+
});
|
|
8270
|
+
}
|
|
8271
|
+
}
|
|
8272
|
+
const sumSidecar = (key) => Number(measurements.reduce((sum, measurement) => sum + BigInt(measurement.histogramSidecar[key]), 0n));
|
|
8273
|
+
return buildHistogramMeasurement({
|
|
8274
|
+
count: Number(latency.count),
|
|
8275
|
+
allBytes: measurements.reduce((sum, measurement) => sum + measurement.dataTransfer.allBytes, 0),
|
|
8276
|
+
latency,
|
|
8277
|
+
size,
|
|
8278
|
+
statusCodes,
|
|
8279
|
+
lessOrEq800: sumSidecar("lessOrEq80064"),
|
|
8280
|
+
more800Less1200: sumSidecar("more800Less120064"),
|
|
8281
|
+
moreOrEq1200: sumSidecar("moreOrEq120064")
|
|
8282
|
+
}, allRequestCount, durationMs);
|
|
8283
|
+
}
|
|
8284
|
+
if (requireHistograms) {
|
|
8285
|
+
throw new Error("Load Engine V2 aggregation requires canonical histogram state for every node measurement.");
|
|
8286
|
+
}
|
|
6237
8287
|
const weights = measurements.map((value) => value.request.count);
|
|
6238
8288
|
const totalCount = measurements.reduce((sum, value) => sum + value.request.count, 0);
|
|
6239
8289
|
return {
|
|
@@ -7449,11 +9499,23 @@ function readRuntimeTrackingId(payload, selector) {
|
|
|
7449
9499
|
return null;
|
|
7450
9500
|
}
|
|
7451
9501
|
let current = body;
|
|
7452
|
-
|
|
9502
|
+
const path = selector.slice("json:".length).trim().replace(/^\$\./, "");
|
|
9503
|
+
let segments;
|
|
9504
|
+
try {
|
|
9505
|
+
segments = runtimeSafeJsonPathSegments(path);
|
|
9506
|
+
}
|
|
9507
|
+
catch {
|
|
9508
|
+
return null;
|
|
9509
|
+
}
|
|
9510
|
+
for (const segment of segments) {
|
|
7453
9511
|
if (!current || typeof current !== "object" || Array.isArray(current)) {
|
|
7454
9512
|
return null;
|
|
7455
9513
|
}
|
|
7456
|
-
|
|
9514
|
+
const record = current;
|
|
9515
|
+
if (!Object.prototype.hasOwnProperty.call(record, segment)) {
|
|
9516
|
+
return null;
|
|
9517
|
+
}
|
|
9518
|
+
current = runtimeReadOwnJsonProperty(record, segment);
|
|
7457
9519
|
}
|
|
7458
9520
|
return current == null ? null : String(current);
|
|
7459
9521
|
}
|
|
@@ -7477,23 +9539,57 @@ function runtimeParseBodyAsObject(body) {
|
|
|
7477
9539
|
}
|
|
7478
9540
|
}
|
|
7479
9541
|
function setRuntimeJsonPathValue(body, path, value) {
|
|
7480
|
-
const target =
|
|
7481
|
-
|
|
7482
|
-
: {};
|
|
7483
|
-
const segments = path.split(".").filter(Boolean);
|
|
9542
|
+
const target = runtimeCloneJsonRecord(body);
|
|
9543
|
+
const segments = runtimeSafeJsonPathSegments(path);
|
|
7484
9544
|
if (!segments.length) {
|
|
7485
9545
|
return target;
|
|
7486
9546
|
}
|
|
7487
9547
|
let current = target;
|
|
7488
9548
|
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
7489
9549
|
const segment = segments[i];
|
|
7490
|
-
const next = current
|
|
9550
|
+
const next = runtimeReadOwnJsonProperty(current, segment);
|
|
9551
|
+
let child;
|
|
7491
9552
|
if (!next || typeof next !== "object" || Array.isArray(next)) {
|
|
7492
|
-
|
|
9553
|
+
child = {};
|
|
9554
|
+
}
|
|
9555
|
+
else {
|
|
9556
|
+
child = runtimeCloneJsonRecord(next);
|
|
7493
9557
|
}
|
|
7494
|
-
current
|
|
9558
|
+
runtimeDefineJsonProperty(current, segment, child);
|
|
9559
|
+
current = child;
|
|
9560
|
+
}
|
|
9561
|
+
runtimeDefineJsonProperty(current, segments[segments.length - 1], value);
|
|
9562
|
+
return target;
|
|
9563
|
+
}
|
|
9564
|
+
const FORBIDDEN_RUNTIME_JSON_PATH_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
9565
|
+
function runtimeSafeJsonPathSegments(path) {
|
|
9566
|
+
const segments = path.split(".").filter(Boolean);
|
|
9567
|
+
const forbidden = segments.find((segment) => FORBIDDEN_RUNTIME_JSON_PATH_SEGMENTS.has(segment));
|
|
9568
|
+
if (forbidden) {
|
|
9569
|
+
throw new Error(`Tracking selector contains forbidden JSON path segment '${forbidden}'.`);
|
|
9570
|
+
}
|
|
9571
|
+
return segments;
|
|
9572
|
+
}
|
|
9573
|
+
function runtimeDefineJsonProperty(target, key, value) {
|
|
9574
|
+
Object.defineProperty(target, key, {
|
|
9575
|
+
configurable: true,
|
|
9576
|
+
enumerable: true,
|
|
9577
|
+
value,
|
|
9578
|
+
writable: true
|
|
9579
|
+
});
|
|
9580
|
+
}
|
|
9581
|
+
function runtimeReadOwnJsonProperty(target, key) {
|
|
9582
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
9583
|
+
return descriptor && "value" in descriptor ? descriptor.value : undefined;
|
|
9584
|
+
}
|
|
9585
|
+
function runtimeCloneJsonRecord(value) {
|
|
9586
|
+
const target = {};
|
|
9587
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
9588
|
+
return target;
|
|
9589
|
+
}
|
|
9590
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
9591
|
+
runtimeDefineJsonProperty(target, key, entry);
|
|
7495
9592
|
}
|
|
7496
|
-
current[segments[segments.length - 1]] = value;
|
|
7497
9593
|
return target;
|
|
7498
9594
|
}
|
|
7499
9595
|
function asTrackingRecord(value) {
|
|
@@ -7503,8 +9599,8 @@ function asTrackingRecord(value) {
|
|
|
7503
9599
|
}
|
|
7504
9600
|
function pickTrackingValue(source, ...keys) {
|
|
7505
9601
|
for (const key of keys) {
|
|
7506
|
-
if (key
|
|
7507
|
-
return source
|
|
9602
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
9603
|
+
return runtimeReadOwnJsonProperty(source, key);
|
|
7508
9604
|
}
|
|
7509
9605
|
}
|
|
7510
9606
|
return undefined;
|
|
@@ -7758,26 +9854,10 @@ function createDefaultLogger(logFilePath) {
|
|
|
7758
9854
|
function wrapLoggerWithMinimumLevel(baseLogger, minimumLogLevel) {
|
|
7759
9855
|
const threshold = logLevelOrder(minimumLogLevel);
|
|
7760
9856
|
return {
|
|
7761
|
-
debug: (message) =>
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
},
|
|
7766
|
-
info: (message) => {
|
|
7767
|
-
if (threshold <= 1) {
|
|
7768
|
-
baseLogger.info(message);
|
|
7769
|
-
}
|
|
7770
|
-
},
|
|
7771
|
-
warn: (message) => {
|
|
7772
|
-
if (threshold <= 2) {
|
|
7773
|
-
baseLogger.warn(message);
|
|
7774
|
-
}
|
|
7775
|
-
},
|
|
7776
|
-
error: (message) => {
|
|
7777
|
-
if (threshold <= 3) {
|
|
7778
|
-
baseLogger.error(message);
|
|
7779
|
-
}
|
|
7780
|
-
}
|
|
9857
|
+
debug: (message) => threshold <= 0 ? baseLogger.debug(message) : undefined,
|
|
9858
|
+
info: (message) => threshold <= 1 ? baseLogger.info(message) : undefined,
|
|
9859
|
+
warn: (message) => threshold <= 2 ? baseLogger.warn(message) : undefined,
|
|
9860
|
+
error: (message) => threshold <= 3 ? baseLogger.error(message) : undefined
|
|
7781
9861
|
};
|
|
7782
9862
|
}
|
|
7783
9863
|
function formatDefaultLoggerLine(level, message) {
|
|
@@ -8055,6 +10135,68 @@ function normalizeOptionalReportFormats(value) {
|
|
|
8055
10135
|
const normalized = normalizeReportFormats(value);
|
|
8056
10136
|
return normalized.length ? normalized : undefined;
|
|
8057
10137
|
}
|
|
10138
|
+
function normalizeDeclaredStepNames(value) {
|
|
10139
|
+
if (!Array.isArray(value)) {
|
|
10140
|
+
throw new TypeError("Declared step names must be provided as text values.");
|
|
10141
|
+
}
|
|
10142
|
+
const seen = new Set();
|
|
10143
|
+
const normalized = [];
|
|
10144
|
+
for (const entry of value) {
|
|
10145
|
+
if (typeof entry !== "string" || !entry.trim()) {
|
|
10146
|
+
throw new Error("Declared step name must be non-empty text.");
|
|
10147
|
+
}
|
|
10148
|
+
const stepName = entry.trim();
|
|
10149
|
+
for (let index = 0; index < stepName.length; index += 1) {
|
|
10150
|
+
const code = stepName.charCodeAt(index);
|
|
10151
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
10152
|
+
const next = stepName.charCodeAt(index + 1);
|
|
10153
|
+
if (!(next >= 0xdc00 && next <= 0xdfff)) {
|
|
10154
|
+
throw new Error("Declared step name contains an invalid Unicode scalar.");
|
|
10155
|
+
}
|
|
10156
|
+
index += 1;
|
|
10157
|
+
}
|
|
10158
|
+
else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
10159
|
+
throw new Error("Declared step name contains an invalid Unicode scalar.");
|
|
10160
|
+
}
|
|
10161
|
+
}
|
|
10162
|
+
if (!seen.has(stepName)) {
|
|
10163
|
+
seen.add(stepName);
|
|
10164
|
+
normalized.push(stepName);
|
|
10165
|
+
}
|
|
10166
|
+
}
|
|
10167
|
+
return normalized;
|
|
10168
|
+
}
|
|
10169
|
+
function resolveIterationObservationSettings(options) {
|
|
10170
|
+
return {
|
|
10171
|
+
flushIntervalMs: options.iterationObservationFlushIntervalSeconds === undefined
|
|
10172
|
+
? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.flushIntervalMs
|
|
10173
|
+
: options.iterationObservationFlushIntervalSeconds * 1000,
|
|
10174
|
+
maxBufferBytes: options.maxIterationObservationBufferBytes
|
|
10175
|
+
?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.maxBufferBytes,
|
|
10176
|
+
maxObservationsPerBatch: options.maxIterationObservationsPerBatch
|
|
10177
|
+
?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.maxObservationsPerBatch,
|
|
10178
|
+
maxBatchBytes: options.maxIterationObservationBatchBytes
|
|
10179
|
+
?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.maxBatchBytes,
|
|
10180
|
+
sinkQueueDepth: options.iterationObservationSinkQueueDepth
|
|
10181
|
+
?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.sinkQueueDepth,
|
|
10182
|
+
sinkParallelism: options.iterationObservationSinkParallelism
|
|
10183
|
+
?? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.sinkParallelism,
|
|
10184
|
+
drainTimeoutMs: options.iterationObservationDrainTimeoutSeconds === undefined
|
|
10185
|
+
? iteration_observations_js_1.DEFAULT_ITERATION_OBSERVATION_SETTINGS.drainTimeoutMs
|
|
10186
|
+
: options.iterationObservationDrainTimeoutSeconds * 1000
|
|
10187
|
+
};
|
|
10188
|
+
}
|
|
10189
|
+
function validateRunContextIterationObservationSettings(values) {
|
|
10190
|
+
(0, iteration_observations_js_1.validateIterationObservationSettings)(resolveIterationObservationSettings({
|
|
10191
|
+
iterationObservationFlushIntervalSeconds: values.IterationObservationFlushIntervalSeconds,
|
|
10192
|
+
maxIterationObservationBufferBytes: values.MaxIterationObservationBufferBytes,
|
|
10193
|
+
maxIterationObservationsPerBatch: values.MaxIterationObservationsPerBatch,
|
|
10194
|
+
maxIterationObservationBatchBytes: values.MaxIterationObservationBatchBytes,
|
|
10195
|
+
iterationObservationSinkQueueDepth: values.IterationObservationSinkQueueDepth,
|
|
10196
|
+
iterationObservationSinkParallelism: values.IterationObservationSinkParallelism,
|
|
10197
|
+
iterationObservationDrainTimeoutSeconds: values.IterationObservationDrainTimeoutSeconds
|
|
10198
|
+
}));
|
|
10199
|
+
}
|
|
8058
10200
|
function assertNoDisableLicenseEnforcementOption(value, source) {
|
|
8059
10201
|
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
8060
10202
|
return;
|
|
@@ -8073,12 +10215,121 @@ function normalizeRunContextCollectionShapes(values) {
|
|
|
8073
10215
|
TargetScenarios: normalizeOptionalStringArray(values.TargetScenarios),
|
|
8074
10216
|
AgentTargetScenarios: normalizeOptionalStringArray(values.AgentTargetScenarios),
|
|
8075
10217
|
CoordinatorTargetScenarios: normalizeOptionalStringArray(values.CoordinatorTargetScenarios),
|
|
10218
|
+
ExpectedAgentIds: normalizeOptionalStringArray(values.ExpectedAgentIds),
|
|
8076
10219
|
ReportFormats: normalizeOptionalReportFormats(values.ReportFormats)
|
|
8077
10220
|
};
|
|
8078
10221
|
validateNamedReportingSinks(normalized.ReportingSinks ?? []);
|
|
8079
10222
|
validateNamedWorkerPlugins(normalized.WorkerPlugins ?? []);
|
|
10223
|
+
validateLoadEngineV2Options(normalized.LoadEngineContractVersion, normalized.MaxInFlight);
|
|
10224
|
+
validateRunContextIterationObservationSettings(normalized);
|
|
8080
10225
|
return normalized;
|
|
8081
10226
|
}
|
|
10227
|
+
function normalizeAliasStringRecord(value) {
|
|
10228
|
+
const source = asAliasRecord(value);
|
|
10229
|
+
const output = {};
|
|
10230
|
+
for (const [key, entry] of Object.entries(source)) {
|
|
10231
|
+
output[key] = String(entry);
|
|
10232
|
+
}
|
|
10233
|
+
return output;
|
|
10234
|
+
}
|
|
10235
|
+
function attachGeneratorWarningAliases(value) {
|
|
10236
|
+
const source = asAliasRecord(value);
|
|
10237
|
+
const projected = {
|
|
10238
|
+
code: pickAliasString(source, "code", "Code"),
|
|
10239
|
+
...(hasAliasValue(source, "sinkName", "SinkName")
|
|
10240
|
+
? { sinkName: pickAliasString(source, "sinkName", "SinkName") }
|
|
10241
|
+
: {}),
|
|
10242
|
+
scenarioName: pickAliasString(source, "scenarioName", "ScenarioName"),
|
|
10243
|
+
...(hasAliasValue(source, "scenarioIndex", "ScenarioIndex")
|
|
10244
|
+
? { scenarioIndex: pickAliasNumber(source, "scenarioIndex", "ScenarioIndex") }
|
|
10245
|
+
: {}),
|
|
10246
|
+
simulationIndex: pickAliasNumber(source, "simulationIndex", "SimulationIndex"),
|
|
10247
|
+
...(hasAliasValue(source, "simulationKind", "SimulationKind")
|
|
10248
|
+
? { simulationKind: pickAliasString(source, "simulationKind", "SimulationKind") }
|
|
10249
|
+
: {}),
|
|
10250
|
+
count64: pickAliasString(source, "count64", "Count64"),
|
|
10251
|
+
message: pickAliasString(source, "message", "Message"),
|
|
10252
|
+
firstObservedUtcNs: pickAliasString(source, "firstObservedUtcNs", "FirstObservedUtcNs"),
|
|
10253
|
+
lastObservedUtcNs: pickAliasString(source, "lastObservedUtcNs", "LastObservedUtcNs")
|
|
10254
|
+
};
|
|
10255
|
+
return attachAliasMap(projected, {
|
|
10256
|
+
Code: "code",
|
|
10257
|
+
SinkName: "sinkName",
|
|
10258
|
+
ScenarioName: "scenarioName",
|
|
10259
|
+
ScenarioIndex: "scenarioIndex",
|
|
10260
|
+
SimulationIndex: "simulationIndex",
|
|
10261
|
+
SimulationKind: "simulationKind",
|
|
10262
|
+
Count64: "count64",
|
|
10263
|
+
Message: "message",
|
|
10264
|
+
FirstObservedUtcNs: "firstObservedUtcNs",
|
|
10265
|
+
LastObservedUtcNs: "lastObservedUtcNs"
|
|
10266
|
+
});
|
|
10267
|
+
}
|
|
10268
|
+
function normalizeSchedulerSegment(value) {
|
|
10269
|
+
const source = asAliasRecord(value);
|
|
10270
|
+
return {
|
|
10271
|
+
scenarioName: pickAliasString(source, "scenarioName", "ScenarioName"),
|
|
10272
|
+
scenarioIndex: pickAliasNumber(source, "scenarioIndex", "ScenarioIndex"),
|
|
10273
|
+
simulationIndex: pickAliasNumber(source, "simulationIndex", "SimulationIndex"),
|
|
10274
|
+
kind: pickAliasString(source, "kind", "Kind"),
|
|
10275
|
+
shardIndex: pickAliasNumber(source, "shardIndex", "ShardIndex"),
|
|
10276
|
+
shardCount: Math.max(pickAliasNumber(source, "shardCount", "ShardCount"), 1),
|
|
10277
|
+
plannedIterations64: pickAliasString(source, "plannedIterations64", "PlannedIterations64"),
|
|
10278
|
+
dueIterations64: pickAliasString(source, "dueIterations64", "DueIterations64"),
|
|
10279
|
+
startedIterations64: pickAliasString(source, "startedIterations64", "StartedIterations64"),
|
|
10280
|
+
completedIterations64: pickAliasString(source, "completedIterations64", "CompletedIterations64"),
|
|
10281
|
+
droppedIterations64: pickAliasString(source, "droppedIterations64", "DroppedIterations64"),
|
|
10282
|
+
unreachedIterations64: pickAliasString(source, "unreachedIterations64", "UnreachedIterations64"),
|
|
10283
|
+
requestedWorkerSlots64: pickAliasString(source, "requestedWorkerSlots64", "RequestedWorkerSlots64"),
|
|
10284
|
+
startedWorkerSlots64: pickAliasString(source, "startedWorkerSlots64", "StartedWorkerSlots64"),
|
|
10285
|
+
unavailableWorkerSlots64: pickAliasString(source, "unavailableWorkerSlots64", "UnavailableWorkerSlots64"),
|
|
10286
|
+
dropReasons: normalizeAliasStringRecord(pickAliasValue(source, "dropReasons", "DropReasons")),
|
|
10287
|
+
unavailableWorkerReasons: normalizeAliasStringRecord(pickAliasValue(source, "unavailableWorkerReasons", "UnavailableWorkerReasons")),
|
|
10288
|
+
deliveryPercent: pickAliasNumber(source, "deliveryPercent", "DeliveryPercent"),
|
|
10289
|
+
accountingComplete: pickAliasBoolean(source, "accountingComplete", "AccountingComplete")
|
|
10290
|
+
};
|
|
10291
|
+
}
|
|
10292
|
+
function normalizeSchedulerStats(value) {
|
|
10293
|
+
const source = asAliasRecord(value);
|
|
10294
|
+
return {
|
|
10295
|
+
configuredMaxInFlight: pickAliasNumber(source, "configuredMaxInFlight", "ConfiguredMaxInFlight"),
|
|
10296
|
+
maxInFlightObserved: pickAliasNumber(source, "maxInFlightObserved", "MaxInFlightObserved"),
|
|
10297
|
+
currentInFlight: pickAliasNumber(source, "currentInFlight", "CurrentInFlight"),
|
|
10298
|
+
segments: pickAliasArray(source, "segments", "Segments").map(normalizeSchedulerSegment)
|
|
10299
|
+
};
|
|
10300
|
+
}
|
|
10301
|
+
function normalizeObservationDeliveryStats(value) {
|
|
10302
|
+
const source = asAliasRecord(value);
|
|
10303
|
+
return attachAliasMap({
|
|
10304
|
+
lastBatchSequence64: pickAliasString(source, "lastBatchSequence64", "LastBatchSequence64"),
|
|
10305
|
+
capturedCount64: pickAliasString(source, "capturedCount64", "CapturedCount64"),
|
|
10306
|
+
deliveredCount64: pickAliasString(source, "deliveredCount64", "DeliveredCount64"),
|
|
10307
|
+
droppedBufferCount64: pickAliasString(source, "droppedBufferCount64", "DroppedBufferCount64"),
|
|
10308
|
+
droppedSinkCount64: pickAliasString(source, "droppedSinkCount64", "DroppedSinkCount64")
|
|
10309
|
+
}, {
|
|
10310
|
+
LastBatchSequence64: "lastBatchSequence64",
|
|
10311
|
+
CapturedCount64: "capturedCount64",
|
|
10312
|
+
DeliveredCount64: "deliveredCount64",
|
|
10313
|
+
DroppedBufferCount64: "droppedBufferCount64",
|
|
10314
|
+
DroppedSinkCount64: "droppedSinkCount64"
|
|
10315
|
+
});
|
|
10316
|
+
}
|
|
10317
|
+
function validateLoadEngineV2Options(contractVersion, maxInFlight) {
|
|
10318
|
+
if (contractVersion !== undefined && contractVersion !== 1 && contractVersion !== 2) {
|
|
10319
|
+
throw new RangeError("Load engine contract version must be either 1 or 2.");
|
|
10320
|
+
}
|
|
10321
|
+
if (maxInFlight !== undefined) {
|
|
10322
|
+
validateV2MaxInFlight(contractVersion, maxInFlight);
|
|
10323
|
+
}
|
|
10324
|
+
}
|
|
10325
|
+
function validateV2MaxInFlight(contractVersion, maxInFlight) {
|
|
10326
|
+
if (contractVersion !== 2) {
|
|
10327
|
+
throw new Error("MaxInFlight is available only when Load Engine V2 is selected.");
|
|
10328
|
+
}
|
|
10329
|
+
if (!Number.isSafeInteger(maxInFlight) || maxInFlight < 1 || maxInFlight > 1000000) {
|
|
10330
|
+
throw new RangeError("MaxInFlight must be an integer from 1 through 1000000.");
|
|
10331
|
+
}
|
|
10332
|
+
}
|
|
8082
10333
|
function normalizeRunnerOptionCollectionShapes(options) {
|
|
8083
10334
|
assertNoDisableLicenseEnforcementOption(options, "LoadStrikeRunner");
|
|
8084
10335
|
const normalized = {
|
|
@@ -8086,10 +10337,13 @@ function normalizeRunnerOptionCollectionShapes(options) {
|
|
|
8086
10337
|
targetScenarios: normalizeOptionalStringArray(options.targetScenarios),
|
|
8087
10338
|
agentTargetScenarios: normalizeOptionalStringArray(options.agentTargetScenarios),
|
|
8088
10339
|
coordinatorTargetScenarios: normalizeOptionalStringArray(options.coordinatorTargetScenarios),
|
|
10340
|
+
expectedAgentIds: normalizeOptionalStringArray(options.expectedAgentIds),
|
|
8089
10341
|
reportFormats: normalizeOptionalReportFormats(options.reportFormats)
|
|
8090
10342
|
};
|
|
8091
10343
|
validateNamedReportingSinks(normalized.reportingSinks ?? []);
|
|
8092
10344
|
validateNamedWorkerPlugins(normalized.workerPlugins ?? []);
|
|
10345
|
+
validateLoadEngineV2Options(normalized.loadEngineContractVersion, normalized.maxInFlight);
|
|
10346
|
+
(0, iteration_observations_js_1.validateIterationObservationSettings)(resolveIterationObservationSettings(normalized));
|
|
8093
10347
|
return normalized;
|
|
8094
10348
|
}
|
|
8095
10349
|
function normalizedRuntimePolicyErrorMode(value) {
|
|
@@ -8153,6 +10407,7 @@ function extractContextOverridesFromConfig(config) {
|
|
|
8153
10407
|
setString("ReportFileName", "ReportFileName", "LoadStrike:ReportFileName");
|
|
8154
10408
|
setString("ClusterId", "ClusterId", "LoadStrike:ClusterId");
|
|
8155
10409
|
setString("AgentGroup", "AgentGroup", "LoadStrike:AgentGroup");
|
|
10410
|
+
setString("AgentId", "AgentId", "LoadStrike:AgentId");
|
|
8156
10411
|
setString("NatsServerUrl", "NatsServerUrl", "LoadStrike:NatsServerUrl");
|
|
8157
10412
|
setString("RunnerKey", "RunnerKey", "LoadStrike:RunnerKey");
|
|
8158
10413
|
setString("RuntimePolicyErrorMode", "RuntimePolicyErrorMode", "LoadStrike:RuntimePolicyErrorMode");
|
|
@@ -8182,6 +10437,13 @@ function extractContextOverridesFromConfig(config) {
|
|
|
8182
10437
|
}
|
|
8183
10438
|
}
|
|
8184
10439
|
setPositiveNumber("ReportingIntervalSeconds", "ReportingIntervalSeconds", "LoadStrike:ReportingIntervalSeconds");
|
|
10440
|
+
setPositiveNumber("IterationObservationFlushIntervalSeconds", "IterationObservationFlushInterval", "IterationObservationFlushIntervalSeconds", "LoadStrike:IterationObservationFlushInterval");
|
|
10441
|
+
setPositiveNumber("MaxIterationObservationBufferBytes", "MaxIterationObservationBufferBytes", "LoadStrike:MaxIterationObservationBufferBytes");
|
|
10442
|
+
setPositiveNumber("MaxIterationObservationsPerBatch", "MaxIterationObservationsPerBatch", "LoadStrike:MaxIterationObservationsPerBatch");
|
|
10443
|
+
setPositiveNumber("MaxIterationObservationBatchBytes", "MaxIterationObservationBatchBytes", "LoadStrike:MaxIterationObservationBatchBytes");
|
|
10444
|
+
setPositiveNumber("IterationObservationSinkQueueDepth", "IterationObservationSinkQueueDepth", "LoadStrike:IterationObservationSinkQueueDepth");
|
|
10445
|
+
setPositiveNumber("IterationObservationSinkParallelism", "IterationObservationSinkParallelism", "LoadStrike:IterationObservationSinkParallelism");
|
|
10446
|
+
setPositiveNumber("IterationObservationDrainTimeoutSeconds", "IterationObservationDrainTimeout", "IterationObservationDrainTimeoutSeconds", "LoadStrike:IterationObservationDrainTimeout");
|
|
8185
10447
|
setPositiveNumber("ScenarioCompletionTimeoutSeconds", "ScenarioCompletionTimeoutSeconds", "LoadStrike:ScenarioCompletionTimeoutSeconds");
|
|
8186
10448
|
setPositiveNumber("ClusterCommandTimeoutSeconds", "ClusterCommandTimeoutSeconds", "LoadStrike:ClusterCommandTimeoutSeconds");
|
|
8187
10449
|
setPositiveNumber("LicenseValidationTimeoutSeconds", "LicenseValidationTimeoutSeconds", "LoadStrike:LicenseValidation:TimeoutSeconds");
|
|
@@ -8189,6 +10451,14 @@ function extractContextOverridesFromConfig(config) {
|
|
|
8189
10451
|
if (reportingIntervalMs > 0) {
|
|
8190
10452
|
patch.ReportingIntervalSeconds = reportingIntervalMs / 1000;
|
|
8191
10453
|
}
|
|
10454
|
+
const observationFlushIntervalMs = toNumber(pick("IterationObservationFlushIntervalMs", "LoadStrike:IterationObservationFlushIntervalMs"));
|
|
10455
|
+
if (observationFlushIntervalMs > 0) {
|
|
10456
|
+
patch.IterationObservationFlushIntervalSeconds = observationFlushIntervalMs / 1000;
|
|
10457
|
+
}
|
|
10458
|
+
const observationDrainTimeoutMs = toNumber(pick("IterationObservationDrainTimeoutMs", "LoadStrike:IterationObservationDrainTimeoutMs"));
|
|
10459
|
+
if (observationDrainTimeoutMs > 0) {
|
|
10460
|
+
patch.IterationObservationDrainTimeoutSeconds = observationDrainTimeoutMs / 1000;
|
|
10461
|
+
}
|
|
8192
10462
|
const scenarioCompletionTimeoutMs = toNumber(pick("ScenarioCompletionTimeoutMs", "LoadStrike:ScenarioCompletionTimeoutMs"));
|
|
8193
10463
|
if (scenarioCompletionTimeoutMs > 0) {
|
|
8194
10464
|
patch.ScenarioCompletionTimeoutSeconds = scenarioCompletionTimeoutMs / 1000;
|
|
@@ -8233,6 +10503,10 @@ function extractContextOverridesFromConfig(config) {
|
|
|
8233
10503
|
if (agentTargetScenarios.length) {
|
|
8234
10504
|
patch.AgentTargetScenarios = agentTargetScenarios;
|
|
8235
10505
|
}
|
|
10506
|
+
const expectedAgentIds = normalizeStringArray(pick("ExpectedAgentIds", "LoadStrike:ExpectedAgentIds"));
|
|
10507
|
+
if (expectedAgentIds.length) {
|
|
10508
|
+
patch.ExpectedAgentIds = expectedAgentIds;
|
|
10509
|
+
}
|
|
8236
10510
|
const coordinatorTargetScenarios = normalizeStringArray(pick("CoordinatorTargetScenarios", "LoadStrike:CoordinatorTargetScenarios"));
|
|
8237
10511
|
if (coordinatorTargetScenarios.length) {
|
|
8238
10512
|
patch.CoordinatorTargetScenarios = coordinatorTargetScenarios;
|
|
@@ -8295,10 +10569,14 @@ function toRunContext(options) {
|
|
|
8295
10569
|
const normalized = normalizeRunnerOptionCollectionShapes(options);
|
|
8296
10570
|
return {
|
|
8297
10571
|
ConsoleMetricsEnabled: normalized.displayConsoleMetrics,
|
|
10572
|
+
LoadEngineContractVersion: normalized.loadEngineContractVersion,
|
|
10573
|
+
MaxInFlight: normalized.maxInFlight,
|
|
8298
10574
|
NodeType: normalized.nodeType,
|
|
8299
10575
|
LocalDevClusterEnabled: normalized.localDevClusterEnabled,
|
|
8300
10576
|
AgentGroup: normalized.agentGroup,
|
|
8301
10577
|
AgentsCount: normalized.agentsCount,
|
|
10578
|
+
AgentId: normalized.agentId,
|
|
10579
|
+
ExpectedAgentIds: normalized.expectedAgentIds,
|
|
8302
10580
|
TargetScenarios: normalized.targetScenarios,
|
|
8303
10581
|
AgentTargetScenarios: normalized.agentTargetScenarios,
|
|
8304
10582
|
CoordinatorTargetScenarios: normalized.coordinatorTargetScenarios,
|
|
@@ -8317,6 +10595,13 @@ function toRunContext(options) {
|
|
|
8317
10595
|
ReportFolderPath: normalized.reportFolderPath,
|
|
8318
10596
|
ReportFormats: normalized.reportFormats,
|
|
8319
10597
|
ReportingIntervalSeconds: normalized.reportingIntervalSeconds,
|
|
10598
|
+
IterationObservationFlushIntervalSeconds: normalized.iterationObservationFlushIntervalSeconds,
|
|
10599
|
+
MaxIterationObservationBufferBytes: normalized.maxIterationObservationBufferBytes,
|
|
10600
|
+
MaxIterationObservationsPerBatch: normalized.maxIterationObservationsPerBatch,
|
|
10601
|
+
MaxIterationObservationBatchBytes: normalized.maxIterationObservationBatchBytes,
|
|
10602
|
+
IterationObservationSinkQueueDepth: normalized.iterationObservationSinkQueueDepth,
|
|
10603
|
+
IterationObservationSinkParallelism: normalized.iterationObservationSinkParallelism,
|
|
10604
|
+
IterationObservationDrainTimeoutSeconds: normalized.iterationObservationDrainTimeoutSeconds,
|
|
8320
10605
|
MinimumLogLevel: normalized.minimumLogLevel,
|
|
8321
10606
|
LoggerConfig: normalized.loggerConfig,
|
|
8322
10607
|
ReportingSinks: normalized.reportingSinks,
|
|
@@ -8331,7 +10616,9 @@ function toRunContext(options) {
|
|
|
8331
10616
|
CustomSettings: normalized.customSettings,
|
|
8332
10617
|
GlobalCustomSettings: normalized.globalCustomSettings,
|
|
8333
10618
|
AgentExecutionToken: normalized.agentExecutionToken,
|
|
8334
|
-
AgentCommandId: normalized.agentCommandId
|
|
10619
|
+
AgentCommandId: normalized.agentCommandId,
|
|
10620
|
+
ClusterShardIndex: normalized.clusterShardIndex,
|
|
10621
|
+
ClusterShardCount: normalized.clusterShardCount
|
|
8335
10622
|
};
|
|
8336
10623
|
}
|
|
8337
10624
|
class RuntimePolicyCallbackError extends Error {
|
|
@@ -8419,11 +10706,15 @@ function looksLikeRunContext(value) {
|
|
|
8419
10706
|
const keys = new Set(Object.keys(value));
|
|
8420
10707
|
return [
|
|
8421
10708
|
"ConsoleMetricsEnabled",
|
|
10709
|
+
"LoadEngineContractVersion",
|
|
10710
|
+
"MaxInFlight",
|
|
8422
10711
|
"LocalDevClusterEnabled",
|
|
8423
10712
|
"ConfigPath",
|
|
8424
10713
|
"InfraConfigPath",
|
|
8425
10714
|
"AgentGroup",
|
|
8426
10715
|
"AgentsCount",
|
|
10716
|
+
"AgentId",
|
|
10717
|
+
"ExpectedAgentIds",
|
|
8427
10718
|
"AgentTargetScenarios",
|
|
8428
10719
|
"ClusterId",
|
|
8429
10720
|
"CoordinatorTargetScenarios",
|
|
@@ -8438,6 +10729,13 @@ function looksLikeRunContext(value) {
|
|
|
8438
10729
|
"ReportFolderPath",
|
|
8439
10730
|
"ReportFormats",
|
|
8440
10731
|
"ReportingIntervalSeconds",
|
|
10732
|
+
"IterationObservationFlushIntervalSeconds",
|
|
10733
|
+
"MaxIterationObservationBufferBytes",
|
|
10734
|
+
"MaxIterationObservationsPerBatch",
|
|
10735
|
+
"MaxIterationObservationBatchBytes",
|
|
10736
|
+
"IterationObservationSinkQueueDepth",
|
|
10737
|
+
"IterationObservationSinkParallelism",
|
|
10738
|
+
"IterationObservationDrainTimeoutSeconds",
|
|
8441
10739
|
"ReportingSinks",
|
|
8442
10740
|
"SinkRetryCount",
|
|
8443
10741
|
"SinkRetryBackoffMs",
|
|
@@ -8579,6 +10877,18 @@ function resolveSinkSaveRunResult(sink) {
|
|
|
8579
10877
|
? method.bind(sink)
|
|
8580
10878
|
: undefined;
|
|
8581
10879
|
}
|
|
10880
|
+
function resolveSinkSaveIterationBatch(sink) {
|
|
10881
|
+
const method = sink.saveIterationBatch ?? sink.SaveIterationBatch;
|
|
10882
|
+
return typeof method === "function"
|
|
10883
|
+
? method.bind(sink)
|
|
10884
|
+
: undefined;
|
|
10885
|
+
}
|
|
10886
|
+
function resolveSinkCompleteIterationObservationStream(sink) {
|
|
10887
|
+
const method = sink.completeIterationObservationStream ?? sink.CompleteIterationObservationStream;
|
|
10888
|
+
return typeof method === "function"
|
|
10889
|
+
? method.bind(sink)
|
|
10890
|
+
: undefined;
|
|
10891
|
+
}
|
|
8582
10892
|
function resolveSinkStop(sink) {
|
|
8583
10893
|
const method = sink.stop ?? sink.Stop;
|
|
8584
10894
|
return typeof method === "function"
|
|
@@ -8996,13 +11306,17 @@ exports.__loadstrikeTestExports = {
|
|
|
8996
11306
|
ManagedScenarioTrackingRuntime,
|
|
8997
11307
|
ScenarioStatsAccumulator,
|
|
8998
11308
|
StepStatsAccumulator,
|
|
11309
|
+
planRuntimeClusterAssignments,
|
|
8999
11310
|
TrackingFieldSelector: correlation_js_1.TrackingFieldSelector,
|
|
9000
11311
|
addCorrelationRow,
|
|
9001
11312
|
addFailedResponseRow,
|
|
9002
11313
|
aggregateNodeStats,
|
|
11314
|
+
aggregateMeasurementStats,
|
|
9003
11315
|
asRecord,
|
|
9004
11316
|
assertNoDisableLicenseEnforcementOption,
|
|
9005
11317
|
buildEmptyNodeStats,
|
|
11318
|
+
buildRuntimeLoadEngineV2HistogramArtifact,
|
|
11319
|
+
buildRuntimeLoadEngineV2Plan,
|
|
9006
11320
|
buildGroupedCorrelationRows,
|
|
9007
11321
|
buildMeasurementPlaceholder,
|
|
9008
11322
|
buildRichHtmlReport,
|
|
@@ -9057,6 +11371,7 @@ exports.__loadstrikeTestExports = {
|
|
|
9057
11371
|
parseStrictBooleanToken,
|
|
9058
11372
|
percentile,
|
|
9059
11373
|
pickOptionalTrackingSelectorString,
|
|
11374
|
+
pickTrackingValue,
|
|
9060
11375
|
pickTrackingNumber,
|
|
9061
11376
|
produceOrConsumeTrackingPayload,
|
|
9062
11377
|
readConfiguredSinkName,
|
|
@@ -9075,7 +11390,9 @@ exports.__loadstrikeTestExports = {
|
|
|
9075
11390
|
resolveSinkName,
|
|
9076
11391
|
resolveSinkSaveRealtimeMetrics,
|
|
9077
11392
|
resolveSinkSaveRealtimeStats,
|
|
11393
|
+
resolveSinkSaveIterationBatch,
|
|
9078
11394
|
resolveSinkSaveRunResult,
|
|
11395
|
+
resolveSinkCompleteIterationObservationStream,
|
|
9079
11396
|
resolveSinkStart,
|
|
9080
11397
|
resolveSinkStop,
|
|
9081
11398
|
resolveWorkerPlugins,
|
|
@@ -9096,6 +11413,7 @@ exports.__loadstrikeTestExports = {
|
|
|
9096
11413
|
tryParseNodeTypeToken,
|
|
9097
11414
|
tryReadConfigValue,
|
|
9098
11415
|
validateNamedReportingSinks,
|
|
11416
|
+
validateLoadEngineV2ScenarioFeatures,
|
|
9099
11417
|
validateRegisteredScenarios,
|
|
9100
11418
|
validateRuntimeRedisCorrelationStoreConfiguration,
|
|
9101
11419
|
validateRuntimeTrackingConfiguration,
|