@loadstrike/loadstrike-sdk 1.0.26901 → 1.0.27301

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
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.__loadstrikeTestExports = exports.OtelCollectorReportingSink = exports.SplunkReportingSink = exports.DatadogReportingSink = exports.TimescaleDbReportingSink = exports.GrafanaLokiReportingSink = exports.InfluxDbReportingSink = exports.PortalReportingSink = exports.CompositeReportingSink = exports.ConsoleReportingSink = exports.MemoryReportingSink = exports.OtelCollectorReportingSinkOptions = exports.SplunkReportingSinkOptions = exports.DatadogReportingSinkOptions = exports.TimescaleDbReportingSinkOptions = exports.GrafanaLokiReportingSinkOptions = exports.InfluxDbReportingSinkOptions = void 0;
3
+ exports.__loadstrikeTestExports = exports.JsonlFileReportingSink = exports.NetdataStatsDReportingSink = exports.DogStatsDReportingSink = exports.StatsDReportingSink = exports.KafkaReportingSink = exports.GenericWebhookReportingSink = exports.NewRelicReportingSink = exports.OpenSearchReportingSink = exports.ElasticsearchReportingSink = exports.DynatraceReportingSink = exports.CloudWatchReportingSink = exports.PrometheusRemoteWriteReportingSink = exports.OtelCollectorReportingSink = exports.SplunkReportingSink = exports.DatadogReportingSink = exports.TimescaleDbReportingSink = exports.GrafanaLokiReportingSink = exports.InfluxDbReportingSink = exports.PortalReportingSink = exports.CompositeReportingSink = exports.ConsoleReportingSink = exports.MemoryReportingSink = exports.OtelCollectorReportingSinkOptions = exports.SplunkReportingSinkOptions = exports.DatadogReportingSinkOptions = exports.TimescaleDbReportingSinkOptions = exports.GrafanaLokiReportingSinkOptions = exports.InfluxDbReportingSinkOptions = void 0;
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");
8
+ const node_path_1 = require("node:path");
9
+ const node_dgram_1 = require("node:dgram");
7
10
  const pg_1 = require("pg");
8
11
  const DEFAULT_INFLUX_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:InfluxDb";
9
12
  const DEFAULT_GRAFANA_LOKI_CONFIGURATION_SECTION_PATH = "LoadStrike:ReportingSinks:GrafanaLoki";
@@ -1384,6 +1387,238 @@ class OtelCollectorReportingSink {
1384
1387
  }
1385
1388
  }
1386
1389
  exports.OtelCollectorReportingSink = OtelCollectorReportingSink;
1390
+ class ExpandedEventReportingSink {
1391
+ constructor(sinkName, licenseFeature) {
1392
+ this.baseContext = null;
1393
+ this.session = null;
1394
+ this.sinkName = sinkName;
1395
+ this.SinkName = sinkName;
1396
+ this.licenseFeature = licenseFeature;
1397
+ this.LicenseFeature = licenseFeature;
1398
+ }
1399
+ init(context, _infraConfig) {
1400
+ this.baseContext = cloneBaseContext(context);
1401
+ }
1402
+ Init(context, infraConfig) {
1403
+ this.init(context, infraConfig);
1404
+ }
1405
+ start(session) {
1406
+ this.session = sinkSessionMetadataFromContext(this.getBaseContext(), session);
1407
+ }
1408
+ Start(session) {
1409
+ this.start(session);
1410
+ }
1411
+ async saveRealtimeStats(scenarioStats) {
1412
+ await this.persistEvents(createRealtimeStatsEvents(this.getSession(), scenarioStats));
1413
+ }
1414
+ async SaveRealtimeStats(scenarioStats) {
1415
+ await this.saveRealtimeStats(scenarioStats);
1416
+ }
1417
+ async saveRealtimeMetrics(metrics) {
1418
+ await this.persistEvents(createRealtimeMetricEvents(this.getSession(), metrics));
1419
+ }
1420
+ async SaveRealtimeMetrics(metrics) {
1421
+ await this.saveRealtimeMetrics(metrics);
1422
+ }
1423
+ async saveRunResult(result) {
1424
+ await this.persistEvents(createRunResultEvents(this.getSession(), result));
1425
+ }
1426
+ async SaveRunResult(result) {
1427
+ await this.saveRunResult(result);
1428
+ }
1429
+ stop() {
1430
+ this.session = null;
1431
+ }
1432
+ Stop() {
1433
+ this.stop();
1434
+ }
1435
+ Dispose() {
1436
+ this.baseContext = null;
1437
+ this.stop();
1438
+ }
1439
+ getBaseContext() {
1440
+ if (!this.baseContext) {
1441
+ throw new Error(`${this.sinkName} has not been initialized.`);
1442
+ }
1443
+ return this.baseContext;
1444
+ }
1445
+ getSession() {
1446
+ if (!this.session) {
1447
+ throw new Error(`${this.sinkName} has not been started.`);
1448
+ }
1449
+ return this.session;
1450
+ }
1451
+ buildPayload(events, staticTags = {}) {
1452
+ return {
1453
+ sinkName: this.sinkName,
1454
+ runId: this.getSession().runId,
1455
+ sessionId: this.getSession().sessionId,
1456
+ events: events.map((event) => ({
1457
+ ...event,
1458
+ tags: { ...staticTags, ...event.tags }
1459
+ })),
1460
+ metrics: createReportingSinkMetricPoints(this.sinkName, events, staticTags)
1461
+ };
1462
+ }
1463
+ }
1464
+ class ExpandedHttpJsonReportingSink extends ExpandedEventReportingSink {
1465
+ constructor(sinkName, licenseFeature, defaultEndpointPath, options = {}) {
1466
+ super(sinkName, licenseFeature);
1467
+ const source = asRecord(options);
1468
+ this.baseUrl = optionString(source, "baseUrl", "BaseUrl").trim();
1469
+ this.endpointPath = optionString(source, "endpointPath", "EndpointPath").trim() || defaultEndpointPath;
1470
+ this.headers = normalizeStringMap(optionRecord(source, "headers", "Headers"));
1471
+ this.staticTags = normalizeStringMap(optionRecord(source, "staticTags", "StaticTags"));
1472
+ this.timeoutMs = resolveTimeoutMs(optionNumber(source, "timeoutSeconds", "TimeoutSeconds"), optionNumber(source, "timeoutMs", "TimeoutMs"));
1473
+ this.fetchImpl = pickRecordValue(source, "fetchImpl", "FetchImpl") ?? fetch;
1474
+ }
1475
+ init(context, infraConfig) {
1476
+ super.init(context, infraConfig);
1477
+ if (!this.baseUrl.trim()) {
1478
+ throw new Error(`${this.constructor.name} requires BaseUrl.`);
1479
+ }
1480
+ }
1481
+ async persistEvents(events) {
1482
+ if (!events.length) {
1483
+ return;
1484
+ }
1485
+ await postWithTimeout(this.fetchImpl, `${trimTrailingSlashes(this.baseUrl)}${normalizePath(this.endpointPath)}`, {
1486
+ method: "POST",
1487
+ headers: {
1488
+ "Content-Type": "application/json",
1489
+ ...this.headers
1490
+ },
1491
+ body: JSON.stringify(this.buildPayload(events, this.staticTags))
1492
+ }, this.timeoutMs, this.constructor.name);
1493
+ }
1494
+ }
1495
+ class PrometheusRemoteWriteReportingSink extends ExpandedHttpJsonReportingSink {
1496
+ constructor(options = {}) {
1497
+ super("prometheus-remote-write", "extensions.reporting_sinks.prometheus_remote_write", "/api/v1/write", options);
1498
+ }
1499
+ }
1500
+ exports.PrometheusRemoteWriteReportingSink = PrometheusRemoteWriteReportingSink;
1501
+ class CloudWatchReportingSink extends ExpandedHttpJsonReportingSink {
1502
+ constructor(options = {}) {
1503
+ super("cloudwatch", "extensions.reporting_sinks.cloudwatch", "/loadstrike/events", options);
1504
+ }
1505
+ }
1506
+ exports.CloudWatchReportingSink = CloudWatchReportingSink;
1507
+ class DynatraceReportingSink extends ExpandedHttpJsonReportingSink {
1508
+ constructor(options = {}) {
1509
+ super("dynatrace", "extensions.reporting_sinks.dynatrace", "/api/v2/logs/ingest", options);
1510
+ }
1511
+ }
1512
+ exports.DynatraceReportingSink = DynatraceReportingSink;
1513
+ class ElasticsearchReportingSink extends ExpandedHttpJsonReportingSink {
1514
+ constructor(options = {}) {
1515
+ super("elasticsearch", "extensions.reporting_sinks.elasticsearch", "/loadstrike-events/_doc", options);
1516
+ }
1517
+ }
1518
+ exports.ElasticsearchReportingSink = ElasticsearchReportingSink;
1519
+ class OpenSearchReportingSink extends ExpandedHttpJsonReportingSink {
1520
+ constructor(options = {}) {
1521
+ super("opensearch", "extensions.reporting_sinks.opensearch", "/loadstrike-events/_doc", options);
1522
+ }
1523
+ }
1524
+ exports.OpenSearchReportingSink = OpenSearchReportingSink;
1525
+ class NewRelicReportingSink extends ExpandedHttpJsonReportingSink {
1526
+ constructor(options = {}) {
1527
+ super("new-relic", "extensions.reporting_sinks.new_relic", "/log/v1", options);
1528
+ }
1529
+ }
1530
+ exports.NewRelicReportingSink = NewRelicReportingSink;
1531
+ class GenericWebhookReportingSink extends ExpandedHttpJsonReportingSink {
1532
+ constructor(options = {}) {
1533
+ super("webhook", "extensions.reporting_sinks.webhook", "/loadstrike", options);
1534
+ }
1535
+ }
1536
+ exports.GenericWebhookReportingSink = GenericWebhookReportingSink;
1537
+ class KafkaReportingSink extends ExpandedEventReportingSink {
1538
+ constructor(options = {}) {
1539
+ super("kafka", "extensions.reporting_sinks.kafka");
1540
+ const source = asRecord(options);
1541
+ this.topic = optionString(source, "topic", "Topic").trim() || "loadstrike-events";
1542
+ this.staticTags = normalizeStringMap(optionRecord(source, "staticTags", "StaticTags"));
1543
+ this.publishAsync = pickRecordValue(source, "publishAsync", "PublishAsync");
1544
+ }
1545
+ async persistEvents(events) {
1546
+ if (!events.length) {
1547
+ return;
1548
+ }
1549
+ if (!this.publishAsync) {
1550
+ throw new Error("KafkaReportingSink requires PublishAsync when a Kafka producer is not configured by the host application.");
1551
+ }
1552
+ await this.publishAsync(this.topic, JSON.stringify(this.buildPayload(events, this.staticTags)));
1553
+ }
1554
+ }
1555
+ exports.KafkaReportingSink = KafkaReportingSink;
1556
+ class StatsDReportingSinkBase extends ExpandedEventReportingSink {
1557
+ constructor(sinkName, licenseFeature, options = {}, dogStatsD = false) {
1558
+ super(sinkName, licenseFeature);
1559
+ const source = asRecord(options);
1560
+ this.prefix = optionString(source, "prefix", "Prefix").trim() || "loadstrike";
1561
+ this.host = optionString(source, "host", "Host").trim() || "127.0.0.1";
1562
+ this.port = optionNumber(source, "port", "Port") ?? 8125;
1563
+ this.tags = normalizeStringMap(optionRecord(source, "tags", "Tags"));
1564
+ this.sendLineAsync = pickRecordValue(source, "sendLineAsync", "SendLineAsync");
1565
+ this.dogStatsD = dogStatsD;
1566
+ }
1567
+ async persistEvents(events) {
1568
+ const points = createReportingSinkMetricPoints(this.sinkName, events, this.tags);
1569
+ for (const point of points) {
1570
+ await this.sendLine(this.formatPoint(point));
1571
+ }
1572
+ }
1573
+ formatPoint(point) {
1574
+ const metricType = point.metricKind === "count" ? "c" : "g";
1575
+ const tagSuffix = this.dogStatsD
1576
+ ? dogStatsDTagSuffix({ ...this.tags, ...point.tags })
1577
+ : "";
1578
+ return `${this.prefix}.${sanitizeMetricToken(point.metricName)}:${point.value}|${metricType}${tagSuffix}`;
1579
+ }
1580
+ async sendLine(line) {
1581
+ if (this.sendLineAsync) {
1582
+ await this.sendLineAsync(line);
1583
+ return;
1584
+ }
1585
+ await sendStatsDUdpLine(this.host, this.port, line);
1586
+ }
1587
+ }
1588
+ class StatsDReportingSink extends StatsDReportingSinkBase {
1589
+ constructor(options = {}) {
1590
+ super("statsd", "extensions.reporting_sinks.statsd", options, false);
1591
+ }
1592
+ }
1593
+ exports.StatsDReportingSink = StatsDReportingSink;
1594
+ class DogStatsDReportingSink extends StatsDReportingSinkBase {
1595
+ constructor(options = {}) {
1596
+ super("dogstatsd", "extensions.reporting_sinks.dogstatsd", options, true);
1597
+ }
1598
+ }
1599
+ exports.DogStatsDReportingSink = DogStatsDReportingSink;
1600
+ class NetdataStatsDReportingSink extends StatsDReportingSinkBase {
1601
+ constructor(options = {}) {
1602
+ super("netdata-statsd", "extensions.reporting_sinks.netdata", options, false);
1603
+ }
1604
+ }
1605
+ exports.NetdataStatsDReportingSink = NetdataStatsDReportingSink;
1606
+ class JsonlFileReportingSink extends ExpandedEventReportingSink {
1607
+ constructor(options = {}) {
1608
+ super("jsonl", "extensions.reporting_sinks.jsonl");
1609
+ const source = asRecord(options);
1610
+ this.filePath = optionString(source, "filePath", "FilePath").trim() || "loadstrike-reporting.jsonl";
1611
+ this.staticTags = normalizeStringMap(optionRecord(source, "staticTags", "StaticTags"));
1612
+ }
1613
+ async persistEvents(events) {
1614
+ if (!events.length) {
1615
+ return;
1616
+ }
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");
1619
+ }
1620
+ }
1621
+ exports.JsonlFileReportingSink = JsonlFileReportingSink;
1387
1622
  function createRealtimeStatsEvents(session, scenarios) {
1388
1623
  const occurredUtc = new Date();
1389
1624
  const events = [];
@@ -2597,6 +2832,32 @@ function normalizeStringMap(value) {
2597
2832
  }
2598
2833
  return rows;
2599
2834
  }
2835
+ function sanitizeMetricToken(value) {
2836
+ return String(value ?? "")
2837
+ .trim()
2838
+ .replace(/[^A-Za-z0-9_.-]+/g, "_")
2839
+ .replace(/^_+|_+$/g, "") || "metric";
2840
+ }
2841
+ function dogStatsDTagSuffix(tags) {
2842
+ const values = Object.entries(tags)
2843
+ .filter(([key]) => key.trim().length > 0)
2844
+ .map(([key, value]) => `${sanitizeMetricToken(key)}:${String(value ?? "").replace(/[,|]+/g, "_")}`);
2845
+ return values.length ? `|#${values.join(",")}` : "";
2846
+ }
2847
+ function sendStatsDUdpLine(host, port, line) {
2848
+ return new Promise((resolve, reject) => {
2849
+ const socket = (0, node_dgram_1.createSocket)("udp4");
2850
+ const payload = Buffer.from(line, "utf8");
2851
+ socket.send(payload, port, host, (error) => {
2852
+ socket.close();
2853
+ if (error) {
2854
+ reject(error);
2855
+ return;
2856
+ }
2857
+ resolve();
2858
+ });
2859
+ });
2860
+ }
2600
2861
  function resolveTimeoutMs(timeoutSeconds, timeoutMs) {
2601
2862
  if (Number.isFinite(timeoutMs) && Number(timeoutMs) > 0) {
2602
2863
  return Math.max(Math.trunc(Number(timeoutMs)), 1);