@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/sinks.js
CHANGED
|
@@ -4,10 +4,44 @@ exports.__loadstrikeTestExports = exports.JsonlFileReportingSink = exports.Netda
|
|
|
4
4
|
exports.cloneReportingSinkForRun = cloneReportingSinkForRun;
|
|
5
5
|
const runtime_js_1 = require("./runtime.js");
|
|
6
6
|
const node_crypto_1 = require("node:crypto");
|
|
7
|
-
const
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
8
|
const node_path_1 = require("node:path");
|
|
9
9
|
const node_dgram_1 = require("node:dgram");
|
|
10
|
+
const node_zlib_1 = require("node:zlib");
|
|
10
11
|
const pg_1 = require("pg");
|
|
12
|
+
const iteration_observations_js_1 = require("./iteration-observations.js");
|
|
13
|
+
const iteration_observation_diagnostics_js_1 = require("./iteration-observation-diagnostics.js");
|
|
14
|
+
const PORTAL_RUN_TOKEN_PROVIDER = Symbol.for("loadstrike.internal.portal-run-token-provider");
|
|
15
|
+
const MAXIMUM_HTTP_RESPONSE_BODY_BYTES = 256 * 1024;
|
|
16
|
+
const MAXIMUM_HTTP_METADATA_CHARACTERS = 256;
|
|
17
|
+
const HTTP_METADATA_TRUNCATION_SUFFIX = " [truncated]";
|
|
18
|
+
class ReportingSinkHttpError extends Error {
|
|
19
|
+
constructor(sinkName, status, statusText, requestId) {
|
|
20
|
+
const normalizedStatus = Number.isFinite(status) ? Math.max(Math.trunc(status), 0) : 0;
|
|
21
|
+
const normalizedStatusText = normalizeHttpMetadata(statusText);
|
|
22
|
+
const normalizedRequestId = normalizeHttpMetadata(requestId);
|
|
23
|
+
super(`${sinkName} write failed with HTTP status ${normalizedStatus}`
|
|
24
|
+
+ (normalizedStatusText ? ` ${normalizedStatusText}` : "")
|
|
25
|
+
+ (normalizedRequestId ? ` (requestId=${normalizedRequestId})` : "")
|
|
26
|
+
+ ".");
|
|
27
|
+
this.name = "ReportingSinkHttpError";
|
|
28
|
+
this.status = normalizedStatus;
|
|
29
|
+
this.statusText = normalizedStatusText;
|
|
30
|
+
this.requestId = normalizedRequestId;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function gzipJsonAsync(value) {
|
|
34
|
+
const json = Buffer.from(JSON.stringify(value), "utf8");
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
(0, node_zlib_1.gzip)(json, (error, compressed) => {
|
|
37
|
+
if (error) {
|
|
38
|
+
reject(error);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
resolve(compressed);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
11
45
|
const DEFAULT_INFLUX_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:InfluxDb";
|
|
12
46
|
const DEFAULT_GRAFANA_LOKI_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:GrafanaLoki";
|
|
13
47
|
const DEFAULT_TIMESCALEDB_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:TimescaleDb";
|
|
@@ -376,6 +410,28 @@ class CompositeReportingSink {
|
|
|
376
410
|
async SaveRunResult(result) {
|
|
377
411
|
await this.saveRunResult(result);
|
|
378
412
|
}
|
|
413
|
+
async saveIterationBatch(batch) {
|
|
414
|
+
for (const sink of this.sinks) {
|
|
415
|
+
const saveIterationBatch = sink.saveIterationBatch ?? sink.SaveIterationBatch;
|
|
416
|
+
if (saveIterationBatch) {
|
|
417
|
+
await saveIterationBatch.call(sink, batch);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
async SaveIterationBatch(batch) {
|
|
422
|
+
await this.saveIterationBatch(batch);
|
|
423
|
+
}
|
|
424
|
+
async completeIterationObservationStream(completion) {
|
|
425
|
+
for (const sink of this.sinks) {
|
|
426
|
+
const completeIterationObservationStream = sink.completeIterationObservationStream ?? sink.CompleteIterationObservationStream;
|
|
427
|
+
if (completeIterationObservationStream) {
|
|
428
|
+
await completeIterationObservationStream.call(sink, completion);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async CompleteIterationObservationStream(completion) {
|
|
433
|
+
await this.completeIterationObservationStream(completion);
|
|
434
|
+
}
|
|
379
435
|
async stop() {
|
|
380
436
|
for (const sink of this.sinks) {
|
|
381
437
|
const stop = sink.stop ?? sink.Stop;
|
|
@@ -391,6 +447,7 @@ class CompositeReportingSink {
|
|
|
391
447
|
exports.CompositeReportingSink = CompositeReportingSink;
|
|
392
448
|
class PortalReportingSink {
|
|
393
449
|
constructor(options = {}) {
|
|
450
|
+
this.iterationObservationPortalSink = true;
|
|
394
451
|
this.sinkName = "portal";
|
|
395
452
|
this.SinkName = "portal";
|
|
396
453
|
this.licenseFeature = "extensions.reporting_sinks.portal";
|
|
@@ -398,6 +455,7 @@ class PortalReportingSink {
|
|
|
398
455
|
this.baseContext = null;
|
|
399
456
|
this.session = null;
|
|
400
457
|
this.runToken = "";
|
|
458
|
+
this.runTokenProvider = null;
|
|
401
459
|
this.ingestUrl = "";
|
|
402
460
|
this.optionsInput = { ...options };
|
|
403
461
|
const source = asRecord(options);
|
|
@@ -414,7 +472,11 @@ class PortalReportingSink {
|
|
|
414
472
|
this.init(context, infraConfig);
|
|
415
473
|
}
|
|
416
474
|
start(session) {
|
|
417
|
-
|
|
475
|
+
const internalProvider = session[PORTAL_RUN_TOKEN_PROVIDER];
|
|
476
|
+
this.runTokenProvider = typeof internalProvider === "function"
|
|
477
|
+
? internalProvider
|
|
478
|
+
: () => String(session.runToken ?? session.RunToken ?? "").trim();
|
|
479
|
+
this.runToken = this.runTokenProvider();
|
|
418
480
|
this.ingestUrl = String(session.portalReportingIngestUrl ?? session.PortalReportingIngestUrl ?? "").trim();
|
|
419
481
|
if (!this.runToken || !this.ingestUrl) {
|
|
420
482
|
throw new Error("PortalReportingSink requires a managed portal reporting session.");
|
|
@@ -444,8 +506,27 @@ class PortalReportingSink {
|
|
|
444
506
|
async SaveRunResult(result) {
|
|
445
507
|
await this.saveRunResult(result);
|
|
446
508
|
}
|
|
509
|
+
async saveIterationBatch(batch) {
|
|
510
|
+
const compressed = await gzipJsonAsync(batch);
|
|
511
|
+
await this.persistObservationPayload({
|
|
512
|
+
compressedObservationBatches: [{
|
|
513
|
+
compression: "gzip-json",
|
|
514
|
+
payloadBase64: compressed.toString("base64")
|
|
515
|
+
}]
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
async SaveIterationBatch(batch) {
|
|
519
|
+
await this.saveIterationBatch(batch);
|
|
520
|
+
}
|
|
521
|
+
async completeIterationObservationStream(completion) {
|
|
522
|
+
await this.persistObservationPayload({ observationStreamCompletions: [completion] });
|
|
523
|
+
}
|
|
524
|
+
async CompleteIterationObservationStream(completion) {
|
|
525
|
+
await this.completeIterationObservationStream(completion);
|
|
526
|
+
}
|
|
447
527
|
stop() {
|
|
448
528
|
this.session = null;
|
|
529
|
+
this.runTokenProvider = null;
|
|
449
530
|
}
|
|
450
531
|
Stop() {
|
|
451
532
|
this.stop();
|
|
@@ -474,12 +555,27 @@ class PortalReportingSink {
|
|
|
474
555
|
method: "POST",
|
|
475
556
|
headers: { "Content-Type": "application/json" },
|
|
476
557
|
body: JSON.stringify({
|
|
477
|
-
runToken: this.
|
|
478
|
-
events: events.map((event, index) => portalEventPayload(event, index))
|
|
558
|
+
runToken: this.currentRunToken(),
|
|
559
|
+
events: events.map((event, index) => portalEventPayload(removeSdkPercentileFields(event), index))
|
|
560
|
+
})
|
|
561
|
+
}, this.timeoutMs, "PortalReportingSink");
|
|
562
|
+
validatePortalIngestResponse(responseBody);
|
|
563
|
+
}
|
|
564
|
+
async persistObservationPayload(payload) {
|
|
565
|
+
const responseBody = await postWithTimeout(this.fetchImpl, this.ingestUrl, {
|
|
566
|
+
method: "POST",
|
|
567
|
+
headers: { "Content-Type": "application/json" },
|
|
568
|
+
body: JSON.stringify({
|
|
569
|
+
runToken: this.currentRunToken(),
|
|
570
|
+
...payload
|
|
479
571
|
})
|
|
480
572
|
}, this.timeoutMs, "PortalReportingSink");
|
|
481
573
|
validatePortalIngestResponse(responseBody);
|
|
482
574
|
}
|
|
575
|
+
currentRunToken() {
|
|
576
|
+
const current = this.runTokenProvider?.() ?? "";
|
|
577
|
+
return current || this.runToken;
|
|
578
|
+
}
|
|
483
579
|
}
|
|
484
580
|
exports.PortalReportingSink = PortalReportingSink;
|
|
485
581
|
function cloneReportingSinkForRun(sink) {
|
|
@@ -556,6 +652,18 @@ class InfluxDbReportingSink {
|
|
|
556
652
|
async SaveRunResult(result) {
|
|
557
653
|
await this.saveRunResult(result);
|
|
558
654
|
}
|
|
655
|
+
async saveIterationBatch(batch) {
|
|
656
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
657
|
+
}
|
|
658
|
+
async SaveIterationBatch(batch) {
|
|
659
|
+
await this.saveIterationBatch(batch);
|
|
660
|
+
}
|
|
661
|
+
async completeIterationObservationStream(completion) {
|
|
662
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
663
|
+
}
|
|
664
|
+
async CompleteIterationObservationStream(completion) {
|
|
665
|
+
await this.completeIterationObservationStream(completion);
|
|
666
|
+
}
|
|
559
667
|
stop() {
|
|
560
668
|
this.session = null;
|
|
561
669
|
}
|
|
@@ -666,6 +774,18 @@ class GrafanaLokiReportingSink {
|
|
|
666
774
|
async SaveRunResult(result) {
|
|
667
775
|
await this.saveRunResult(result);
|
|
668
776
|
}
|
|
777
|
+
async saveIterationBatch(batch) {
|
|
778
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
779
|
+
}
|
|
780
|
+
async SaveIterationBatch(batch) {
|
|
781
|
+
await this.saveIterationBatch(batch);
|
|
782
|
+
}
|
|
783
|
+
async completeIterationObservationStream(completion) {
|
|
784
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
785
|
+
}
|
|
786
|
+
async CompleteIterationObservationStream(completion) {
|
|
787
|
+
await this.completeIterationObservationStream(completion);
|
|
788
|
+
}
|
|
669
789
|
stop() {
|
|
670
790
|
this.session = null;
|
|
671
791
|
}
|
|
@@ -812,6 +932,18 @@ class TimescaleDbReportingSink {
|
|
|
812
932
|
async SaveRunResult(result) {
|
|
813
933
|
await this.saveRunResult(result);
|
|
814
934
|
}
|
|
935
|
+
async saveIterationBatch(batch) {
|
|
936
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
937
|
+
}
|
|
938
|
+
async SaveIterationBatch(batch) {
|
|
939
|
+
await this.saveIterationBatch(batch);
|
|
940
|
+
}
|
|
941
|
+
async completeIterationObservationStream(completion) {
|
|
942
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
943
|
+
}
|
|
944
|
+
async CompleteIterationObservationStream(completion) {
|
|
945
|
+
await this.completeIterationObservationStream(completion);
|
|
946
|
+
}
|
|
815
947
|
async stop() {
|
|
816
948
|
this.session = null;
|
|
817
949
|
if (this.pool) {
|
|
@@ -1088,6 +1220,18 @@ class DatadogReportingSink {
|
|
|
1088
1220
|
async SaveRunResult(result) {
|
|
1089
1221
|
await this.saveRunResult(result);
|
|
1090
1222
|
}
|
|
1223
|
+
async saveIterationBatch(batch) {
|
|
1224
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
1225
|
+
}
|
|
1226
|
+
async SaveIterationBatch(batch) {
|
|
1227
|
+
await this.saveIterationBatch(batch);
|
|
1228
|
+
}
|
|
1229
|
+
async completeIterationObservationStream(completion) {
|
|
1230
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
1231
|
+
}
|
|
1232
|
+
async CompleteIterationObservationStream(completion) {
|
|
1233
|
+
await this.completeIterationObservationStream(completion);
|
|
1234
|
+
}
|
|
1091
1235
|
stop() {
|
|
1092
1236
|
this.session = null;
|
|
1093
1237
|
}
|
|
@@ -1211,6 +1355,18 @@ class SplunkReportingSink {
|
|
|
1211
1355
|
async SaveRunResult(result) {
|
|
1212
1356
|
await this.saveRunResult(result);
|
|
1213
1357
|
}
|
|
1358
|
+
async saveIterationBatch(batch) {
|
|
1359
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
1360
|
+
}
|
|
1361
|
+
async SaveIterationBatch(batch) {
|
|
1362
|
+
await this.saveIterationBatch(batch);
|
|
1363
|
+
}
|
|
1364
|
+
async completeIterationObservationStream(completion) {
|
|
1365
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
1366
|
+
}
|
|
1367
|
+
async CompleteIterationObservationStream(completion) {
|
|
1368
|
+
await this.completeIterationObservationStream(completion);
|
|
1369
|
+
}
|
|
1214
1370
|
stop() {
|
|
1215
1371
|
this.session = null;
|
|
1216
1372
|
}
|
|
@@ -1314,6 +1470,18 @@ class OtelCollectorReportingSink {
|
|
|
1314
1470
|
async SaveRunResult(result) {
|
|
1315
1471
|
await this.saveRunResult(result);
|
|
1316
1472
|
}
|
|
1473
|
+
async saveIterationBatch(batch) {
|
|
1474
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
1475
|
+
}
|
|
1476
|
+
async SaveIterationBatch(batch) {
|
|
1477
|
+
await this.saveIterationBatch(batch);
|
|
1478
|
+
}
|
|
1479
|
+
async completeIterationObservationStream(completion) {
|
|
1480
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
1481
|
+
}
|
|
1482
|
+
async CompleteIterationObservationStream(completion) {
|
|
1483
|
+
await this.completeIterationObservationStream(completion);
|
|
1484
|
+
}
|
|
1317
1485
|
stop() {
|
|
1318
1486
|
this.session = null;
|
|
1319
1487
|
}
|
|
@@ -1391,6 +1559,7 @@ class ExpandedEventReportingSink {
|
|
|
1391
1559
|
constructor(sinkName, licenseFeature) {
|
|
1392
1560
|
this.baseContext = null;
|
|
1393
1561
|
this.session = null;
|
|
1562
|
+
this.preservesRawIterationObservations = true;
|
|
1394
1563
|
this.sinkName = sinkName;
|
|
1395
1564
|
this.SinkName = sinkName;
|
|
1396
1565
|
this.licenseFeature = licenseFeature;
|
|
@@ -1426,6 +1595,18 @@ class ExpandedEventReportingSink {
|
|
|
1426
1595
|
async SaveRunResult(result) {
|
|
1427
1596
|
await this.saveRunResult(result);
|
|
1428
1597
|
}
|
|
1598
|
+
async saveIterationBatch(batch) {
|
|
1599
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
1600
|
+
}
|
|
1601
|
+
async SaveIterationBatch(batch) {
|
|
1602
|
+
await this.saveIterationBatch(batch);
|
|
1603
|
+
}
|
|
1604
|
+
async completeIterationObservationStream(completion) {
|
|
1605
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
1606
|
+
}
|
|
1607
|
+
async CompleteIterationObservationStream(completion) {
|
|
1608
|
+
await this.completeIterationObservationStream(completion);
|
|
1609
|
+
}
|
|
1429
1610
|
stop() {
|
|
1430
1611
|
this.session = null;
|
|
1431
1612
|
}
|
|
@@ -1449,15 +1630,18 @@ class ExpandedEventReportingSink {
|
|
|
1449
1630
|
return this.session;
|
|
1450
1631
|
}
|
|
1451
1632
|
buildPayload(events, staticTags = {}) {
|
|
1633
|
+
const projectedEvents = this.preservesRawIterationObservations
|
|
1634
|
+
? events.map(removeSdkPercentileFields)
|
|
1635
|
+
: events;
|
|
1452
1636
|
return {
|
|
1453
1637
|
sinkName: this.sinkName,
|
|
1454
1638
|
runId: this.getSession().runId,
|
|
1455
1639
|
sessionId: this.getSession().sessionId,
|
|
1456
|
-
events:
|
|
1640
|
+
events: projectedEvents.map((event) => ({
|
|
1457
1641
|
...event,
|
|
1458
1642
|
tags: { ...staticTags, ...event.tags }
|
|
1459
1643
|
})),
|
|
1460
|
-
metrics: createReportingSinkMetricPoints(this.sinkName,
|
|
1644
|
+
metrics: createReportingSinkMetricPoints(this.sinkName, projectedEvents, staticTags)
|
|
1461
1645
|
};
|
|
1462
1646
|
}
|
|
1463
1647
|
}
|
|
@@ -1491,22 +1675,43 @@ class ExpandedHttpJsonReportingSink extends ExpandedEventReportingSink {
|
|
|
1491
1675
|
body: JSON.stringify(this.buildPayload(events, this.staticTags))
|
|
1492
1676
|
}, this.timeoutMs, this.constructor.name);
|
|
1493
1677
|
}
|
|
1678
|
+
async saveIterationBatch(batch) {
|
|
1679
|
+
await this.persistCanonicalGzipPayload(batch);
|
|
1680
|
+
}
|
|
1681
|
+
async completeIterationObservationStream(completion) {
|
|
1682
|
+
await this.persistCanonicalGzipPayload(completion);
|
|
1683
|
+
}
|
|
1684
|
+
async persistCanonicalGzipPayload(payload) {
|
|
1685
|
+
const compressed = await gzipJsonAsync(payload);
|
|
1686
|
+
await postWithTimeout(this.fetchImpl, `${trimTrailingSlashes(this.baseUrl)}${normalizePath(this.endpointPath)}`, {
|
|
1687
|
+
method: "POST",
|
|
1688
|
+
headers: {
|
|
1689
|
+
"Content-Type": "application/json",
|
|
1690
|
+
"Content-Encoding": "gzip",
|
|
1691
|
+
...this.headers
|
|
1692
|
+
},
|
|
1693
|
+
body: Uint8Array.from(compressed).buffer
|
|
1694
|
+
}, this.timeoutMs, this.constructor.name);
|
|
1695
|
+
}
|
|
1494
1696
|
}
|
|
1495
1697
|
class PrometheusRemoteWriteReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1496
1698
|
constructor(options = {}) {
|
|
1497
1699
|
super("prometheus-remote-write", "extensions.reporting_sinks.prometheus_remote_write", "/api/v1/write", options);
|
|
1700
|
+
this.iterationObservationShapeLimited = true;
|
|
1498
1701
|
}
|
|
1499
1702
|
}
|
|
1500
1703
|
exports.PrometheusRemoteWriteReportingSink = PrometheusRemoteWriteReportingSink;
|
|
1501
1704
|
class CloudWatchReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1502
1705
|
constructor(options = {}) {
|
|
1503
1706
|
super("cloudwatch", "extensions.reporting_sinks.cloudwatch", "/loadstrike/events", options);
|
|
1707
|
+
this.iterationObservationShapeLimited = true;
|
|
1504
1708
|
}
|
|
1505
1709
|
}
|
|
1506
1710
|
exports.CloudWatchReportingSink = CloudWatchReportingSink;
|
|
1507
1711
|
class DynatraceReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1508
1712
|
constructor(options = {}) {
|
|
1509
1713
|
super("dynatrace", "extensions.reporting_sinks.dynatrace", "/api/v2/logs/ingest", options);
|
|
1714
|
+
this.iterationObservationShapeLimited = true;
|
|
1510
1715
|
}
|
|
1511
1716
|
}
|
|
1512
1717
|
exports.DynatraceReportingSink = DynatraceReportingSink;
|
|
@@ -1525,12 +1730,26 @@ exports.OpenSearchReportingSink = OpenSearchReportingSink;
|
|
|
1525
1730
|
class NewRelicReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1526
1731
|
constructor(options = {}) {
|
|
1527
1732
|
super("new-relic", "extensions.reporting_sinks.new_relic", "/log/v1", options);
|
|
1733
|
+
this.iterationObservationShapeLimited = true;
|
|
1528
1734
|
}
|
|
1529
1735
|
}
|
|
1530
1736
|
exports.NewRelicReportingSink = NewRelicReportingSink;
|
|
1531
1737
|
class GenericWebhookReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1532
1738
|
constructor(options = {}) {
|
|
1533
1739
|
super("webhook", "extensions.reporting_sinks.webhook", "/loadstrike", options);
|
|
1740
|
+
this.preservesRawIterationObservations = true;
|
|
1741
|
+
}
|
|
1742
|
+
async saveIterationBatch(batch) {
|
|
1743
|
+
await this.persistCanonicalGzipPayload(batch);
|
|
1744
|
+
}
|
|
1745
|
+
async SaveIterationBatch(batch) {
|
|
1746
|
+
await this.saveIterationBatch(batch);
|
|
1747
|
+
}
|
|
1748
|
+
async completeIterationObservationStream(completion) {
|
|
1749
|
+
await this.persistCanonicalGzipPayload(completion);
|
|
1750
|
+
}
|
|
1751
|
+
async CompleteIterationObservationStream(completion) {
|
|
1752
|
+
await this.completeIterationObservationStream(completion);
|
|
1534
1753
|
}
|
|
1535
1754
|
}
|
|
1536
1755
|
exports.GenericWebhookReportingSink = GenericWebhookReportingSink;
|
|
@@ -1542,6 +1761,12 @@ class KafkaReportingSink extends ExpandedEventReportingSink {
|
|
|
1542
1761
|
this.staticTags = normalizeStringMap(optionRecord(source, "staticTags", "StaticTags"));
|
|
1543
1762
|
this.publishAsync = pickRecordValue(source, "publishAsync", "PublishAsync");
|
|
1544
1763
|
}
|
|
1764
|
+
async saveIterationBatch(batch) {
|
|
1765
|
+
await this.publishCanonicalValue(batch);
|
|
1766
|
+
}
|
|
1767
|
+
async completeIterationObservationStream(completion) {
|
|
1768
|
+
await this.publishCanonicalValue(completion);
|
|
1769
|
+
}
|
|
1545
1770
|
async persistEvents(events) {
|
|
1546
1771
|
if (!events.length) {
|
|
1547
1772
|
return;
|
|
@@ -1551,11 +1776,18 @@ class KafkaReportingSink extends ExpandedEventReportingSink {
|
|
|
1551
1776
|
}
|
|
1552
1777
|
await this.publishAsync(this.topic, JSON.stringify(this.buildPayload(events, this.staticTags)));
|
|
1553
1778
|
}
|
|
1779
|
+
async publishCanonicalValue(value) {
|
|
1780
|
+
if (!this.publishAsync) {
|
|
1781
|
+
throw new Error("KafkaReportingSink requires PublishAsync when a Kafka producer is not configured by the host application.");
|
|
1782
|
+
}
|
|
1783
|
+
await this.publishAsync(this.topic, JSON.stringify(value));
|
|
1784
|
+
}
|
|
1554
1785
|
}
|
|
1555
1786
|
exports.KafkaReportingSink = KafkaReportingSink;
|
|
1556
1787
|
class StatsDReportingSinkBase extends ExpandedEventReportingSink {
|
|
1557
1788
|
constructor(sinkName, licenseFeature, options = {}, dogStatsD = false) {
|
|
1558
1789
|
super(sinkName, licenseFeature);
|
|
1790
|
+
this.iterationObservationShapeLimited = true;
|
|
1559
1791
|
const source = asRecord(options);
|
|
1560
1792
|
this.prefix = optionString(source, "prefix", "Prefix").trim() || "loadstrike";
|
|
1561
1793
|
this.host = optionString(source, "host", "Host").trim() || "127.0.0.1";
|
|
@@ -1564,6 +1796,47 @@ class StatsDReportingSinkBase extends ExpandedEventReportingSink {
|
|
|
1564
1796
|
this.sendLineAsync = pickRecordValue(source, "sendLineAsync", "SendLineAsync");
|
|
1565
1797
|
this.dogStatsD = dogStatsD;
|
|
1566
1798
|
}
|
|
1799
|
+
async saveIterationBatch(batch) {
|
|
1800
|
+
for (const observation of batch.observations) {
|
|
1801
|
+
const tags = {
|
|
1802
|
+
run_id: batch.runId,
|
|
1803
|
+
result_owner_id: batch.resultOwnerId,
|
|
1804
|
+
scenario_name: observation.scenarioName,
|
|
1805
|
+
phase: observation.phase,
|
|
1806
|
+
outcome: observation.isSuccess ? "success" : "failure"
|
|
1807
|
+
};
|
|
1808
|
+
const occurredUtc = new Date();
|
|
1809
|
+
await this.sendLine(this.formatPoint({
|
|
1810
|
+
metricName: "iteration.reported_latency_us",
|
|
1811
|
+
metricKind: "gauge",
|
|
1812
|
+
occurredUtc,
|
|
1813
|
+
value: Number(observation.reportedLatencyUs64),
|
|
1814
|
+
unitOfMeasure: "us",
|
|
1815
|
+
tags
|
|
1816
|
+
}));
|
|
1817
|
+
await this.sendLine(this.formatPoint({
|
|
1818
|
+
metricName: "iteration.attempt",
|
|
1819
|
+
metricKind: "count",
|
|
1820
|
+
occurredUtc,
|
|
1821
|
+
value: 1,
|
|
1822
|
+
unitOfMeasure: "count",
|
|
1823
|
+
tags
|
|
1824
|
+
}));
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
async completeIterationObservationStream(completion) {
|
|
1828
|
+
await this.sendLine(this.formatPoint({
|
|
1829
|
+
metricName: "observation.reporting_complete",
|
|
1830
|
+
metricKind: "gauge",
|
|
1831
|
+
occurredUtc: new Date(),
|
|
1832
|
+
value: completion.reportingComplete ? 1 : 0,
|
|
1833
|
+
unitOfMeasure: "boolean",
|
|
1834
|
+
tags: {
|
|
1835
|
+
run_id: completion.runId,
|
|
1836
|
+
result_owner_id: completion.resultOwnerId
|
|
1837
|
+
}
|
|
1838
|
+
}));
|
|
1839
|
+
}
|
|
1567
1840
|
async persistEvents(events) {
|
|
1568
1841
|
const points = createReportingSinkMetricPoints(this.sinkName, events, this.tags);
|
|
1569
1842
|
for (const point of points) {
|
|
@@ -1606,6 +1879,8 @@ exports.NetdataStatsDReportingSink = NetdataStatsDReportingSink;
|
|
|
1606
1879
|
class JsonlFileReportingSink extends ExpandedEventReportingSink {
|
|
1607
1880
|
constructor(options = {}) {
|
|
1608
1881
|
super("jsonl", "extensions.reporting_sinks.jsonl");
|
|
1882
|
+
this.preservesRawIterationObservations = true;
|
|
1883
|
+
this.writeTail = Promise.resolve();
|
|
1609
1884
|
const source = asRecord(options);
|
|
1610
1885
|
this.filePath = optionString(source, "filePath", "FilePath").trim() || "loadstrike-reporting.jsonl";
|
|
1611
1886
|
this.staticTags = normalizeStringMap(optionRecord(source, "staticTags", "StaticTags"));
|
|
@@ -1614,8 +1889,28 @@ class JsonlFileReportingSink extends ExpandedEventReportingSink {
|
|
|
1614
1889
|
if (!events.length) {
|
|
1615
1890
|
return;
|
|
1616
1891
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1892
|
+
await this.persistCanonicalPayload(this.buildPayload(events, this.staticTags));
|
|
1893
|
+
}
|
|
1894
|
+
async saveIterationBatch(batch) {
|
|
1895
|
+
await this.persistCanonicalPayload(batch);
|
|
1896
|
+
}
|
|
1897
|
+
async SaveIterationBatch(batch) {
|
|
1898
|
+
await this.saveIterationBatch(batch);
|
|
1899
|
+
}
|
|
1900
|
+
async completeIterationObservationStream(completion) {
|
|
1901
|
+
await this.persistCanonicalPayload(completion);
|
|
1902
|
+
}
|
|
1903
|
+
async CompleteIterationObservationStream(completion) {
|
|
1904
|
+
await this.completeIterationObservationStream(completion);
|
|
1905
|
+
}
|
|
1906
|
+
persistCanonicalPayload(payload) {
|
|
1907
|
+
const line = `${JSON.stringify(payload)}\n`;
|
|
1908
|
+
const write = this.writeTail.then(async () => {
|
|
1909
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(this.filePath), { recursive: true });
|
|
1910
|
+
await (0, promises_1.appendFile)(this.filePath, line, "utf8");
|
|
1911
|
+
});
|
|
1912
|
+
this.writeTail = write.catch(() => undefined);
|
|
1913
|
+
return write;
|
|
1619
1914
|
}
|
|
1620
1915
|
}
|
|
1621
1916
|
exports.JsonlFileReportingSink = JsonlFileReportingSink;
|
|
@@ -1663,9 +1958,18 @@ function createRunResultEvents(session, result) {
|
|
|
1663
1958
|
completed_utc: result.completedUtc,
|
|
1664
1959
|
report_file_count: result.reportFiles.length,
|
|
1665
1960
|
disabled_sink_count: result.disabledSinks.length,
|
|
1666
|
-
sink_error_count: result.sinkErrors.length
|
|
1961
|
+
sink_error_count: result.sinkErrors.length,
|
|
1962
|
+
reporting_complete: result.reportingComplete ?? true,
|
|
1963
|
+
observation_last_batch_sequence_64: result.observationDeliveryStats?.lastBatchSequence64 ?? "-1",
|
|
1964
|
+
observation_captured_count_64: result.observationDeliveryStats?.capturedCount64 ?? "0",
|
|
1965
|
+
observation_delivered_count_64: result.observationDeliveryStats?.deliveredCount64 ?? "0",
|
|
1966
|
+
observation_dropped_buffer_count_64: result.observationDeliveryStats?.droppedBufferCount64 ?? "0",
|
|
1967
|
+
observation_dropped_sink_count_64: result.observationDeliveryStats?.droppedSinkCount64 ?? "0"
|
|
1667
1968
|
})
|
|
1668
1969
|
];
|
|
1970
|
+
for (const row of result.correlationRows ?? []) {
|
|
1971
|
+
events.push(createCorrelationOutcomeEvent(session, occurredUtc, row));
|
|
1972
|
+
}
|
|
1669
1973
|
for (const reportFile of result.reportFiles) {
|
|
1670
1974
|
events.push(createReportingEvent(session, occurredUtc, "run.report.final", null, null, {
|
|
1671
1975
|
phase: "final",
|
|
@@ -1696,8 +2000,65 @@ function createRunResultEvents(session, result) {
|
|
|
1696
2000
|
attempts: sinkError.attempts
|
|
1697
2001
|
}));
|
|
1698
2002
|
}
|
|
2003
|
+
for (const warning of result.generatorWarnings ?? []) {
|
|
2004
|
+
events.push(createReportingEvent(session, occurredUtc, "run.generator-warning.final", warning.scenarioName || null, null, {
|
|
2005
|
+
phase: "final",
|
|
2006
|
+
entity: "generator-warning",
|
|
2007
|
+
warning_code: warning.code,
|
|
2008
|
+
...(warning.sinkName ? { sink_name: warning.sinkName } : {})
|
|
2009
|
+
}, {
|
|
2010
|
+
warning_code: warning.code,
|
|
2011
|
+
sink_name: warning.sinkName ?? "",
|
|
2012
|
+
scenario_name: warning.scenarioName,
|
|
2013
|
+
scenario_index: warning.scenarioIndex ?? -1,
|
|
2014
|
+
simulation_index: warning.simulationIndex,
|
|
2015
|
+
simulation_kind: warning.simulationKind ?? "",
|
|
2016
|
+
count_64: warning.count64,
|
|
2017
|
+
message: warning.message,
|
|
2018
|
+
first_observed_utc_ns: warning.firstObservedUtcNs,
|
|
2019
|
+
last_observed_utc_ns: warning.lastObservedUtcNs
|
|
2020
|
+
}));
|
|
2021
|
+
}
|
|
1699
2022
|
return events;
|
|
1700
2023
|
}
|
|
2024
|
+
function createCorrelationOutcomeEvent(session, fallbackOccurredUtc, row) {
|
|
2025
|
+
const occurredToken = optionString(row, "occurredUtc", "OccurredUtc");
|
|
2026
|
+
const parsedOccurredUtc = occurredToken ? new Date(occurredToken) : fallbackOccurredUtc;
|
|
2027
|
+
const occurredUtc = Number.isFinite(parsedOccurredUtc.getTime())
|
|
2028
|
+
? parsedOccurredUtc
|
|
2029
|
+
: fallbackOccurredUtc;
|
|
2030
|
+
const scenarioName = optionString(row, "scenario", "Scenario");
|
|
2031
|
+
const source = optionString(row, "source", "Source");
|
|
2032
|
+
const destination = optionString(row, "destination", "Destination");
|
|
2033
|
+
const runMode = optionString(row, "runMode", "RunMode");
|
|
2034
|
+
const statusCode = optionString(row, "statusCode", "StatusCode");
|
|
2035
|
+
const gatherByField = optionString(row, "gatherByField", "GatherByField");
|
|
2036
|
+
const gatherByValue = optionString(row, "gatherByValue", "GatherByValue");
|
|
2037
|
+
return createReportingEvent(session, occurredUtc, "correlation.outcome.final", scenarioName || null, null, {
|
|
2038
|
+
phase: "final",
|
|
2039
|
+
entity: "correlation-outcome",
|
|
2040
|
+
source,
|
|
2041
|
+
destination,
|
|
2042
|
+
run_mode: runMode,
|
|
2043
|
+
status_code: statusCode,
|
|
2044
|
+
gather_by_field: gatherByField,
|
|
2045
|
+
gather_by_value: gatherByValue
|
|
2046
|
+
}, {
|
|
2047
|
+
occurred_utc: occurredToken || occurredUtc.toISOString(),
|
|
2048
|
+
source,
|
|
2049
|
+
destination,
|
|
2050
|
+
run_mode: runMode,
|
|
2051
|
+
status_code: statusCode,
|
|
2052
|
+
is_success: pickBooleanValue(row, false, "isSuccess", "IsSuccess"),
|
|
2053
|
+
is_failure: pickBooleanValue(row, false, "isFailure", "IsFailure"),
|
|
2054
|
+
gather_by_field: gatherByField,
|
|
2055
|
+
gather_by_value: gatherByValue,
|
|
2056
|
+
tracking_id: optionString(row, "trackingId", "TrackingId"),
|
|
2057
|
+
event_id: optionString(row, "eventId", "EventId"),
|
|
2058
|
+
latency_ms: optionNumber(row, "latencyMs", "LatencyMs") ?? 0,
|
|
2059
|
+
message: optionString(row, "message", "Message")
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
1701
2062
|
function createNodeSummaryEvent(session, occurredUtc, stats) {
|
|
1702
2063
|
return createReportingEvent(session, occurredUtc, "test.final", null, null, {
|
|
1703
2064
|
phase: "final",
|
|
@@ -1883,6 +2244,125 @@ function createReportingEvent(session, occurredUtc, eventType, scenarioName, ste
|
|
|
1883
2244
|
fields: eventFields
|
|
1884
2245
|
};
|
|
1885
2246
|
}
|
|
2247
|
+
function removeSdkPercentileFields(event) {
|
|
2248
|
+
const fields = Object.fromEntries(Object.entries(event.fields).filter(([name]) => !/_(?:latency|bytes)_p(?:50|75|95|99)(?:_|$)/iu.test(name)));
|
|
2249
|
+
return {
|
|
2250
|
+
...event,
|
|
2251
|
+
fields
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
function createIterationObservationEvents(session, batch) {
|
|
2255
|
+
const events = [];
|
|
2256
|
+
const occurredUtc = dateFromUtcNanoseconds(batch.createdUtcNs);
|
|
2257
|
+
for (const observation of batch.observations) {
|
|
2258
|
+
const tags = {
|
|
2259
|
+
run_id: batch.runId,
|
|
2260
|
+
result_owner_id: batch.resultOwnerId,
|
|
2261
|
+
phase: observation.phase,
|
|
2262
|
+
scenario_name: observation.scenarioName,
|
|
2263
|
+
simulation_kind: observation.simulationKind,
|
|
2264
|
+
is_final_attempt: observation.isFinalAttempt ? "true" : "false"
|
|
2265
|
+
};
|
|
2266
|
+
events.push({
|
|
2267
|
+
runId: batch.runId,
|
|
2268
|
+
eventType: "iteration.observation",
|
|
2269
|
+
occurredUtc,
|
|
2270
|
+
sessionId: batch.sessionId,
|
|
2271
|
+
testSuite: session.testSuite,
|
|
2272
|
+
testName: session.testName,
|
|
2273
|
+
clusterId: session.clusterId,
|
|
2274
|
+
nodeType: session.nodeType,
|
|
2275
|
+
machineName: session.machineName,
|
|
2276
|
+
scenarioName: observation.scenarioName,
|
|
2277
|
+
stepName: null,
|
|
2278
|
+
tags,
|
|
2279
|
+
fields: {
|
|
2280
|
+
schema_version: observation.schemaVersion,
|
|
2281
|
+
batch_id: batch.batchId,
|
|
2282
|
+
observation_id: observation.observationId,
|
|
2283
|
+
iteration_id: observation.iterationId,
|
|
2284
|
+
process_group: observation.processGroup,
|
|
2285
|
+
scenario_index: observation.scenarioIndex,
|
|
2286
|
+
simulation_index: observation.simulationIndex,
|
|
2287
|
+
global_ordinal_64: observation.globalOrdinal64,
|
|
2288
|
+
shard_index: observation.shardIndex,
|
|
2289
|
+
shard_count: observation.shardCount,
|
|
2290
|
+
attempt_index: observation.attemptIndex,
|
|
2291
|
+
started_utc_ns: observation.startedUtcNs,
|
|
2292
|
+
completed_utc_ns: observation.completedUtcNs,
|
|
2293
|
+
observed_latency_us_64: observation.observedLatencyUs64,
|
|
2294
|
+
reported_latency_us_64: observation.reportedLatencyUs64,
|
|
2295
|
+
is_success: observation.isSuccess,
|
|
2296
|
+
status_code: observation.statusCode,
|
|
2297
|
+
size_bytes_64: observation.sizeBytes64
|
|
2298
|
+
}
|
|
2299
|
+
});
|
|
2300
|
+
for (const step of observation.steps) {
|
|
2301
|
+
events.push({
|
|
2302
|
+
runId: batch.runId,
|
|
2303
|
+
eventType: "iteration.step",
|
|
2304
|
+
occurredUtc,
|
|
2305
|
+
sessionId: batch.sessionId,
|
|
2306
|
+
testSuite: session.testSuite,
|
|
2307
|
+
testName: session.testName,
|
|
2308
|
+
clusterId: session.clusterId,
|
|
2309
|
+
nodeType: session.nodeType,
|
|
2310
|
+
machineName: session.machineName,
|
|
2311
|
+
scenarioName: observation.scenarioName,
|
|
2312
|
+
stepName: step.stepName,
|
|
2313
|
+
tags: { ...tags },
|
|
2314
|
+
fields: {
|
|
2315
|
+
schema_version: observation.schemaVersion,
|
|
2316
|
+
batch_id: batch.batchId,
|
|
2317
|
+
observation_id: observation.observationId,
|
|
2318
|
+
iteration_id: observation.iterationId,
|
|
2319
|
+
attempt_index: observation.attemptIndex,
|
|
2320
|
+
sort_index: step.sortIndex,
|
|
2321
|
+
started_utc_ns: step.startedUtcNs,
|
|
2322
|
+
completed_utc_ns: step.completedUtcNs,
|
|
2323
|
+
observed_latency_us_64: step.observedLatencyUs64,
|
|
2324
|
+
reported_latency_us_64: step.reportedLatencyUs64,
|
|
2325
|
+
is_success: step.isSuccess,
|
|
2326
|
+
status_code: step.statusCode,
|
|
2327
|
+
size_bytes_64: step.sizeBytes64
|
|
2328
|
+
}
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
return events;
|
|
2333
|
+
}
|
|
2334
|
+
function createIterationCompletionEvents(session, completion) {
|
|
2335
|
+
const occurredUtc = dateFromUtcNanoseconds(completion.completedUtcNs);
|
|
2336
|
+
return [{
|
|
2337
|
+
runId: completion.runId,
|
|
2338
|
+
eventType: "observation.stream.completed",
|
|
2339
|
+
occurredUtc,
|
|
2340
|
+
sessionId: completion.sessionId,
|
|
2341
|
+
testSuite: session.testSuite,
|
|
2342
|
+
testName: session.testName,
|
|
2343
|
+
clusterId: session.clusterId,
|
|
2344
|
+
nodeType: session.nodeType,
|
|
2345
|
+
machineName: session.machineName,
|
|
2346
|
+
scenarioName: null,
|
|
2347
|
+
stepName: null,
|
|
2348
|
+
tags: {
|
|
2349
|
+
run_id: completion.runId,
|
|
2350
|
+
result_owner_id: completion.resultOwnerId,
|
|
2351
|
+
phase: "final"
|
|
2352
|
+
},
|
|
2353
|
+
fields: {
|
|
2354
|
+
schema_version: completion.schemaVersion,
|
|
2355
|
+
process_group: completion.processGroup,
|
|
2356
|
+
last_batch_sequence_64: completion.lastBatchSequence64,
|
|
2357
|
+
captured_count_64: completion.capturedCount64,
|
|
2358
|
+
delivered_count_64: completion.deliveredCount64,
|
|
2359
|
+
dropped_buffer_count_64: completion.droppedBufferCount64,
|
|
2360
|
+
dropped_sink_count_64: completion.droppedSinkCount64,
|
|
2361
|
+
reporting_complete: completion.reportingComplete,
|
|
2362
|
+
completed_utc_ns: completion.completedUtcNs
|
|
2363
|
+
}
|
|
2364
|
+
}];
|
|
2365
|
+
}
|
|
1886
2366
|
function portalEventPayload(event, index) {
|
|
1887
2367
|
return {
|
|
1888
2368
|
eventId: portalEventId(event, index),
|
|
@@ -1915,6 +2395,22 @@ function portalEventId(event, index) {
|
|
|
1915
2395
|
});
|
|
1916
2396
|
return `lsr_${(0, node_crypto_1.createHash)("sha256").update(material).digest("hex").slice(0, 40)}`;
|
|
1917
2397
|
}
|
|
2398
|
+
function dateFromUtcNanoseconds(value) {
|
|
2399
|
+
try {
|
|
2400
|
+
const nanoseconds = BigInt(value);
|
|
2401
|
+
if (nanoseconds < 0n) {
|
|
2402
|
+
return new Date(0);
|
|
2403
|
+
}
|
|
2404
|
+
const milliseconds = nanoseconds / 1000000n;
|
|
2405
|
+
const numeric = Number(milliseconds);
|
|
2406
|
+
return Number.isFinite(numeric) && numeric <= 8640000000000000
|
|
2407
|
+
? new Date(numeric)
|
|
2408
|
+
: new Date(0);
|
|
2409
|
+
}
|
|
2410
|
+
catch {
|
|
2411
|
+
return new Date(0);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
1918
2414
|
function addMeasurementFields(fields, prefix, measurement) {
|
|
1919
2415
|
const request = measurement?.request ?? { count: 0, percent: 0, rps: 0 };
|
|
1920
2416
|
const latency = measurement?.latency ?? {
|
|
@@ -1955,10 +2451,6 @@ function addMeasurementFields(fields, prefix, measurement) {
|
|
|
1955
2451
|
fields[`${prefix}_latency_min_ms`] = latency.minMs ?? 0;
|
|
1956
2452
|
fields[`${prefix}_latency_mean_ms`] = latency.meanMs ?? 0;
|
|
1957
2453
|
fields[`${prefix}_latency_max_ms`] = latency.maxMs ?? 0;
|
|
1958
|
-
fields[`${prefix}_latency_p50_ms`] = latency.percent50 ?? 0;
|
|
1959
|
-
fields[`${prefix}_latency_p75_ms`] = latency.percent75 ?? 0;
|
|
1960
|
-
fields[`${prefix}_latency_p95_ms`] = latency.percent95 ?? 0;
|
|
1961
|
-
fields[`${prefix}_latency_p99_ms`] = latency.percent99 ?? 0;
|
|
1962
2454
|
fields[`${prefix}_latency_std_dev`] = latency.stdDev ?? 0;
|
|
1963
2455
|
fields[`${prefix}_latency_le_800_count`] = latencyCount.lessOrEq800 ?? 0;
|
|
1964
2456
|
fields[`${prefix}_latency_gt_800_lt_1200_count`] = latencyCount.more800Less1200 ?? 0;
|
|
@@ -1967,10 +2459,6 @@ function addMeasurementFields(fields, prefix, measurement) {
|
|
|
1967
2459
|
fields[`${prefix}_bytes_min`] = dataTransfer.minBytes ?? 0;
|
|
1968
2460
|
fields[`${prefix}_bytes_mean`] = dataTransfer.meanBytes ?? 0;
|
|
1969
2461
|
fields[`${prefix}_bytes_max`] = dataTransfer.maxBytes ?? 0;
|
|
1970
|
-
fields[`${prefix}_bytes_p50`] = dataTransfer.percent50 ?? 0;
|
|
1971
|
-
fields[`${prefix}_bytes_p75`] = dataTransfer.percent75 ?? 0;
|
|
1972
|
-
fields[`${prefix}_bytes_p95`] = dataTransfer.percent95 ?? 0;
|
|
1973
|
-
fields[`${prefix}_bytes_p99`] = dataTransfer.percent99 ?? 0;
|
|
1974
2462
|
fields[`${prefix}_bytes_std_dev`] = dataTransfer.stdDev ?? 0;
|
|
1975
2463
|
fields[`${prefix}_status_code_count`] = statusCodes.length;
|
|
1976
2464
|
}
|
|
@@ -3106,27 +3594,93 @@ function deepCloneValue(value) {
|
|
|
3106
3594
|
}
|
|
3107
3595
|
return value;
|
|
3108
3596
|
}
|
|
3597
|
+
function gzipJsonRequestBody(value) {
|
|
3598
|
+
return Uint8Array.from((0, iteration_observations_js_1.serializeIterationObservationBatchGzipJson)(value)).buffer;
|
|
3599
|
+
}
|
|
3109
3600
|
async function postWithTimeout(fetchImpl, url, init, timeoutMs, sinkName) {
|
|
3110
3601
|
const controller = new AbortController();
|
|
3111
3602
|
const timer = setTimeout(() => controller.abort(), Math.max(timeoutMs, 1));
|
|
3112
3603
|
try {
|
|
3113
3604
|
const response = await fetchImpl(url, { ...init, signal: controller.signal });
|
|
3114
|
-
const body = await readResponseBodyText(response);
|
|
3115
3605
|
if (!response.ok) {
|
|
3116
|
-
throw new
|
|
3606
|
+
throw new ReportingSinkHttpError(sinkName, response.status, readHttpResponseStatusText(response), readHttpResponseRequestId(response));
|
|
3117
3607
|
}
|
|
3118
|
-
return
|
|
3608
|
+
return await readResponseBodyText(response);
|
|
3119
3609
|
}
|
|
3120
3610
|
finally {
|
|
3121
3611
|
clearTimeout(timer);
|
|
3122
3612
|
}
|
|
3123
3613
|
}
|
|
3124
3614
|
async function readResponseBodyText(response) {
|
|
3125
|
-
const
|
|
3126
|
-
if (typeof
|
|
3615
|
+
const body = response.body;
|
|
3616
|
+
if (body && typeof body.getReader === "function") {
|
|
3617
|
+
const reader = body.getReader();
|
|
3618
|
+
const chunks = [];
|
|
3619
|
+
let bytes = 0;
|
|
3620
|
+
try {
|
|
3621
|
+
while (true) {
|
|
3622
|
+
const next = await reader.read();
|
|
3623
|
+
if (next.done)
|
|
3624
|
+
break;
|
|
3625
|
+
if (!next.value)
|
|
3626
|
+
continue;
|
|
3627
|
+
if (bytes + next.value.byteLength > MAXIMUM_HTTP_RESPONSE_BODY_BYTES) {
|
|
3628
|
+
await reader.cancel();
|
|
3629
|
+
throw new Error("Reporting sink response exceeded the bounded response limit.");
|
|
3630
|
+
}
|
|
3631
|
+
chunks.push(next.value);
|
|
3632
|
+
bytes += next.value.byteLength;
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
finally {
|
|
3636
|
+
reader.releaseLock();
|
|
3637
|
+
}
|
|
3638
|
+
return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))).toString("utf8");
|
|
3639
|
+
}
|
|
3640
|
+
return "";
|
|
3641
|
+
}
|
|
3642
|
+
function readHttpResponseStatusText(response) {
|
|
3643
|
+
try {
|
|
3644
|
+
return normalizeHttpMetadata(response.statusText);
|
|
3645
|
+
}
|
|
3646
|
+
catch {
|
|
3647
|
+
return "";
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
function readHttpResponseRequestId(response) {
|
|
3651
|
+
for (const name of [
|
|
3652
|
+
"x-request-id",
|
|
3653
|
+
"x-correlation-id",
|
|
3654
|
+
"request-id",
|
|
3655
|
+
"traceparent"
|
|
3656
|
+
]) {
|
|
3657
|
+
try {
|
|
3658
|
+
const value = response.headers?.get(name);
|
|
3659
|
+
if (value) {
|
|
3660
|
+
return normalizeHttpMetadata(value);
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
catch {
|
|
3664
|
+
return "";
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
return "";
|
|
3668
|
+
}
|
|
3669
|
+
function normalizeHttpMetadata(value) {
|
|
3670
|
+
let text;
|
|
3671
|
+
try {
|
|
3672
|
+
const source = String(value ?? "");
|
|
3673
|
+
text = source.length <= MAXIMUM_HTTP_METADATA_CHARACTERS
|
|
3674
|
+
? source
|
|
3675
|
+
: source.slice(0, MAXIMUM_HTTP_METADATA_CHARACTERS - HTTP_METADATA_TRUNCATION_SUFFIX.length) + HTTP_METADATA_TRUNCATION_SUFFIX;
|
|
3676
|
+
}
|
|
3677
|
+
catch {
|
|
3127
3678
|
return "";
|
|
3128
3679
|
}
|
|
3129
|
-
return
|
|
3680
|
+
return (0, iteration_observation_diagnostics_js_1.redactIterationObservationSecrets)(Buffer.from(text, "utf8").toString("utf8")
|
|
3681
|
+
.replace(/[\u0000-\u001F\u007F-\u009F]/gu, " ")
|
|
3682
|
+
.replace(/\s{2,}/gu, " ")
|
|
3683
|
+
.trim());
|
|
3130
3684
|
}
|
|
3131
3685
|
function validatePortalIngestResponse(responseBody) {
|
|
3132
3686
|
const text = String(responseBody ?? "").trim();
|
|
@@ -3179,6 +3733,8 @@ exports.__loadstrikeTestExports = {
|
|
|
3179
3733
|
cloneScenarioStats,
|
|
3180
3734
|
cloneSessionStartInfo,
|
|
3181
3735
|
createFinalStatsEvents,
|
|
3736
|
+
createIterationCompletionEvents,
|
|
3737
|
+
createIterationObservationEvents,
|
|
3182
3738
|
createRealtimeStatsEvents,
|
|
3183
3739
|
createRunResultEvents,
|
|
3184
3740
|
createReportingEvent,
|
|
@@ -3195,6 +3751,7 @@ exports.__loadstrikeTestExports = {
|
|
|
3195
3751
|
normalizeStringMap,
|
|
3196
3752
|
optionNumber,
|
|
3197
3753
|
optionString,
|
|
3754
|
+
portalEventPayload,
|
|
3198
3755
|
pickBooleanValue,
|
|
3199
3756
|
pickRecordValue,
|
|
3200
3757
|
postWithTimeout,
|