@loadstrike/loadstrike-sdk 1.0.30001 → 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 +123 -13
- 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 +123 -13
- 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/esm/sinks.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import { LoadStrikePluginData as LoadStrikePluginDataModel, LoadStrikePluginDataTable as LoadStrikePluginDataTableModel } from "./runtime.js";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
-
import {
|
|
3
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
5
|
import { createSocket } from "node:dgram";
|
|
6
|
+
import { gzip } from "node:zlib";
|
|
6
7
|
import { Pool } from "pg";
|
|
8
|
+
import { serializeIterationObservationBatchGzipJson } from "./iteration-observations.js";
|
|
9
|
+
const PORTAL_RUN_TOKEN_PROVIDER = Symbol.for("loadstrike.internal.portal-run-token-provider");
|
|
10
|
+
function gzipJsonAsync(value) {
|
|
11
|
+
const json = Buffer.from(JSON.stringify(value), "utf8");
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
gzip(json, (error, compressed) => {
|
|
14
|
+
if (error) {
|
|
15
|
+
reject(error);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
resolve(compressed);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
7
22
|
const DEFAULT_INFLUX_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:InfluxDb";
|
|
8
23
|
const DEFAULT_GRAFANA_LOKI_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:GrafanaLoki";
|
|
9
24
|
const DEFAULT_TIMESCALEDB_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:TimescaleDb";
|
|
@@ -364,6 +379,28 @@ export class CompositeReportingSink {
|
|
|
364
379
|
async SaveRunResult(result) {
|
|
365
380
|
await this.saveRunResult(result);
|
|
366
381
|
}
|
|
382
|
+
async saveIterationBatch(batch) {
|
|
383
|
+
for (const sink of this.sinks) {
|
|
384
|
+
const saveIterationBatch = sink.saveIterationBatch ?? sink.SaveIterationBatch;
|
|
385
|
+
if (saveIterationBatch) {
|
|
386
|
+
await saveIterationBatch.call(sink, batch);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
async SaveIterationBatch(batch) {
|
|
391
|
+
await this.saveIterationBatch(batch);
|
|
392
|
+
}
|
|
393
|
+
async completeIterationObservationStream(completion) {
|
|
394
|
+
for (const sink of this.sinks) {
|
|
395
|
+
const completeIterationObservationStream = sink.completeIterationObservationStream ?? sink.CompleteIterationObservationStream;
|
|
396
|
+
if (completeIterationObservationStream) {
|
|
397
|
+
await completeIterationObservationStream.call(sink, completion);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async CompleteIterationObservationStream(completion) {
|
|
402
|
+
await this.completeIterationObservationStream(completion);
|
|
403
|
+
}
|
|
367
404
|
async stop() {
|
|
368
405
|
for (const sink of this.sinks) {
|
|
369
406
|
const stop = sink.stop ?? sink.Stop;
|
|
@@ -378,6 +415,7 @@ export class CompositeReportingSink {
|
|
|
378
415
|
}
|
|
379
416
|
export class PortalReportingSink {
|
|
380
417
|
constructor(options = {}) {
|
|
418
|
+
this.iterationObservationPortalSink = true;
|
|
381
419
|
this.sinkName = "portal";
|
|
382
420
|
this.SinkName = "portal";
|
|
383
421
|
this.licenseFeature = "extensions.reporting_sinks.portal";
|
|
@@ -385,6 +423,7 @@ export class PortalReportingSink {
|
|
|
385
423
|
this.baseContext = null;
|
|
386
424
|
this.session = null;
|
|
387
425
|
this.runToken = "";
|
|
426
|
+
this.runTokenProvider = null;
|
|
388
427
|
this.ingestUrl = "";
|
|
389
428
|
this.optionsInput = { ...options };
|
|
390
429
|
const source = asRecord(options);
|
|
@@ -401,7 +440,11 @@ export class PortalReportingSink {
|
|
|
401
440
|
this.init(context, infraConfig);
|
|
402
441
|
}
|
|
403
442
|
start(session) {
|
|
404
|
-
|
|
443
|
+
const internalProvider = session[PORTAL_RUN_TOKEN_PROVIDER];
|
|
444
|
+
this.runTokenProvider = typeof internalProvider === "function"
|
|
445
|
+
? internalProvider
|
|
446
|
+
: () => String(session.runToken ?? session.RunToken ?? "").trim();
|
|
447
|
+
this.runToken = this.runTokenProvider();
|
|
405
448
|
this.ingestUrl = String(session.portalReportingIngestUrl ?? session.PortalReportingIngestUrl ?? "").trim();
|
|
406
449
|
if (!this.runToken || !this.ingestUrl) {
|
|
407
450
|
throw new Error("PortalReportingSink requires a managed portal reporting session.");
|
|
@@ -431,8 +474,27 @@ export class PortalReportingSink {
|
|
|
431
474
|
async SaveRunResult(result) {
|
|
432
475
|
await this.saveRunResult(result);
|
|
433
476
|
}
|
|
477
|
+
async saveIterationBatch(batch) {
|
|
478
|
+
const compressed = await gzipJsonAsync(batch);
|
|
479
|
+
await this.persistObservationPayload({
|
|
480
|
+
compressedObservationBatches: [{
|
|
481
|
+
compression: "gzip-json",
|
|
482
|
+
payloadBase64: compressed.toString("base64")
|
|
483
|
+
}]
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
async SaveIterationBatch(batch) {
|
|
487
|
+
await this.saveIterationBatch(batch);
|
|
488
|
+
}
|
|
489
|
+
async completeIterationObservationStream(completion) {
|
|
490
|
+
await this.persistObservationPayload({ observationStreamCompletions: [completion] });
|
|
491
|
+
}
|
|
492
|
+
async CompleteIterationObservationStream(completion) {
|
|
493
|
+
await this.completeIterationObservationStream(completion);
|
|
494
|
+
}
|
|
434
495
|
stop() {
|
|
435
496
|
this.session = null;
|
|
497
|
+
this.runTokenProvider = null;
|
|
436
498
|
}
|
|
437
499
|
Stop() {
|
|
438
500
|
this.stop();
|
|
@@ -461,12 +523,27 @@ export class PortalReportingSink {
|
|
|
461
523
|
method: "POST",
|
|
462
524
|
headers: { "Content-Type": "application/json" },
|
|
463
525
|
body: JSON.stringify({
|
|
464
|
-
runToken: this.
|
|
465
|
-
events: events.map((event, index) => portalEventPayload(event, index))
|
|
526
|
+
runToken: this.currentRunToken(),
|
|
527
|
+
events: events.map((event, index) => portalEventPayload(removeSdkPercentileFields(event), index))
|
|
528
|
+
})
|
|
529
|
+
}, this.timeoutMs, "PortalReportingSink");
|
|
530
|
+
validatePortalIngestResponse(responseBody);
|
|
531
|
+
}
|
|
532
|
+
async persistObservationPayload(payload) {
|
|
533
|
+
const responseBody = await postWithTimeout(this.fetchImpl, this.ingestUrl, {
|
|
534
|
+
method: "POST",
|
|
535
|
+
headers: { "Content-Type": "application/json" },
|
|
536
|
+
body: JSON.stringify({
|
|
537
|
+
runToken: this.currentRunToken(),
|
|
538
|
+
...payload
|
|
466
539
|
})
|
|
467
540
|
}, this.timeoutMs, "PortalReportingSink");
|
|
468
541
|
validatePortalIngestResponse(responseBody);
|
|
469
542
|
}
|
|
543
|
+
currentRunToken() {
|
|
544
|
+
const current = this.runTokenProvider?.() ?? "";
|
|
545
|
+
return current || this.runToken;
|
|
546
|
+
}
|
|
470
547
|
}
|
|
471
548
|
export function cloneReportingSinkForRun(sink) {
|
|
472
549
|
if (sink instanceof PortalReportingSink) {
|
|
@@ -542,6 +619,18 @@ export class InfluxDbReportingSink {
|
|
|
542
619
|
async SaveRunResult(result) {
|
|
543
620
|
await this.saveRunResult(result);
|
|
544
621
|
}
|
|
622
|
+
async saveIterationBatch(batch) {
|
|
623
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
624
|
+
}
|
|
625
|
+
async SaveIterationBatch(batch) {
|
|
626
|
+
await this.saveIterationBatch(batch);
|
|
627
|
+
}
|
|
628
|
+
async completeIterationObservationStream(completion) {
|
|
629
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
630
|
+
}
|
|
631
|
+
async CompleteIterationObservationStream(completion) {
|
|
632
|
+
await this.completeIterationObservationStream(completion);
|
|
633
|
+
}
|
|
545
634
|
stop() {
|
|
546
635
|
this.session = null;
|
|
547
636
|
}
|
|
@@ -651,6 +740,18 @@ export class GrafanaLokiReportingSink {
|
|
|
651
740
|
async SaveRunResult(result) {
|
|
652
741
|
await this.saveRunResult(result);
|
|
653
742
|
}
|
|
743
|
+
async saveIterationBatch(batch) {
|
|
744
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
745
|
+
}
|
|
746
|
+
async SaveIterationBatch(batch) {
|
|
747
|
+
await this.saveIterationBatch(batch);
|
|
748
|
+
}
|
|
749
|
+
async completeIterationObservationStream(completion) {
|
|
750
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
751
|
+
}
|
|
752
|
+
async CompleteIterationObservationStream(completion) {
|
|
753
|
+
await this.completeIterationObservationStream(completion);
|
|
754
|
+
}
|
|
654
755
|
stop() {
|
|
655
756
|
this.session = null;
|
|
656
757
|
}
|
|
@@ -796,6 +897,18 @@ export class TimescaleDbReportingSink {
|
|
|
796
897
|
async SaveRunResult(result) {
|
|
797
898
|
await this.saveRunResult(result);
|
|
798
899
|
}
|
|
900
|
+
async saveIterationBatch(batch) {
|
|
901
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
902
|
+
}
|
|
903
|
+
async SaveIterationBatch(batch) {
|
|
904
|
+
await this.saveIterationBatch(batch);
|
|
905
|
+
}
|
|
906
|
+
async completeIterationObservationStream(completion) {
|
|
907
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
908
|
+
}
|
|
909
|
+
async CompleteIterationObservationStream(completion) {
|
|
910
|
+
await this.completeIterationObservationStream(completion);
|
|
911
|
+
}
|
|
799
912
|
async stop() {
|
|
800
913
|
this.session = null;
|
|
801
914
|
if (this.pool) {
|
|
@@ -1071,6 +1184,18 @@ export class DatadogReportingSink {
|
|
|
1071
1184
|
async SaveRunResult(result) {
|
|
1072
1185
|
await this.saveRunResult(result);
|
|
1073
1186
|
}
|
|
1187
|
+
async saveIterationBatch(batch) {
|
|
1188
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
1189
|
+
}
|
|
1190
|
+
async SaveIterationBatch(batch) {
|
|
1191
|
+
await this.saveIterationBatch(batch);
|
|
1192
|
+
}
|
|
1193
|
+
async completeIterationObservationStream(completion) {
|
|
1194
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
1195
|
+
}
|
|
1196
|
+
async CompleteIterationObservationStream(completion) {
|
|
1197
|
+
await this.completeIterationObservationStream(completion);
|
|
1198
|
+
}
|
|
1074
1199
|
stop() {
|
|
1075
1200
|
this.session = null;
|
|
1076
1201
|
}
|
|
@@ -1193,6 +1318,18 @@ export class SplunkReportingSink {
|
|
|
1193
1318
|
async SaveRunResult(result) {
|
|
1194
1319
|
await this.saveRunResult(result);
|
|
1195
1320
|
}
|
|
1321
|
+
async saveIterationBatch(batch) {
|
|
1322
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
1323
|
+
}
|
|
1324
|
+
async SaveIterationBatch(batch) {
|
|
1325
|
+
await this.saveIterationBatch(batch);
|
|
1326
|
+
}
|
|
1327
|
+
async completeIterationObservationStream(completion) {
|
|
1328
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
1329
|
+
}
|
|
1330
|
+
async CompleteIterationObservationStream(completion) {
|
|
1331
|
+
await this.completeIterationObservationStream(completion);
|
|
1332
|
+
}
|
|
1196
1333
|
stop() {
|
|
1197
1334
|
this.session = null;
|
|
1198
1335
|
}
|
|
@@ -1295,6 +1432,18 @@ export class OtelCollectorReportingSink {
|
|
|
1295
1432
|
async SaveRunResult(result) {
|
|
1296
1433
|
await this.saveRunResult(result);
|
|
1297
1434
|
}
|
|
1435
|
+
async saveIterationBatch(batch) {
|
|
1436
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
1437
|
+
}
|
|
1438
|
+
async SaveIterationBatch(batch) {
|
|
1439
|
+
await this.saveIterationBatch(batch);
|
|
1440
|
+
}
|
|
1441
|
+
async completeIterationObservationStream(completion) {
|
|
1442
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
1443
|
+
}
|
|
1444
|
+
async CompleteIterationObservationStream(completion) {
|
|
1445
|
+
await this.completeIterationObservationStream(completion);
|
|
1446
|
+
}
|
|
1298
1447
|
stop() {
|
|
1299
1448
|
this.session = null;
|
|
1300
1449
|
}
|
|
@@ -1371,6 +1520,7 @@ class ExpandedEventReportingSink {
|
|
|
1371
1520
|
constructor(sinkName, licenseFeature) {
|
|
1372
1521
|
this.baseContext = null;
|
|
1373
1522
|
this.session = null;
|
|
1523
|
+
this.preservesRawIterationObservations = true;
|
|
1374
1524
|
this.sinkName = sinkName;
|
|
1375
1525
|
this.SinkName = sinkName;
|
|
1376
1526
|
this.licenseFeature = licenseFeature;
|
|
@@ -1406,6 +1556,18 @@ class ExpandedEventReportingSink {
|
|
|
1406
1556
|
async SaveRunResult(result) {
|
|
1407
1557
|
await this.saveRunResult(result);
|
|
1408
1558
|
}
|
|
1559
|
+
async saveIterationBatch(batch) {
|
|
1560
|
+
await this.persistEvents(createIterationObservationEvents(this.getSession(), batch));
|
|
1561
|
+
}
|
|
1562
|
+
async SaveIterationBatch(batch) {
|
|
1563
|
+
await this.saveIterationBatch(batch);
|
|
1564
|
+
}
|
|
1565
|
+
async completeIterationObservationStream(completion) {
|
|
1566
|
+
await this.persistEvents(createIterationCompletionEvents(this.getSession(), completion));
|
|
1567
|
+
}
|
|
1568
|
+
async CompleteIterationObservationStream(completion) {
|
|
1569
|
+
await this.completeIterationObservationStream(completion);
|
|
1570
|
+
}
|
|
1409
1571
|
stop() {
|
|
1410
1572
|
this.session = null;
|
|
1411
1573
|
}
|
|
@@ -1429,15 +1591,18 @@ class ExpandedEventReportingSink {
|
|
|
1429
1591
|
return this.session;
|
|
1430
1592
|
}
|
|
1431
1593
|
buildPayload(events, staticTags = {}) {
|
|
1594
|
+
const projectedEvents = this.preservesRawIterationObservations
|
|
1595
|
+
? events.map(removeSdkPercentileFields)
|
|
1596
|
+
: events;
|
|
1432
1597
|
return {
|
|
1433
1598
|
sinkName: this.sinkName,
|
|
1434
1599
|
runId: this.getSession().runId,
|
|
1435
1600
|
sessionId: this.getSession().sessionId,
|
|
1436
|
-
events:
|
|
1601
|
+
events: projectedEvents.map((event) => ({
|
|
1437
1602
|
...event,
|
|
1438
1603
|
tags: { ...staticTags, ...event.tags }
|
|
1439
1604
|
})),
|
|
1440
|
-
metrics: createReportingSinkMetricPoints(this.sinkName,
|
|
1605
|
+
metrics: createReportingSinkMetricPoints(this.sinkName, projectedEvents, staticTags)
|
|
1441
1606
|
};
|
|
1442
1607
|
}
|
|
1443
1608
|
}
|
|
@@ -1471,20 +1636,41 @@ class ExpandedHttpJsonReportingSink extends ExpandedEventReportingSink {
|
|
|
1471
1636
|
body: JSON.stringify(this.buildPayload(events, this.staticTags))
|
|
1472
1637
|
}, this.timeoutMs, this.constructor.name);
|
|
1473
1638
|
}
|
|
1639
|
+
async saveIterationBatch(batch) {
|
|
1640
|
+
await this.persistCanonicalGzipPayload(batch);
|
|
1641
|
+
}
|
|
1642
|
+
async completeIterationObservationStream(completion) {
|
|
1643
|
+
await this.persistCanonicalGzipPayload(completion);
|
|
1644
|
+
}
|
|
1645
|
+
async persistCanonicalGzipPayload(payload) {
|
|
1646
|
+
const compressed = await gzipJsonAsync(payload);
|
|
1647
|
+
await postWithTimeout(this.fetchImpl, `${trimTrailingSlashes(this.baseUrl)}${normalizePath(this.endpointPath)}`, {
|
|
1648
|
+
method: "POST",
|
|
1649
|
+
headers: {
|
|
1650
|
+
"Content-Type": "application/json",
|
|
1651
|
+
"Content-Encoding": "gzip",
|
|
1652
|
+
...this.headers
|
|
1653
|
+
},
|
|
1654
|
+
body: Uint8Array.from(compressed).buffer
|
|
1655
|
+
}, this.timeoutMs, this.constructor.name);
|
|
1656
|
+
}
|
|
1474
1657
|
}
|
|
1475
1658
|
export class PrometheusRemoteWriteReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1476
1659
|
constructor(options = {}) {
|
|
1477
1660
|
super("prometheus-remote-write", "extensions.reporting_sinks.prometheus_remote_write", "/api/v1/write", options);
|
|
1661
|
+
this.iterationObservationShapeLimited = true;
|
|
1478
1662
|
}
|
|
1479
1663
|
}
|
|
1480
1664
|
export class CloudWatchReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1481
1665
|
constructor(options = {}) {
|
|
1482
1666
|
super("cloudwatch", "extensions.reporting_sinks.cloudwatch", "/loadstrike/events", options);
|
|
1667
|
+
this.iterationObservationShapeLimited = true;
|
|
1483
1668
|
}
|
|
1484
1669
|
}
|
|
1485
1670
|
export class DynatraceReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1486
1671
|
constructor(options = {}) {
|
|
1487
1672
|
super("dynatrace", "extensions.reporting_sinks.dynatrace", "/api/v2/logs/ingest", options);
|
|
1673
|
+
this.iterationObservationShapeLimited = true;
|
|
1488
1674
|
}
|
|
1489
1675
|
}
|
|
1490
1676
|
export class ElasticsearchReportingSink extends ExpandedHttpJsonReportingSink {
|
|
@@ -1500,11 +1686,25 @@ export class OpenSearchReportingSink extends ExpandedHttpJsonReportingSink {
|
|
|
1500
1686
|
export class NewRelicReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1501
1687
|
constructor(options = {}) {
|
|
1502
1688
|
super("new-relic", "extensions.reporting_sinks.new_relic", "/log/v1", options);
|
|
1689
|
+
this.iterationObservationShapeLimited = true;
|
|
1503
1690
|
}
|
|
1504
1691
|
}
|
|
1505
1692
|
export class GenericWebhookReportingSink extends ExpandedHttpJsonReportingSink {
|
|
1506
1693
|
constructor(options = {}) {
|
|
1507
1694
|
super("webhook", "extensions.reporting_sinks.webhook", "/loadstrike", options);
|
|
1695
|
+
this.preservesRawIterationObservations = true;
|
|
1696
|
+
}
|
|
1697
|
+
async saveIterationBatch(batch) {
|
|
1698
|
+
await this.persistCanonicalGzipPayload(batch);
|
|
1699
|
+
}
|
|
1700
|
+
async SaveIterationBatch(batch) {
|
|
1701
|
+
await this.saveIterationBatch(batch);
|
|
1702
|
+
}
|
|
1703
|
+
async completeIterationObservationStream(completion) {
|
|
1704
|
+
await this.persistCanonicalGzipPayload(completion);
|
|
1705
|
+
}
|
|
1706
|
+
async CompleteIterationObservationStream(completion) {
|
|
1707
|
+
await this.completeIterationObservationStream(completion);
|
|
1508
1708
|
}
|
|
1509
1709
|
}
|
|
1510
1710
|
export class KafkaReportingSink extends ExpandedEventReportingSink {
|
|
@@ -1515,6 +1715,12 @@ export class KafkaReportingSink extends ExpandedEventReportingSink {
|
|
|
1515
1715
|
this.staticTags = normalizeStringMap(optionRecord(source, "staticTags", "StaticTags"));
|
|
1516
1716
|
this.publishAsync = pickRecordValue(source, "publishAsync", "PublishAsync");
|
|
1517
1717
|
}
|
|
1718
|
+
async saveIterationBatch(batch) {
|
|
1719
|
+
await this.publishCanonicalValue(batch);
|
|
1720
|
+
}
|
|
1721
|
+
async completeIterationObservationStream(completion) {
|
|
1722
|
+
await this.publishCanonicalValue(completion);
|
|
1723
|
+
}
|
|
1518
1724
|
async persistEvents(events) {
|
|
1519
1725
|
if (!events.length) {
|
|
1520
1726
|
return;
|
|
@@ -1524,10 +1730,17 @@ export class KafkaReportingSink extends ExpandedEventReportingSink {
|
|
|
1524
1730
|
}
|
|
1525
1731
|
await this.publishAsync(this.topic, JSON.stringify(this.buildPayload(events, this.staticTags)));
|
|
1526
1732
|
}
|
|
1733
|
+
async publishCanonicalValue(value) {
|
|
1734
|
+
if (!this.publishAsync) {
|
|
1735
|
+
throw new Error("KafkaReportingSink requires PublishAsync when a Kafka producer is not configured by the host application.");
|
|
1736
|
+
}
|
|
1737
|
+
await this.publishAsync(this.topic, JSON.stringify(value));
|
|
1738
|
+
}
|
|
1527
1739
|
}
|
|
1528
1740
|
class StatsDReportingSinkBase extends ExpandedEventReportingSink {
|
|
1529
1741
|
constructor(sinkName, licenseFeature, options = {}, dogStatsD = false) {
|
|
1530
1742
|
super(sinkName, licenseFeature);
|
|
1743
|
+
this.iterationObservationShapeLimited = true;
|
|
1531
1744
|
const source = asRecord(options);
|
|
1532
1745
|
this.prefix = optionString(source, "prefix", "Prefix").trim() || "loadstrike";
|
|
1533
1746
|
this.host = optionString(source, "host", "Host").trim() || "127.0.0.1";
|
|
@@ -1536,6 +1749,47 @@ class StatsDReportingSinkBase extends ExpandedEventReportingSink {
|
|
|
1536
1749
|
this.sendLineAsync = pickRecordValue(source, "sendLineAsync", "SendLineAsync");
|
|
1537
1750
|
this.dogStatsD = dogStatsD;
|
|
1538
1751
|
}
|
|
1752
|
+
async saveIterationBatch(batch) {
|
|
1753
|
+
for (const observation of batch.observations) {
|
|
1754
|
+
const tags = {
|
|
1755
|
+
run_id: batch.runId,
|
|
1756
|
+
result_owner_id: batch.resultOwnerId,
|
|
1757
|
+
scenario_name: observation.scenarioName,
|
|
1758
|
+
phase: observation.phase,
|
|
1759
|
+
outcome: observation.isSuccess ? "success" : "failure"
|
|
1760
|
+
};
|
|
1761
|
+
const occurredUtc = new Date();
|
|
1762
|
+
await this.sendLine(this.formatPoint({
|
|
1763
|
+
metricName: "iteration.reported_latency_us",
|
|
1764
|
+
metricKind: "gauge",
|
|
1765
|
+
occurredUtc,
|
|
1766
|
+
value: Number(observation.reportedLatencyUs64),
|
|
1767
|
+
unitOfMeasure: "us",
|
|
1768
|
+
tags
|
|
1769
|
+
}));
|
|
1770
|
+
await this.sendLine(this.formatPoint({
|
|
1771
|
+
metricName: "iteration.attempt",
|
|
1772
|
+
metricKind: "count",
|
|
1773
|
+
occurredUtc,
|
|
1774
|
+
value: 1,
|
|
1775
|
+
unitOfMeasure: "count",
|
|
1776
|
+
tags
|
|
1777
|
+
}));
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
async completeIterationObservationStream(completion) {
|
|
1781
|
+
await this.sendLine(this.formatPoint({
|
|
1782
|
+
metricName: "observation.reporting_complete",
|
|
1783
|
+
metricKind: "gauge",
|
|
1784
|
+
occurredUtc: new Date(),
|
|
1785
|
+
value: completion.reportingComplete ? 1 : 0,
|
|
1786
|
+
unitOfMeasure: "boolean",
|
|
1787
|
+
tags: {
|
|
1788
|
+
run_id: completion.runId,
|
|
1789
|
+
result_owner_id: completion.resultOwnerId
|
|
1790
|
+
}
|
|
1791
|
+
}));
|
|
1792
|
+
}
|
|
1539
1793
|
async persistEvents(events) {
|
|
1540
1794
|
const points = createReportingSinkMetricPoints(this.sinkName, events, this.tags);
|
|
1541
1795
|
for (const point of points) {
|
|
@@ -1575,6 +1829,8 @@ export class NetdataStatsDReportingSink extends StatsDReportingSinkBase {
|
|
|
1575
1829
|
export class JsonlFileReportingSink extends ExpandedEventReportingSink {
|
|
1576
1830
|
constructor(options = {}) {
|
|
1577
1831
|
super("jsonl", "extensions.reporting_sinks.jsonl");
|
|
1832
|
+
this.preservesRawIterationObservations = true;
|
|
1833
|
+
this.writeTail = Promise.resolve();
|
|
1578
1834
|
const source = asRecord(options);
|
|
1579
1835
|
this.filePath = optionString(source, "filePath", "FilePath").trim() || "loadstrike-reporting.jsonl";
|
|
1580
1836
|
this.staticTags = normalizeStringMap(optionRecord(source, "staticTags", "StaticTags"));
|
|
@@ -1583,8 +1839,28 @@ export class JsonlFileReportingSink extends ExpandedEventReportingSink {
|
|
|
1583
1839
|
if (!events.length) {
|
|
1584
1840
|
return;
|
|
1585
1841
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1842
|
+
await this.persistCanonicalPayload(this.buildPayload(events, this.staticTags));
|
|
1843
|
+
}
|
|
1844
|
+
async saveIterationBatch(batch) {
|
|
1845
|
+
await this.persistCanonicalPayload(batch);
|
|
1846
|
+
}
|
|
1847
|
+
async SaveIterationBatch(batch) {
|
|
1848
|
+
await this.saveIterationBatch(batch);
|
|
1849
|
+
}
|
|
1850
|
+
async completeIterationObservationStream(completion) {
|
|
1851
|
+
await this.persistCanonicalPayload(completion);
|
|
1852
|
+
}
|
|
1853
|
+
async CompleteIterationObservationStream(completion) {
|
|
1854
|
+
await this.completeIterationObservationStream(completion);
|
|
1855
|
+
}
|
|
1856
|
+
persistCanonicalPayload(payload) {
|
|
1857
|
+
const line = `${JSON.stringify(payload)}\n`;
|
|
1858
|
+
const write = this.writeTail.then(async () => {
|
|
1859
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
1860
|
+
await appendFile(this.filePath, line, "utf8");
|
|
1861
|
+
});
|
|
1862
|
+
this.writeTail = write.catch(() => undefined);
|
|
1863
|
+
return write;
|
|
1588
1864
|
}
|
|
1589
1865
|
}
|
|
1590
1866
|
function createRealtimeStatsEvents(session, scenarios) {
|
|
@@ -1631,9 +1907,18 @@ function createRunResultEvents(session, result) {
|
|
|
1631
1907
|
completed_utc: result.completedUtc,
|
|
1632
1908
|
report_file_count: result.reportFiles.length,
|
|
1633
1909
|
disabled_sink_count: result.disabledSinks.length,
|
|
1634
|
-
sink_error_count: result.sinkErrors.length
|
|
1910
|
+
sink_error_count: result.sinkErrors.length,
|
|
1911
|
+
reporting_complete: result.reportingComplete ?? true,
|
|
1912
|
+
observation_last_batch_sequence_64: result.observationDeliveryStats?.lastBatchSequence64 ?? "-1",
|
|
1913
|
+
observation_captured_count_64: result.observationDeliveryStats?.capturedCount64 ?? "0",
|
|
1914
|
+
observation_delivered_count_64: result.observationDeliveryStats?.deliveredCount64 ?? "0",
|
|
1915
|
+
observation_dropped_buffer_count_64: result.observationDeliveryStats?.droppedBufferCount64 ?? "0",
|
|
1916
|
+
observation_dropped_sink_count_64: result.observationDeliveryStats?.droppedSinkCount64 ?? "0"
|
|
1635
1917
|
})
|
|
1636
1918
|
];
|
|
1919
|
+
for (const row of result.correlationRows ?? []) {
|
|
1920
|
+
events.push(createCorrelationOutcomeEvent(session, occurredUtc, row));
|
|
1921
|
+
}
|
|
1637
1922
|
for (const reportFile of result.reportFiles) {
|
|
1638
1923
|
events.push(createReportingEvent(session, occurredUtc, "run.report.final", null, null, {
|
|
1639
1924
|
phase: "final",
|
|
@@ -1664,8 +1949,65 @@ function createRunResultEvents(session, result) {
|
|
|
1664
1949
|
attempts: sinkError.attempts
|
|
1665
1950
|
}));
|
|
1666
1951
|
}
|
|
1952
|
+
for (const warning of result.generatorWarnings ?? []) {
|
|
1953
|
+
events.push(createReportingEvent(session, occurredUtc, "run.generator-warning.final", warning.scenarioName || null, null, {
|
|
1954
|
+
phase: "final",
|
|
1955
|
+
entity: "generator-warning",
|
|
1956
|
+
warning_code: warning.code,
|
|
1957
|
+
...(warning.sinkName ? { sink_name: warning.sinkName } : {})
|
|
1958
|
+
}, {
|
|
1959
|
+
warning_code: warning.code,
|
|
1960
|
+
sink_name: warning.sinkName ?? "",
|
|
1961
|
+
scenario_name: warning.scenarioName,
|
|
1962
|
+
scenario_index: warning.scenarioIndex ?? -1,
|
|
1963
|
+
simulation_index: warning.simulationIndex,
|
|
1964
|
+
simulation_kind: warning.simulationKind ?? "",
|
|
1965
|
+
count_64: warning.count64,
|
|
1966
|
+
message: warning.message,
|
|
1967
|
+
first_observed_utc_ns: warning.firstObservedUtcNs,
|
|
1968
|
+
last_observed_utc_ns: warning.lastObservedUtcNs
|
|
1969
|
+
}));
|
|
1970
|
+
}
|
|
1667
1971
|
return events;
|
|
1668
1972
|
}
|
|
1973
|
+
function createCorrelationOutcomeEvent(session, fallbackOccurredUtc, row) {
|
|
1974
|
+
const occurredToken = optionString(row, "occurredUtc", "OccurredUtc");
|
|
1975
|
+
const parsedOccurredUtc = occurredToken ? new Date(occurredToken) : fallbackOccurredUtc;
|
|
1976
|
+
const occurredUtc = Number.isFinite(parsedOccurredUtc.getTime())
|
|
1977
|
+
? parsedOccurredUtc
|
|
1978
|
+
: fallbackOccurredUtc;
|
|
1979
|
+
const scenarioName = optionString(row, "scenario", "Scenario");
|
|
1980
|
+
const source = optionString(row, "source", "Source");
|
|
1981
|
+
const destination = optionString(row, "destination", "Destination");
|
|
1982
|
+
const runMode = optionString(row, "runMode", "RunMode");
|
|
1983
|
+
const statusCode = optionString(row, "statusCode", "StatusCode");
|
|
1984
|
+
const gatherByField = optionString(row, "gatherByField", "GatherByField");
|
|
1985
|
+
const gatherByValue = optionString(row, "gatherByValue", "GatherByValue");
|
|
1986
|
+
return createReportingEvent(session, occurredUtc, "correlation.outcome.final", scenarioName || null, null, {
|
|
1987
|
+
phase: "final",
|
|
1988
|
+
entity: "correlation-outcome",
|
|
1989
|
+
source,
|
|
1990
|
+
destination,
|
|
1991
|
+
run_mode: runMode,
|
|
1992
|
+
status_code: statusCode,
|
|
1993
|
+
gather_by_field: gatherByField,
|
|
1994
|
+
gather_by_value: gatherByValue
|
|
1995
|
+
}, {
|
|
1996
|
+
occurred_utc: occurredToken || occurredUtc.toISOString(),
|
|
1997
|
+
source,
|
|
1998
|
+
destination,
|
|
1999
|
+
run_mode: runMode,
|
|
2000
|
+
status_code: statusCode,
|
|
2001
|
+
is_success: pickBooleanValue(row, false, "isSuccess", "IsSuccess"),
|
|
2002
|
+
is_failure: pickBooleanValue(row, false, "isFailure", "IsFailure"),
|
|
2003
|
+
gather_by_field: gatherByField,
|
|
2004
|
+
gather_by_value: gatherByValue,
|
|
2005
|
+
tracking_id: optionString(row, "trackingId", "TrackingId"),
|
|
2006
|
+
event_id: optionString(row, "eventId", "EventId"),
|
|
2007
|
+
latency_ms: optionNumber(row, "latencyMs", "LatencyMs") ?? 0,
|
|
2008
|
+
message: optionString(row, "message", "Message")
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
1669
2011
|
function createNodeSummaryEvent(session, occurredUtc, stats) {
|
|
1670
2012
|
return createReportingEvent(session, occurredUtc, "test.final", null, null, {
|
|
1671
2013
|
phase: "final",
|
|
@@ -1851,6 +2193,123 @@ function createReportingEvent(session, occurredUtc, eventType, scenarioName, ste
|
|
|
1851
2193
|
fields: eventFields
|
|
1852
2194
|
};
|
|
1853
2195
|
}
|
|
2196
|
+
function removeSdkPercentileFields(event) {
|
|
2197
|
+
const fields = Object.fromEntries(Object.entries(event.fields).filter(([name]) => !/_(?:latency|bytes)_p(?:50|75|95|99)(?:_|$)/iu.test(name)));
|
|
2198
|
+
return {
|
|
2199
|
+
...event,
|
|
2200
|
+
fields
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
function createIterationObservationEvents(session, batch) {
|
|
2204
|
+
const events = [];
|
|
2205
|
+
for (const observation of batch.observations) {
|
|
2206
|
+
const tags = {
|
|
2207
|
+
run_id: batch.runId,
|
|
2208
|
+
result_owner_id: batch.resultOwnerId,
|
|
2209
|
+
phase: observation.phase,
|
|
2210
|
+
scenario_name: observation.scenarioName,
|
|
2211
|
+
simulation_kind: observation.simulationKind,
|
|
2212
|
+
is_final_attempt: observation.isFinalAttempt ? "true" : "false"
|
|
2213
|
+
};
|
|
2214
|
+
events.push({
|
|
2215
|
+
runId: batch.runId,
|
|
2216
|
+
eventType: "iteration.observation",
|
|
2217
|
+
occurredUtc: new Date(),
|
|
2218
|
+
sessionId: batch.sessionId,
|
|
2219
|
+
testSuite: session.testSuite,
|
|
2220
|
+
testName: session.testName,
|
|
2221
|
+
clusterId: session.clusterId,
|
|
2222
|
+
nodeType: session.nodeType,
|
|
2223
|
+
machineName: session.machineName,
|
|
2224
|
+
scenarioName: observation.scenarioName,
|
|
2225
|
+
stepName: null,
|
|
2226
|
+
tags,
|
|
2227
|
+
fields: {
|
|
2228
|
+
schema_version: observation.schemaVersion,
|
|
2229
|
+
batch_id: batch.batchId,
|
|
2230
|
+
observation_id: observation.observationId,
|
|
2231
|
+
iteration_id: observation.iterationId,
|
|
2232
|
+
process_group: observation.processGroup,
|
|
2233
|
+
scenario_index: observation.scenarioIndex,
|
|
2234
|
+
simulation_index: observation.simulationIndex,
|
|
2235
|
+
global_ordinal_64: observation.globalOrdinal64,
|
|
2236
|
+
shard_index: observation.shardIndex,
|
|
2237
|
+
shard_count: observation.shardCount,
|
|
2238
|
+
attempt_index: observation.attemptIndex,
|
|
2239
|
+
started_utc_ns: observation.startedUtcNs,
|
|
2240
|
+
completed_utc_ns: observation.completedUtcNs,
|
|
2241
|
+
observed_latency_us_64: observation.observedLatencyUs64,
|
|
2242
|
+
reported_latency_us_64: observation.reportedLatencyUs64,
|
|
2243
|
+
is_success: observation.isSuccess,
|
|
2244
|
+
status_code: observation.statusCode,
|
|
2245
|
+
size_bytes_64: observation.sizeBytes64
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
for (const step of observation.steps) {
|
|
2249
|
+
events.push({
|
|
2250
|
+
runId: batch.runId,
|
|
2251
|
+
eventType: "iteration.step",
|
|
2252
|
+
occurredUtc: new Date(),
|
|
2253
|
+
sessionId: batch.sessionId,
|
|
2254
|
+
testSuite: session.testSuite,
|
|
2255
|
+
testName: session.testName,
|
|
2256
|
+
clusterId: session.clusterId,
|
|
2257
|
+
nodeType: session.nodeType,
|
|
2258
|
+
machineName: session.machineName,
|
|
2259
|
+
scenarioName: observation.scenarioName,
|
|
2260
|
+
stepName: step.stepName,
|
|
2261
|
+
tags: { ...tags },
|
|
2262
|
+
fields: {
|
|
2263
|
+
schema_version: observation.schemaVersion,
|
|
2264
|
+
batch_id: batch.batchId,
|
|
2265
|
+
observation_id: observation.observationId,
|
|
2266
|
+
iteration_id: observation.iterationId,
|
|
2267
|
+
attempt_index: observation.attemptIndex,
|
|
2268
|
+
sort_index: step.sortIndex,
|
|
2269
|
+
started_utc_ns: step.startedUtcNs,
|
|
2270
|
+
completed_utc_ns: step.completedUtcNs,
|
|
2271
|
+
observed_latency_us_64: step.observedLatencyUs64,
|
|
2272
|
+
reported_latency_us_64: step.reportedLatencyUs64,
|
|
2273
|
+
is_success: step.isSuccess,
|
|
2274
|
+
status_code: step.statusCode,
|
|
2275
|
+
size_bytes_64: step.sizeBytes64
|
|
2276
|
+
}
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
return events;
|
|
2281
|
+
}
|
|
2282
|
+
function createIterationCompletionEvents(session, completion) {
|
|
2283
|
+
return [{
|
|
2284
|
+
runId: completion.runId,
|
|
2285
|
+
eventType: "observation.stream.completed",
|
|
2286
|
+
occurredUtc: new Date(),
|
|
2287
|
+
sessionId: completion.sessionId,
|
|
2288
|
+
testSuite: session.testSuite,
|
|
2289
|
+
testName: session.testName,
|
|
2290
|
+
clusterId: session.clusterId,
|
|
2291
|
+
nodeType: session.nodeType,
|
|
2292
|
+
machineName: session.machineName,
|
|
2293
|
+
scenarioName: null,
|
|
2294
|
+
stepName: null,
|
|
2295
|
+
tags: {
|
|
2296
|
+
run_id: completion.runId,
|
|
2297
|
+
result_owner_id: completion.resultOwnerId,
|
|
2298
|
+
phase: "final"
|
|
2299
|
+
},
|
|
2300
|
+
fields: {
|
|
2301
|
+
schema_version: completion.schemaVersion,
|
|
2302
|
+
process_group: completion.processGroup,
|
|
2303
|
+
last_batch_sequence_64: completion.lastBatchSequence64,
|
|
2304
|
+
captured_count_64: completion.capturedCount64,
|
|
2305
|
+
delivered_count_64: completion.deliveredCount64,
|
|
2306
|
+
dropped_buffer_count_64: completion.droppedBufferCount64,
|
|
2307
|
+
dropped_sink_count_64: completion.droppedSinkCount64,
|
|
2308
|
+
reporting_complete: completion.reportingComplete,
|
|
2309
|
+
completed_utc_ns: completion.completedUtcNs
|
|
2310
|
+
}
|
|
2311
|
+
}];
|
|
2312
|
+
}
|
|
1854
2313
|
function portalEventPayload(event, index) {
|
|
1855
2314
|
return {
|
|
1856
2315
|
eventId: portalEventId(event, index),
|
|
@@ -1923,10 +2382,6 @@ function addMeasurementFields(fields, prefix, measurement) {
|
|
|
1923
2382
|
fields[`${prefix}_latency_min_ms`] = latency.minMs ?? 0;
|
|
1924
2383
|
fields[`${prefix}_latency_mean_ms`] = latency.meanMs ?? 0;
|
|
1925
2384
|
fields[`${prefix}_latency_max_ms`] = latency.maxMs ?? 0;
|
|
1926
|
-
fields[`${prefix}_latency_p50_ms`] = latency.percent50 ?? 0;
|
|
1927
|
-
fields[`${prefix}_latency_p75_ms`] = latency.percent75 ?? 0;
|
|
1928
|
-
fields[`${prefix}_latency_p95_ms`] = latency.percent95 ?? 0;
|
|
1929
|
-
fields[`${prefix}_latency_p99_ms`] = latency.percent99 ?? 0;
|
|
1930
2385
|
fields[`${prefix}_latency_std_dev`] = latency.stdDev ?? 0;
|
|
1931
2386
|
fields[`${prefix}_latency_le_800_count`] = latencyCount.lessOrEq800 ?? 0;
|
|
1932
2387
|
fields[`${prefix}_latency_gt_800_lt_1200_count`] = latencyCount.more800Less1200 ?? 0;
|
|
@@ -1935,10 +2390,6 @@ function addMeasurementFields(fields, prefix, measurement) {
|
|
|
1935
2390
|
fields[`${prefix}_bytes_min`] = dataTransfer.minBytes ?? 0;
|
|
1936
2391
|
fields[`${prefix}_bytes_mean`] = dataTransfer.meanBytes ?? 0;
|
|
1937
2392
|
fields[`${prefix}_bytes_max`] = dataTransfer.maxBytes ?? 0;
|
|
1938
|
-
fields[`${prefix}_bytes_p50`] = dataTransfer.percent50 ?? 0;
|
|
1939
|
-
fields[`${prefix}_bytes_p75`] = dataTransfer.percent75 ?? 0;
|
|
1940
|
-
fields[`${prefix}_bytes_p95`] = dataTransfer.percent95 ?? 0;
|
|
1941
|
-
fields[`${prefix}_bytes_p99`] = dataTransfer.percent99 ?? 0;
|
|
1942
2393
|
fields[`${prefix}_bytes_std_dev`] = dataTransfer.stdDev ?? 0;
|
|
1943
2394
|
fields[`${prefix}_status_code_count`] = statusCodes.length;
|
|
1944
2395
|
}
|
|
@@ -3074,6 +3525,9 @@ function deepCloneValue(value) {
|
|
|
3074
3525
|
}
|
|
3075
3526
|
return value;
|
|
3076
3527
|
}
|
|
3528
|
+
function gzipJsonRequestBody(value) {
|
|
3529
|
+
return Uint8Array.from(serializeIterationObservationBatchGzipJson(value)).buffer;
|
|
3530
|
+
}
|
|
3077
3531
|
async function postWithTimeout(fetchImpl, url, init, timeoutMs, sinkName) {
|
|
3078
3532
|
const controller = new AbortController();
|
|
3079
3533
|
const timer = setTimeout(() => controller.abort(), Math.max(timeoutMs, 1));
|