@loadstrike/loadstrike-sdk 1.0.30201 → 1.0.30401
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/cjs/cluster.js +2417 -7
- package/dist/cjs/index.js +13 -2
- package/dist/cjs/iteration-observations.js +711 -0
- package/dist/cjs/load-engine-v2.js +966 -0
- package/dist/cjs/local.js +73 -20
- package/dist/cjs/reporting.js +122 -12
- package/dist/cjs/runtime.js +2368 -129
- package/dist/cjs/sinks.js +471 -17
- package/dist/cjs/transports.js +84 -146
- package/dist/esm/cluster.js +2386 -7
- package/dist/esm/index.js +1 -0
- package/dist/esm/iteration-observations.js +698 -0
- package/dist/esm/load-engine-v2.js +942 -0
- package/dist/esm/local.js +73 -20
- package/dist/esm/reporting.js +122 -12
- package/dist/esm/runtime.js +2369 -130
- package/dist/esm/sinks.js +471 -17
- package/dist/esm/transports.js +84 -146
- package/dist/types/cluster.d.ts +379 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/iteration-observations.d.ts +225 -0
- package/dist/types/load-engine-v2.d.ts +147 -0
- package/dist/types/runtime.d.ts +214 -8
- package/dist/types/sinks.d.ts +67 -0
- package/dist/types/transports.d.ts +2 -8
- package/package.json +3 -4
package/dist/cjs/sinks.js
CHANGED
|
@@ -4,10 +4,25 @@ 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 PORTAL_RUN_TOKEN_PROVIDER = Symbol.for("loadstrike.internal.portal-run-token-provider");
|
|
14
|
+
function gzipJsonAsync(value) {
|
|
15
|
+
const json = Buffer.from(JSON.stringify(value), "utf8");
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
(0, node_zlib_1.gzip)(json, (error, compressed) => {
|
|
18
|
+
if (error) {
|
|
19
|
+
reject(error);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
resolve(compressed);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
11
26
|
const DEFAULT_INFLUX_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:InfluxDb";
|
|
12
27
|
const DEFAULT_GRAFANA_LOKI_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:GrafanaLoki";
|
|
13
28
|
const DEFAULT_TIMESCALEDB_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:TimescaleDb";
|
|
@@ -376,6 +391,28 @@ class CompositeReportingSink {
|
|
|
376
391
|
async SaveRunResult(result) {
|
|
377
392
|
await this.saveRunResult(result);
|
|
378
393
|
}
|
|
394
|
+
async saveIterationBatch(batch) {
|
|
395
|
+
for (const sink of this.sinks) {
|
|
396
|
+
const saveIterationBatch = sink.saveIterationBatch ?? sink.SaveIterationBatch;
|
|
397
|
+
if (saveIterationBatch) {
|
|
398
|
+
await saveIterationBatch.call(sink, batch);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async SaveIterationBatch(batch) {
|
|
403
|
+
await this.saveIterationBatch(batch);
|
|
404
|
+
}
|
|
405
|
+
async completeIterationObservationStream(completion) {
|
|
406
|
+
for (const sink of this.sinks) {
|
|
407
|
+
const completeIterationObservationStream = sink.completeIterationObservationStream ?? sink.CompleteIterationObservationStream;
|
|
408
|
+
if (completeIterationObservationStream) {
|
|
409
|
+
await completeIterationObservationStream.call(sink, completion);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async CompleteIterationObservationStream(completion) {
|
|
414
|
+
await this.completeIterationObservationStream(completion);
|
|
415
|
+
}
|
|
379
416
|
async stop() {
|
|
380
417
|
for (const sink of this.sinks) {
|
|
381
418
|
const stop = sink.stop ?? sink.Stop;
|
|
@@ -391,6 +428,7 @@ class CompositeReportingSink {
|
|
|
391
428
|
exports.CompositeReportingSink = CompositeReportingSink;
|
|
392
429
|
class PortalReportingSink {
|
|
393
430
|
constructor(options = {}) {
|
|
431
|
+
this.iterationObservationPortalSink = true;
|
|
394
432
|
this.sinkName = "portal";
|
|
395
433
|
this.SinkName = "portal";
|
|
396
434
|
this.licenseFeature = "extensions.reporting_sinks.portal";
|
|
@@ -398,6 +436,7 @@ class PortalReportingSink {
|
|
|
398
436
|
this.baseContext = null;
|
|
399
437
|
this.session = null;
|
|
400
438
|
this.runToken = "";
|
|
439
|
+
this.runTokenProvider = null;
|
|
401
440
|
this.ingestUrl = "";
|
|
402
441
|
this.optionsInput = { ...options };
|
|
403
442
|
const source = asRecord(options);
|
|
@@ -414,7 +453,11 @@ class PortalReportingSink {
|
|
|
414
453
|
this.init(context, infraConfig);
|
|
415
454
|
}
|
|
416
455
|
start(session) {
|
|
417
|
-
|
|
456
|
+
const internalProvider = session[PORTAL_RUN_TOKEN_PROVIDER];
|
|
457
|
+
this.runTokenProvider = typeof internalProvider === "function"
|
|
458
|
+
? internalProvider
|
|
459
|
+
: () => String(session.runToken ?? session.RunToken ?? "").trim();
|
|
460
|
+
this.runToken = this.runTokenProvider();
|
|
418
461
|
this.ingestUrl = String(session.portalReportingIngestUrl ?? session.PortalReportingIngestUrl ?? "").trim();
|
|
419
462
|
if (!this.runToken || !this.ingestUrl) {
|
|
420
463
|
throw new Error("PortalReportingSink requires a managed portal reporting session.");
|
|
@@ -444,8 +487,27 @@ class PortalReportingSink {
|
|
|
444
487
|
async SaveRunResult(result) {
|
|
445
488
|
await this.saveRunResult(result);
|
|
446
489
|
}
|
|
490
|
+
async saveIterationBatch(batch) {
|
|
491
|
+
const compressed = await gzipJsonAsync(batch);
|
|
492
|
+
await this.persistObservationPayload({
|
|
493
|
+
compressedObservationBatches: [{
|
|
494
|
+
compression: "gzip-json",
|
|
495
|
+
payloadBase64: compressed.toString("base64")
|
|
496
|
+
}]
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
async SaveIterationBatch(batch) {
|
|
500
|
+
await this.saveIterationBatch(batch);
|
|
501
|
+
}
|
|
502
|
+
async completeIterationObservationStream(completion) {
|
|
503
|
+
await this.persistObservationPayload({ observationStreamCompletions: [completion] });
|
|
504
|
+
}
|
|
505
|
+
async CompleteIterationObservationStream(completion) {
|
|
506
|
+
await this.completeIterationObservationStream(completion);
|
|
507
|
+
}
|
|
447
508
|
stop() {
|
|
448
509
|
this.session = null;
|
|
510
|
+
this.runTokenProvider = null;
|
|
449
511
|
}
|
|
450
512
|
Stop() {
|
|
451
513
|
this.stop();
|
|
@@ -474,12 +536,27 @@ class PortalReportingSink {
|
|
|
474
536
|
method: "POST",
|
|
475
537
|
headers: { "Content-Type": "application/json" },
|
|
476
538
|
body: JSON.stringify({
|
|
477
|
-
runToken: this.
|
|
478
|
-
events: events.map((event, index) => portalEventPayload(event, index))
|
|
539
|
+
runToken: this.currentRunToken(),
|
|
540
|
+
events: events.map((event, index) => portalEventPayload(removeSdkPercentileFields(event), index))
|
|
541
|
+
})
|
|
542
|
+
}, this.timeoutMs, "PortalReportingSink");
|
|
543
|
+
validatePortalIngestResponse(responseBody);
|
|
544
|
+
}
|
|
545
|
+
async persistObservationPayload(payload) {
|
|
546
|
+
const responseBody = await postWithTimeout(this.fetchImpl, this.ingestUrl, {
|
|
547
|
+
method: "POST",
|
|
548
|
+
headers: { "Content-Type": "application/json" },
|
|
549
|
+
body: JSON.stringify({
|
|
550
|
+
runToken: this.currentRunToken(),
|
|
551
|
+
...payload
|
|
479
552
|
})
|
|
480
553
|
}, this.timeoutMs, "PortalReportingSink");
|
|
481
554
|
validatePortalIngestResponse(responseBody);
|
|
482
555
|
}
|
|
556
|
+
currentRunToken() {
|
|
557
|
+
const current = this.runTokenProvider?.() ?? "";
|
|
558
|
+
return current || this.runToken;
|
|
559
|
+
}
|
|
483
560
|
}
|
|
484
561
|
exports.PortalReportingSink = PortalReportingSink;
|
|
485
562
|
function cloneReportingSinkForRun(sink) {
|
|
@@ -556,6 +633,18 @@ class InfluxDbReportingSink {
|
|
|
556
633
|
async SaveRunResult(result) {
|
|
557
634
|
await this.saveRunResult(result);
|
|
558
635
|
}
|
|
636
|
+
async saveIterationBatch(batch) {
|
|
637
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
638
|
+
}
|
|
639
|
+
async SaveIterationBatch(batch) {
|
|
640
|
+
await this.saveIterationBatch(batch);
|
|
641
|
+
}
|
|
642
|
+
async completeIterationObservationStream(completion) {
|
|
643
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
644
|
+
}
|
|
645
|
+
async CompleteIterationObservationStream(completion) {
|
|
646
|
+
await this.completeIterationObservationStream(completion);
|
|
647
|
+
}
|
|
559
648
|
stop() {
|
|
560
649
|
this.session = null;
|
|
561
650
|
}
|
|
@@ -666,6 +755,18 @@ class GrafanaLokiReportingSink {
|
|
|
666
755
|
async SaveRunResult(result) {
|
|
667
756
|
await this.saveRunResult(result);
|
|
668
757
|
}
|
|
758
|
+
async saveIterationBatch(batch) {
|
|
759
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
760
|
+
}
|
|
761
|
+
async SaveIterationBatch(batch) {
|
|
762
|
+
await this.saveIterationBatch(batch);
|
|
763
|
+
}
|
|
764
|
+
async completeIterationObservationStream(completion) {
|
|
765
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
766
|
+
}
|
|
767
|
+
async CompleteIterationObservationStream(completion) {
|
|
768
|
+
await this.completeIterationObservationStream(completion);
|
|
769
|
+
}
|
|
669
770
|
stop() {
|
|
670
771
|
this.session = null;
|
|
671
772
|
}
|
|
@@ -812,6 +913,18 @@ class TimescaleDbReportingSink {
|
|
|
812
913
|
async SaveRunResult(result) {
|
|
813
914
|
await this.saveRunResult(result);
|
|
814
915
|
}
|
|
916
|
+
async saveIterationBatch(batch) {
|
|
917
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
918
|
+
}
|
|
919
|
+
async SaveIterationBatch(batch) {
|
|
920
|
+
await this.saveIterationBatch(batch);
|
|
921
|
+
}
|
|
922
|
+
async completeIterationObservationStream(completion) {
|
|
923
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
924
|
+
}
|
|
925
|
+
async CompleteIterationObservationStream(completion) {
|
|
926
|
+
await this.completeIterationObservationStream(completion);
|
|
927
|
+
}
|
|
815
928
|
async stop() {
|
|
816
929
|
this.session = null;
|
|
817
930
|
if (this.pool) {
|
|
@@ -1088,6 +1201,18 @@ class DatadogReportingSink {
|
|
|
1088
1201
|
async SaveRunResult(result) {
|
|
1089
1202
|
await this.saveRunResult(result);
|
|
1090
1203
|
}
|
|
1204
|
+
async saveIterationBatch(batch) {
|
|
1205
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
1206
|
+
}
|
|
1207
|
+
async SaveIterationBatch(batch) {
|
|
1208
|
+
await this.saveIterationBatch(batch);
|
|
1209
|
+
}
|
|
1210
|
+
async completeIterationObservationStream(completion) {
|
|
1211
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
1212
|
+
}
|
|
1213
|
+
async CompleteIterationObservationStream(completion) {
|
|
1214
|
+
await this.completeIterationObservationStream(completion);
|
|
1215
|
+
}
|
|
1091
1216
|
stop() {
|
|
1092
1217
|
this.session = null;
|
|
1093
1218
|
}
|
|
@@ -1211,6 +1336,18 @@ class SplunkReportingSink {
|
|
|
1211
1336
|
async SaveRunResult(result) {
|
|
1212
1337
|
await this.saveRunResult(result);
|
|
1213
1338
|
}
|
|
1339
|
+
async saveIterationBatch(batch) {
|
|
1340
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
1341
|
+
}
|
|
1342
|
+
async SaveIterationBatch(batch) {
|
|
1343
|
+
await this.saveIterationBatch(batch);
|
|
1344
|
+
}
|
|
1345
|
+
async completeIterationObservationStream(completion) {
|
|
1346
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
1347
|
+
}
|
|
1348
|
+
async CompleteIterationObservationStream(completion) {
|
|
1349
|
+
await this.completeIterationObservationStream(completion);
|
|
1350
|
+
}
|
|
1214
1351
|
stop() {
|
|
1215
1352
|
this.session = null;
|
|
1216
1353
|
}
|
|
@@ -1314,6 +1451,18 @@ class OtelCollectorReportingSink {
|
|
|
1314
1451
|
async SaveRunResult(result) {
|
|
1315
1452
|
await this.saveRunResult(result);
|
|
1316
1453
|
}
|
|
1454
|
+
async saveIterationBatch(batch) {
|
|
1455
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
1456
|
+
}
|
|
1457
|
+
async SaveIterationBatch(batch) {
|
|
1458
|
+
await this.saveIterationBatch(batch);
|
|
1459
|
+
}
|
|
1460
|
+
async completeIterationObservationStream(completion) {
|
|
1461
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
1462
|
+
}
|
|
1463
|
+
async CompleteIterationObservationStream(completion) {
|
|
1464
|
+
await this.completeIterationObservationStream(completion);
|
|
1465
|
+
}
|
|
1317
1466
|
stop() {
|
|
1318
1467
|
this.session = null;
|
|
1319
1468
|
}
|
|
@@ -1391,6 +1540,7 @@ class ExpandedEventReportingSink {
|
|
|
1391
1540
|
constructor(sinkName, licenseFeature) {
|
|
1392
1541
|
this.baseContext = null;
|
|
1393
1542
|
this.session = null;
|
|
1543
|
+
this.preservesRawIterationObservations = true;
|
|
1394
1544
|
this.sinkName = sinkName;
|
|
1395
1545
|
this.SinkName = sinkName;
|
|
1396
1546
|
this.licenseFeature = licenseFeature;
|
|
@@ -1426,6 +1576,18 @@ class ExpandedEventReportingSink {
|
|
|
1426
1576
|
async SaveRunResult(result) {
|
|
1427
1577
|
await this.saveRunResult(result);
|
|
1428
1578
|
}
|
|
1579
|
+
async saveIterationBatch(batch) {
|
|
1580
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
1581
|
+
}
|
|
1582
|
+
async SaveIterationBatch(batch) {
|
|
1583
|
+
await this.saveIterationBatch(batch);
|
|
1584
|
+
}
|
|
1585
|
+
async completeIterationObservationStream(completion) {
|
|
1586
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
1587
|
+
}
|
|
1588
|
+
async CompleteIterationObservationStream(completion) {
|
|
1589
|
+
await this.completeIterationObservationStream(completion);
|
|
1590
|
+
}
|
|
1429
1591
|
stop() {
|
|
1430
1592
|
this.session = null;
|
|
1431
1593
|
}
|
|
@@ -1449,15 +1611,18 @@ class ExpandedEventReportingSink {
|
|
|
1449
1611
|
return this.session;
|
|
1450
1612
|
}
|
|
1451
1613
|
buildPayload(events, staticTags = {}) {
|
|
1614
|
+
const projectedEvents = this.preservesRawIterationObservations
|
|
1615
|
+
? events.map(removeSdkPercentileFields)
|
|
1616
|
+
: events;
|
|
1452
1617
|
return {
|
|
1453
1618
|
sinkName: this.sinkName,
|
|
1454
1619
|
runId: this.getSession().runId,
|
|
1455
1620
|
sessionId: this.getSession().sessionId,
|
|
1456
|
-
events:
|
|
1621
|
+
events: projectedEvents.map((event) => ({
|
|
1457
1622
|
...event,
|
|
1458
1623
|
tags: { ...staticTags, ...event.tags }
|
|
1459
1624
|
})),
|
|
1460
|
-
metrics: createReportingSinkMetricPoints(this.sinkName,
|
|
1625
|
+
metrics: createReportingSinkMetricPoints(this.sinkName, projectedEvents, staticTags)
|
|
1461
1626
|
};
|
|
1462
1627
|
}
|
|
1463
1628
|
}
|
|
@@ -1491,22 +1656,43 @@ class ExpandedHttpJsonReportingSink extends ExpandedEventReportingSink {
|
|
|
1491
1656
|
body: JSON.stringify(this.buildPayload(events, this.staticTags))
|
|
1492
1657
|
}, this.timeoutMs, this.constructor.name);
|
|
1493
1658
|
}
|
|
1659
|
+
async saveIterationBatch(batch) {
|
|
1660
|
+
await this.persistCanonicalGzipPayload(batch);
|
|
1661
|
+
}
|
|
1662
|
+
async completeIterationObservationStream(completion) {
|
|
1663
|
+
await this.persistCanonicalGzipPayload(completion);
|
|
1664
|
+
}
|
|
1665
|
+
async persistCanonicalGzipPayload(payload) {
|
|
1666
|
+
const compressed = await gzipJsonAsync(payload);
|
|
1667
|
+
await postWithTimeout(this.fetchImpl, `${trimTrailingSlashes(this.baseUrl)}${normalizePath(this.endpointPath)}`, {
|
|
1668
|
+
method: "POST",
|
|
1669
|
+
headers: {
|
|
1670
|
+
"Content-Type": "application/json",
|
|
1671
|
+
"Content-Encoding": "gzip",
|
|
1672
|
+
...this.headers
|
|
1673
|
+
},
|
|
1674
|
+
body: Uint8Array.from(compressed).buffer
|
|
1675
|
+
}, this.timeoutMs, this.constructor.name);
|
|
1676
|
+
}
|
|
1494
1677
|
}
|
|
1495
1678
|
class PrometheusRemoteWriteReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1496
1679
|
constructor(options = {}) {
|
|
1497
1680
|
super("prometheus-remote-write", "extensions.reporting_sinks.prometheus_remote_write", "/api/v1/write", options);
|
|
1681
|
+
this.iterationObservationShapeLimited = true;
|
|
1498
1682
|
}
|
|
1499
1683
|
}
|
|
1500
1684
|
exports.PrometheusRemoteWriteReportingSink = PrometheusRemoteWriteReportingSink;
|
|
1501
1685
|
class CloudWatchReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1502
1686
|
constructor(options = {}) {
|
|
1503
1687
|
super("cloudwatch", "extensions.reporting_sinks.cloudwatch", "/loadstrike/events", options);
|
|
1688
|
+
this.iterationObservationShapeLimited = true;
|
|
1504
1689
|
}
|
|
1505
1690
|
}
|
|
1506
1691
|
exports.CloudWatchReportingSink = CloudWatchReportingSink;
|
|
1507
1692
|
class DynatraceReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1508
1693
|
constructor(options = {}) {
|
|
1509
1694
|
super("dynatrace", "extensions.reporting_sinks.dynatrace", "/api/v2/logs/ingest", options);
|
|
1695
|
+
this.iterationObservationShapeLimited = true;
|
|
1510
1696
|
}
|
|
1511
1697
|
}
|
|
1512
1698
|
exports.DynatraceReportingSink = DynatraceReportingSink;
|
|
@@ -1525,12 +1711,26 @@ exports.OpenSearchReportingSink = OpenSearchReportingSink;
|
|
|
1525
1711
|
class NewRelicReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1526
1712
|
constructor(options = {}) {
|
|
1527
1713
|
super("new-relic", "extensions.reporting_sinks.new_relic", "/log/v1", options);
|
|
1714
|
+
this.iterationObservationShapeLimited = true;
|
|
1528
1715
|
}
|
|
1529
1716
|
}
|
|
1530
1717
|
exports.NewRelicReportingSink = NewRelicReportingSink;
|
|
1531
1718
|
class GenericWebhookReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1532
1719
|
constructor(options = {}) {
|
|
1533
1720
|
super("webhook", "extensions.reporting_sinks.webhook", "/loadstrike", options);
|
|
1721
|
+
this.preservesRawIterationObservations = true;
|
|
1722
|
+
}
|
|
1723
|
+
async saveIterationBatch(batch) {
|
|
1724
|
+
await this.persistCanonicalGzipPayload(batch);
|
|
1725
|
+
}
|
|
1726
|
+
async SaveIterationBatch(batch) {
|
|
1727
|
+
await this.saveIterationBatch(batch);
|
|
1728
|
+
}
|
|
1729
|
+
async completeIterationObservationStream(completion) {
|
|
1730
|
+
await this.persistCanonicalGzipPayload(completion);
|
|
1731
|
+
}
|
|
1732
|
+
async CompleteIterationObservationStream(completion) {
|
|
1733
|
+
await this.completeIterationObservationStream(completion);
|
|
1534
1734
|
}
|
|
1535
1735
|
}
|
|
1536
1736
|
exports.GenericWebhookReportingSink = GenericWebhookReportingSink;
|
|
@@ -1542,6 +1742,12 @@ class KafkaReportingSink extends ExpandedEventReportingSink {
|
|
|
1542
1742
|
this.staticTags = normalizeStringMap(optionRecord(source, "staticTags", "StaticTags"));
|
|
1543
1743
|
this.publishAsync = pickRecordValue(source, "publishAsync", "PublishAsync");
|
|
1544
1744
|
}
|
|
1745
|
+
async saveIterationBatch(batch) {
|
|
1746
|
+
await this.publishCanonicalValue(batch);
|
|
1747
|
+
}
|
|
1748
|
+
async completeIterationObservationStream(completion) {
|
|
1749
|
+
await this.publishCanonicalValue(completion);
|
|
1750
|
+
}
|
|
1545
1751
|
async persistEvents(events) {
|
|
1546
1752
|
if (!events.length) {
|
|
1547
1753
|
return;
|
|
@@ -1551,11 +1757,18 @@ class KafkaReportingSink extends ExpandedEventReportingSink {
|
|
|
1551
1757
|
}
|
|
1552
1758
|
await this.publishAsync(this.topic, JSON.stringify(this.buildPayload(events, this.staticTags)));
|
|
1553
1759
|
}
|
|
1760
|
+
async publishCanonicalValue(value) {
|
|
1761
|
+
if (!this.publishAsync) {
|
|
1762
|
+
throw new Error("KafkaReportingSink requires PublishAsync when a Kafka producer is not configured by the host application.");
|
|
1763
|
+
}
|
|
1764
|
+
await this.publishAsync(this.topic, JSON.stringify(value));
|
|
1765
|
+
}
|
|
1554
1766
|
}
|
|
1555
1767
|
exports.KafkaReportingSink = KafkaReportingSink;
|
|
1556
1768
|
class StatsDReportingSinkBase extends ExpandedEventReportingSink {
|
|
1557
1769
|
constructor(sinkName, licenseFeature, options = {}, dogStatsD = false) {
|
|
1558
1770
|
super(sinkName, licenseFeature);
|
|
1771
|
+
this.iterationObservationShapeLimited = true;
|
|
1559
1772
|
const source = asRecord(options);
|
|
1560
1773
|
this.prefix = optionString(source, "prefix", "Prefix").trim() || "loadstrike";
|
|
1561
1774
|
this.host = optionString(source, "host", "Host").trim() || "127.0.0.1";
|
|
@@ -1564,6 +1777,47 @@ class StatsDReportingSinkBase extends ExpandedEventReportingSink {
|
|
|
1564
1777
|
this.sendLineAsync = pickRecordValue(source, "sendLineAsync", "SendLineAsync");
|
|
1565
1778
|
this.dogStatsD = dogStatsD;
|
|
1566
1779
|
}
|
|
1780
|
+
async saveIterationBatch(batch) {
|
|
1781
|
+
for (const observation of batch.observations) {
|
|
1782
|
+
const tags = {
|
|
1783
|
+
run_id: batch.runId,
|
|
1784
|
+
result_owner_id: batch.resultOwnerId,
|
|
1785
|
+
scenario_name: observation.scenarioName,
|
|
1786
|
+
phase: observation.phase,
|
|
1787
|
+
outcome: observation.isSuccess ? "success" : "failure"
|
|
1788
|
+
};
|
|
1789
|
+
const occurredUtc = new Date();
|
|
1790
|
+
await this.sendLine(this.formatPoint({
|
|
1791
|
+
metricName: "iteration.reported_latency_us",
|
|
1792
|
+
metricKind: "gauge",
|
|
1793
|
+
occurredUtc,
|
|
1794
|
+
value: Number(observation.reportedLatencyUs64),
|
|
1795
|
+
unitOfMeasure: "us",
|
|
1796
|
+
tags
|
|
1797
|
+
}));
|
|
1798
|
+
await this.sendLine(this.formatPoint({
|
|
1799
|
+
metricName: "iteration.attempt",
|
|
1800
|
+
metricKind: "count",
|
|
1801
|
+
occurredUtc,
|
|
1802
|
+
value: 1,
|
|
1803
|
+
unitOfMeasure: "count",
|
|
1804
|
+
tags
|
|
1805
|
+
}));
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
async completeIterationObservationStream(completion) {
|
|
1809
|
+
await this.sendLine(this.formatPoint({
|
|
1810
|
+
metricName: "observation.reporting_complete",
|
|
1811
|
+
metricKind: "gauge",
|
|
1812
|
+
occurredUtc: new Date(),
|
|
1813
|
+
value: completion.reportingComplete ? 1 : 0,
|
|
1814
|
+
unitOfMeasure: "boolean",
|
|
1815
|
+
tags: {
|
|
1816
|
+
run_id: completion.runId,
|
|
1817
|
+
result_owner_id: completion.resultOwnerId
|
|
1818
|
+
}
|
|
1819
|
+
}));
|
|
1820
|
+
}
|
|
1567
1821
|
async persistEvents(events) {
|
|
1568
1822
|
const points = createReportingSinkMetricPoints(this.sinkName, events, this.tags);
|
|
1569
1823
|
for (const point of points) {
|
|
@@ -1606,6 +1860,8 @@ exports.NetdataStatsDReportingSink = NetdataStatsDReportingSink;
|
|
|
1606
1860
|
class JsonlFileReportingSink extends ExpandedEventReportingSink {
|
|
1607
1861
|
constructor(options = {}) {
|
|
1608
1862
|
super("jsonl", "extensions.reporting_sinks.jsonl");
|
|
1863
|
+
this.preservesRawIterationObservations = true;
|
|
1864
|
+
this.writeTail = Promise.resolve();
|
|
1609
1865
|
const source = asRecord(options);
|
|
1610
1866
|
this.filePath = optionString(source, "filePath", "FilePath").trim() || "loadstrike-reporting.jsonl";
|
|
1611
1867
|
this.staticTags = normalizeStringMap(optionRecord(source, "staticTags", "StaticTags"));
|
|
@@ -1614,8 +1870,28 @@ class JsonlFileReportingSink extends ExpandedEventReportingSink {
|
|
|
1614
1870
|
if (!events.length) {
|
|
1615
1871
|
return;
|
|
1616
1872
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1873
|
+
await this.persistCanonicalPayload(this.buildPayload(events, this.staticTags));
|
|
1874
|
+
}
|
|
1875
|
+
async saveIterationBatch(batch) {
|
|
1876
|
+
await this.persistCanonicalPayload(batch);
|
|
1877
|
+
}
|
|
1878
|
+
async SaveIterationBatch(batch) {
|
|
1879
|
+
await this.saveIterationBatch(batch);
|
|
1880
|
+
}
|
|
1881
|
+
async completeIterationObservationStream(completion) {
|
|
1882
|
+
await this.persistCanonicalPayload(completion);
|
|
1883
|
+
}
|
|
1884
|
+
async CompleteIterationObservationStream(completion) {
|
|
1885
|
+
await this.completeIterationObservationStream(completion);
|
|
1886
|
+
}
|
|
1887
|
+
persistCanonicalPayload(payload) {
|
|
1888
|
+
const line = `${JSON.stringify(payload)}\n`;
|
|
1889
|
+
const write = this.writeTail.then(async () => {
|
|
1890
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(this.filePath), { recursive: true });
|
|
1891
|
+
await (0, promises_1.appendFile)(this.filePath, line, "utf8");
|
|
1892
|
+
});
|
|
1893
|
+
this.writeTail = write.catch(() => undefined);
|
|
1894
|
+
return write;
|
|
1619
1895
|
}
|
|
1620
1896
|
}
|
|
1621
1897
|
exports.JsonlFileReportingSink = JsonlFileReportingSink;
|
|
@@ -1663,9 +1939,18 @@ function createRunResultEvents(session, result) {
|
|
|
1663
1939
|
completed_utc: result.completedUtc,
|
|
1664
1940
|
report_file_count: result.reportFiles.length,
|
|
1665
1941
|
disabled_sink_count: result.disabledSinks.length,
|
|
1666
|
-
sink_error_count: result.sinkErrors.length
|
|
1942
|
+
sink_error_count: result.sinkErrors.length,
|
|
1943
|
+
reporting_complete: result.reportingComplete ?? true,
|
|
1944
|
+
observation_last_batch_sequence_64: result.observationDeliveryStats?.lastBatchSequence64 ?? "-1",
|
|
1945
|
+
observation_captured_count_64: result.observationDeliveryStats?.capturedCount64 ?? "0",
|
|
1946
|
+
observation_delivered_count_64: result.observationDeliveryStats?.deliveredCount64 ?? "0",
|
|
1947
|
+
observation_dropped_buffer_count_64: result.observationDeliveryStats?.droppedBufferCount64 ?? "0",
|
|
1948
|
+
observation_dropped_sink_count_64: result.observationDeliveryStats?.droppedSinkCount64 ?? "0"
|
|
1667
1949
|
})
|
|
1668
1950
|
];
|
|
1951
|
+
for (const row of result.correlationRows ?? []) {
|
|
1952
|
+
events.push(createCorrelationOutcomeEvent(session, occurredUtc, row));
|
|
1953
|
+
}
|
|
1669
1954
|
for (const reportFile of result.reportFiles) {
|
|
1670
1955
|
events.push(createReportingEvent(session, occurredUtc, "run.report.final", null, null, {
|
|
1671
1956
|
phase: "final",
|
|
@@ -1696,8 +1981,65 @@ function createRunResultEvents(session, result) {
|
|
|
1696
1981
|
attempts: sinkError.attempts
|
|
1697
1982
|
}));
|
|
1698
1983
|
}
|
|
1984
|
+
for (const warning of result.generatorWarnings ?? []) {
|
|
1985
|
+
events.push(createReportingEvent(session, occurredUtc, "run.generator-warning.final", warning.scenarioName || null, null, {
|
|
1986
|
+
phase: "final",
|
|
1987
|
+
entity: "generator-warning",
|
|
1988
|
+
warning_code: warning.code,
|
|
1989
|
+
...(warning.sinkName ? { sink_name: warning.sinkName } : {})
|
|
1990
|
+
}, {
|
|
1991
|
+
warning_code: warning.code,
|
|
1992
|
+
sink_name: warning.sinkName ?? "",
|
|
1993
|
+
scenario_name: warning.scenarioName,
|
|
1994
|
+
scenario_index: warning.scenarioIndex ?? -1,
|
|
1995
|
+
simulation_index: warning.simulationIndex,
|
|
1996
|
+
simulation_kind: warning.simulationKind ?? "",
|
|
1997
|
+
count_64: warning.count64,
|
|
1998
|
+
message: warning.message,
|
|
1999
|
+
first_observed_utc_ns: warning.firstObservedUtcNs,
|
|
2000
|
+
last_observed_utc_ns: warning.lastObservedUtcNs
|
|
2001
|
+
}));
|
|
2002
|
+
}
|
|
1699
2003
|
return events;
|
|
1700
2004
|
}
|
|
2005
|
+
function createCorrelationOutcomeEvent(session, fallbackOccurredUtc, row) {
|
|
2006
|
+
const occurredToken = optionString(row, "occurredUtc", "OccurredUtc");
|
|
2007
|
+
const parsedOccurredUtc = occurredToken ? new Date(occurredToken) : fallbackOccurredUtc;
|
|
2008
|
+
const occurredUtc = Number.isFinite(parsedOccurredUtc.getTime())
|
|
2009
|
+
? parsedOccurredUtc
|
|
2010
|
+
: fallbackOccurredUtc;
|
|
2011
|
+
const scenarioName = optionString(row, "scenario", "Scenario");
|
|
2012
|
+
const source = optionString(row, "source", "Source");
|
|
2013
|
+
const destination = optionString(row, "destination", "Destination");
|
|
2014
|
+
const runMode = optionString(row, "runMode", "RunMode");
|
|
2015
|
+
const statusCode = optionString(row, "statusCode", "StatusCode");
|
|
2016
|
+
const gatherByField = optionString(row, "gatherByField", "GatherByField");
|
|
2017
|
+
const gatherByValue = optionString(row, "gatherByValue", "GatherByValue");
|
|
2018
|
+
return createReportingEvent(session, occurredUtc, "correlation.outcome.final", scenarioName || null, null, {
|
|
2019
|
+
phase: "final",
|
|
2020
|
+
entity: "correlation-outcome",
|
|
2021
|
+
source,
|
|
2022
|
+
destination,
|
|
2023
|
+
run_mode: runMode,
|
|
2024
|
+
status_code: statusCode,
|
|
2025
|
+
gather_by_field: gatherByField,
|
|
2026
|
+
gather_by_value: gatherByValue
|
|
2027
|
+
}, {
|
|
2028
|
+
occurred_utc: occurredToken || occurredUtc.toISOString(),
|
|
2029
|
+
source,
|
|
2030
|
+
destination,
|
|
2031
|
+
run_mode: runMode,
|
|
2032
|
+
status_code: statusCode,
|
|
2033
|
+
is_success: pickBooleanValue(row, false, "isSuccess", "IsSuccess"),
|
|
2034
|
+
is_failure: pickBooleanValue(row, false, "isFailure", "IsFailure"),
|
|
2035
|
+
gather_by_field: gatherByField,
|
|
2036
|
+
gather_by_value: gatherByValue,
|
|
2037
|
+
tracking_id: optionString(row, "trackingId", "TrackingId"),
|
|
2038
|
+
event_id: optionString(row, "eventId", "EventId"),
|
|
2039
|
+
latency_ms: optionNumber(row, "latencyMs", "LatencyMs") ?? 0,
|
|
2040
|
+
message: optionString(row, "message", "Message")
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
1701
2043
|
function createNodeSummaryEvent(session, occurredUtc, stats) {
|
|
1702
2044
|
return createReportingEvent(session, occurredUtc, "test.final", null, null, {
|
|
1703
2045
|
phase: "final",
|
|
@@ -1883,6 +2225,123 @@ function createReportingEvent(session, occurredUtc, eventType, scenarioName, ste
|
|
|
1883
2225
|
fields: eventFields
|
|
1884
2226
|
};
|
|
1885
2227
|
}
|
|
2228
|
+
function removeSdkPercentileFields(event) {
|
|
2229
|
+
const fields = Object.fromEntries(Object.entries(event.fields).filter(([name]) => !/_(?:latency|bytes)_p(?:50|75|95|99)(?:_|$)/iu.test(name)));
|
|
2230
|
+
return {
|
|
2231
|
+
...event,
|
|
2232
|
+
fields
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
function createIterationObservationEvents(session, batch) {
|
|
2236
|
+
const events = [];
|
|
2237
|
+
for (const observation of batch.observations) {
|
|
2238
|
+
const tags = {
|
|
2239
|
+
run_id: batch.runId,
|
|
2240
|
+
result_owner_id: batch.resultOwnerId,
|
|
2241
|
+
phase: observation.phase,
|
|
2242
|
+
scenario_name: observation.scenarioName,
|
|
2243
|
+
simulation_kind: observation.simulationKind,
|
|
2244
|
+
is_final_attempt: observation.isFinalAttempt ? "true" : "false"
|
|
2245
|
+
};
|
|
2246
|
+
events.push({
|
|
2247
|
+
runId: batch.runId,
|
|
2248
|
+
eventType: "iteration.observation",
|
|
2249
|
+
occurredUtc: new Date(),
|
|
2250
|
+
sessionId: batch.sessionId,
|
|
2251
|
+
testSuite: session.testSuite,
|
|
2252
|
+
testName: session.testName,
|
|
2253
|
+
clusterId: session.clusterId,
|
|
2254
|
+
nodeType: session.nodeType,
|
|
2255
|
+
machineName: session.machineName,
|
|
2256
|
+
scenarioName: observation.scenarioName,
|
|
2257
|
+
stepName: null,
|
|
2258
|
+
tags,
|
|
2259
|
+
fields: {
|
|
2260
|
+
schema_version: observation.schemaVersion,
|
|
2261
|
+
batch_id: batch.batchId,
|
|
2262
|
+
observation_id: observation.observationId,
|
|
2263
|
+
iteration_id: observation.iterationId,
|
|
2264
|
+
process_group: observation.processGroup,
|
|
2265
|
+
scenario_index: observation.scenarioIndex,
|
|
2266
|
+
simulation_index: observation.simulationIndex,
|
|
2267
|
+
global_ordinal_64: observation.globalOrdinal64,
|
|
2268
|
+
shard_index: observation.shardIndex,
|
|
2269
|
+
shard_count: observation.shardCount,
|
|
2270
|
+
attempt_index: observation.attemptIndex,
|
|
2271
|
+
started_utc_ns: observation.startedUtcNs,
|
|
2272
|
+
completed_utc_ns: observation.completedUtcNs,
|
|
2273
|
+
observed_latency_us_64: observation.observedLatencyUs64,
|
|
2274
|
+
reported_latency_us_64: observation.reportedLatencyUs64,
|
|
2275
|
+
is_success: observation.isSuccess,
|
|
2276
|
+
status_code: observation.statusCode,
|
|
2277
|
+
size_bytes_64: observation.sizeBytes64
|
|
2278
|
+
}
|
|
2279
|
+
});
|
|
2280
|
+
for (const step of observation.steps) {
|
|
2281
|
+
events.push({
|
|
2282
|
+
runId: batch.runId,
|
|
2283
|
+
eventType: "iteration.step",
|
|
2284
|
+
occurredUtc: new Date(),
|
|
2285
|
+
sessionId: batch.sessionId,
|
|
2286
|
+
testSuite: session.testSuite,
|
|
2287
|
+
testName: session.testName,
|
|
2288
|
+
clusterId: session.clusterId,
|
|
2289
|
+
nodeType: session.nodeType,
|
|
2290
|
+
machineName: session.machineName,
|
|
2291
|
+
scenarioName: observation.scenarioName,
|
|
2292
|
+
stepName: step.stepName,
|
|
2293
|
+
tags: { ...tags },
|
|
2294
|
+
fields: {
|
|
2295
|
+
schema_version: observation.schemaVersion,
|
|
2296
|
+
batch_id: batch.batchId,
|
|
2297
|
+
observation_id: observation.observationId,
|
|
2298
|
+
iteration_id: observation.iterationId,
|
|
2299
|
+
attempt_index: observation.attemptIndex,
|
|
2300
|
+
sort_index: step.sortIndex,
|
|
2301
|
+
started_utc_ns: step.startedUtcNs,
|
|
2302
|
+
completed_utc_ns: step.completedUtcNs,
|
|
2303
|
+
observed_latency_us_64: step.observedLatencyUs64,
|
|
2304
|
+
reported_latency_us_64: step.reportedLatencyUs64,
|
|
2305
|
+
is_success: step.isSuccess,
|
|
2306
|
+
status_code: step.statusCode,
|
|
2307
|
+
size_bytes_64: step.sizeBytes64
|
|
2308
|
+
}
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
return events;
|
|
2313
|
+
}
|
|
2314
|
+
function createIterationCompletionEvents(session, completion) {
|
|
2315
|
+
return [{
|
|
2316
|
+
runId: completion.runId,
|
|
2317
|
+
eventType: "observation.stream.completed",
|
|
2318
|
+
occurredUtc: new Date(),
|
|
2319
|
+
sessionId: completion.sessionId,
|
|
2320
|
+
testSuite: session.testSuite,
|
|
2321
|
+
testName: session.testName,
|
|
2322
|
+
clusterId: session.clusterId,
|
|
2323
|
+
nodeType: session.nodeType,
|
|
2324
|
+
machineName: session.machineName,
|
|
2325
|
+
scenarioName: null,
|
|
2326
|
+
stepName: null,
|
|
2327
|
+
tags: {
|
|
2328
|
+
run_id: completion.runId,
|
|
2329
|
+
result_owner_id: completion.resultOwnerId,
|
|
2330
|
+
phase: "final"
|
|
2331
|
+
},
|
|
2332
|
+
fields: {
|
|
2333
|
+
schema_version: completion.schemaVersion,
|
|
2334
|
+
process_group: completion.processGroup,
|
|
2335
|
+
last_batch_sequence_64: completion.lastBatchSequence64,
|
|
2336
|
+
captured_count_64: completion.capturedCount64,
|
|
2337
|
+
delivered_count_64: completion.deliveredCount64,
|
|
2338
|
+
dropped_buffer_count_64: completion.droppedBufferCount64,
|
|
2339
|
+
dropped_sink_count_64: completion.droppedSinkCount64,
|
|
2340
|
+
reporting_complete: completion.reportingComplete,
|
|
2341
|
+
completed_utc_ns: completion.completedUtcNs
|
|
2342
|
+
}
|
|
2343
|
+
}];
|
|
2344
|
+
}
|
|
1886
2345
|
function portalEventPayload(event, index) {
|
|
1887
2346
|
return {
|
|
1888
2347
|
eventId: portalEventId(event, index),
|
|
@@ -1955,10 +2414,6 @@ function addMeasurementFields(fields, prefix, measurement) {
|
|
|
1955
2414
|
fields[`${prefix}_latency_min_ms`] = latency.minMs ?? 0;
|
|
1956
2415
|
fields[`${prefix}_latency_mean_ms`] = latency.meanMs ?? 0;
|
|
1957
2416
|
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
2417
|
fields[`${prefix}_latency_std_dev`] = latency.stdDev ?? 0;
|
|
1963
2418
|
fields[`${prefix}_latency_le_800_count`] = latencyCount.lessOrEq800 ?? 0;
|
|
1964
2419
|
fields[`${prefix}_latency_gt_800_lt_1200_count`] = latencyCount.more800Less1200 ?? 0;
|
|
@@ -1967,10 +2422,6 @@ function addMeasurementFields(fields, prefix, measurement) {
|
|
|
1967
2422
|
fields[`${prefix}_bytes_min`] = dataTransfer.minBytes ?? 0;
|
|
1968
2423
|
fields[`${prefix}_bytes_mean`] = dataTransfer.meanBytes ?? 0;
|
|
1969
2424
|
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
2425
|
fields[`${prefix}_bytes_std_dev`] = dataTransfer.stdDev ?? 0;
|
|
1975
2426
|
fields[`${prefix}_status_code_count`] = statusCodes.length;
|
|
1976
2427
|
}
|
|
@@ -3106,6 +3557,9 @@ function deepCloneValue(value) {
|
|
|
3106
3557
|
}
|
|
3107
3558
|
return value;
|
|
3108
3559
|
}
|
|
3560
|
+
function gzipJsonRequestBody(value) {
|
|
3561
|
+
return Uint8Array.from((0, iteration_observations_js_1.serializeIterationObservationBatchGzipJson)(value)).buffer;
|
|
3562
|
+
}
|
|
3109
3563
|
async function postWithTimeout(fetchImpl, url, init, timeoutMs, sinkName) {
|
|
3110
3564
|
const controller = new AbortController();
|
|
3111
3565
|
const timer = setTimeout(() => controller.abort(), Math.max(timeoutMs, 1));
|