@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/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 node_fs_1 = require("node:fs");
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
- this.runToken = String(session.runToken ?? session.RunToken ?? "").trim();
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.runToken,
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: events.map((event) => ({
1621
+ events: projectedEvents.map((event) => ({
1457
1622
  ...event,
1458
1623
  tags: { ...staticTags, ...event.tags }
1459
1624
  })),
1460
- metrics: createReportingSinkMetricPoints(this.sinkName, events, staticTags)
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
- (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(this.filePath), { recursive: true });
1618
- (0, node_fs_1.appendFileSync)(this.filePath, `${JSON.stringify(this.buildPayload(events, this.staticTags))}\n`, "utf8");
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));