@loadstrike/loadstrike-sdk 1.0.21401 → 1.0.22001

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.
@@ -51,6 +51,61 @@ function reportValue(source, ...keys) {
51
51
  }
52
52
  return undefined;
53
53
  }
54
+ function reportDurationValue(source) {
55
+ return reportValue(source, "durationMs", "DurationMs", "duration", "Duration");
56
+ }
57
+ function reportBytesValue(source) {
58
+ return reportValue(source, "allBytes", "AllBytes", "totalBytes", "TotalBytes");
59
+ }
60
+ function reportTotalBytes(nodeStats, scenarios) {
61
+ const totalBytes = asInt(reportBytesValue(nodeStats));
62
+ if (totalBytes !== 0) {
63
+ return totalBytes;
64
+ }
65
+ return scenarios.reduce((sum, scenario) => sum + asInt(reportBytesValue(scenario)), 0);
66
+ }
67
+ function reportRequestCountValue(source) {
68
+ return asInt(reportValue(source, "allRequestCount", "AllRequestCount", "requestCount", "RequestCount"));
69
+ }
70
+ function reportOkCountValue(source) {
71
+ return asInt(reportValue(source, "allOkCount", "AllOkCount", "okCount", "OkCount"));
72
+ }
73
+ function reportFailCountValue(source) {
74
+ return asInt(reportValue(source, "allFailCount", "AllFailCount", "failCount", "FailCount"));
75
+ }
76
+ function reportTotalRequestCount(nodeStats, scenarios) {
77
+ const total = reportRequestCountValue(nodeStats);
78
+ return total !== 0 || scenarios.length === 0
79
+ ? total
80
+ : scenarios.reduce((sum, scenario) => sum + reportRequestCountValue(scenario), 0);
81
+ }
82
+ function reportTotalOkCount(nodeStats, scenarios) {
83
+ const total = reportOkCountValue(nodeStats);
84
+ return total !== 0 || scenarios.length === 0
85
+ ? total
86
+ : scenarios.reduce((sum, scenario) => sum + reportOkCountValue(scenario), 0);
87
+ }
88
+ function reportTotalFailCount(nodeStats, scenarios) {
89
+ const total = reportFailCountValue(nodeStats);
90
+ return total !== 0 || scenarios.length === 0
91
+ ? total
92
+ : scenarios.reduce((sum, scenario) => sum + reportFailCountValue(scenario), 0);
93
+ }
94
+ function reportScenarios(nodeStats) {
95
+ return sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"));
96
+ }
97
+ function reportSteps(scenario) {
98
+ return sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"));
99
+ }
100
+ function reportThresholds(nodeStats) {
101
+ return reportArray(nodeStats, "thresholds", "Thresholds", "thresholdResults", "ThresholdResults");
102
+ }
103
+ function reportMetrics(nodeStats) {
104
+ return reportObject(nodeStats, "metrics", "Metrics", "metricStats", "MetricStats");
105
+ }
106
+ function reportMetricName(source) {
107
+ return asString(reportValue(source, "metricName", "MetricName", "name", "Name"));
108
+ }
54
109
  function reportObject(source, ...keys) {
55
110
  const value = reportValue(source, ...keys);
56
111
  return value && typeof value === "object" && !Array.isArray(value)
@@ -84,6 +139,16 @@ function asFloat(value) {
84
139
  const parsed = Number.parseFloat(asString(value));
85
140
  return Number.isFinite(parsed) ? parsed : 0;
86
141
  }
142
+ function asBool(value) {
143
+ if (typeof value === "boolean") {
144
+ return value;
145
+ }
146
+ if (typeof value === "number" && Number.isFinite(value)) {
147
+ return value !== 0;
148
+ }
149
+ const normalized = asString(value).trim().toLowerCase();
150
+ return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "y" || normalized === "on";
151
+ }
87
152
  function formatCellValue(value) {
88
153
  if (value == null) {
89
154
  return "";
@@ -303,7 +368,7 @@ function buildDotnetTableHtml(rows, wrapInCard = true) {
303
368
  appendReportLine(parts, "<table>");
304
369
  appendReportLine(parts, "<thead><tr>");
305
370
  for (const header of headers) {
306
- appendReportLine(parts, `<th>${escapeHtml(header)}</th>`);
371
+ appendReportLine(parts, `<th>${escapeHtml(formatReportTableHeader(header))}</th>`);
307
372
  }
308
373
  appendReportLine(parts, "</tr></thead><tbody>");
309
374
  for (const row of rows) {
@@ -319,6 +384,14 @@ function buildDotnetTableHtml(rows, wrapInCard = true) {
319
384
  }
320
385
  return parts.join("");
321
386
  }
387
+ function formatReportTableHeader(header) {
388
+ if (header === "LatencyStdDev") {
389
+ return "LatencyStdDev (ms)";
390
+ }
391
+ return header.endsWith("Ms") && header.length > "Ms".length
392
+ ? `${header.slice(0, -"Ms".length)} (ms)`
393
+ : header;
394
+ }
322
395
  function buildFailedStatusRows(scenarios) {
323
396
  const rows = [];
324
397
  for (const scenario of scenarios) {
@@ -341,7 +414,7 @@ function buildFailedStatusRows(scenarios) {
341
414
  IsError: isError
342
415
  });
343
416
  }
344
- for (const step of sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"))) {
417
+ for (const step of reportSteps(scenario)) {
345
418
  const stepName = asString(reportValue(step, "stepName", "StepName"));
346
419
  const stepFail = reportObject(step, "fail", "Fail");
347
420
  for (const code of reportArray(stepFail, "statusCodes", "StatusCodes")) {
@@ -593,6 +666,65 @@ function buildMeasurementRow(scope, scenarioName, stepName, resultName, measurem
593
666
  BytesStdDev: formatReportNumber(reportValue(dataTransfer, "stdDev", "StdDev"))
594
667
  };
595
668
  }
669
+ function pushMeasurementRowIfData(rows, scope, scenarioName, stepName, resultName, measurement) {
670
+ if (hasMeasurementData(measurement)) {
671
+ rows.push(buildMeasurementRow(scope, scenarioName, stepName, resultName, measurement));
672
+ }
673
+ }
674
+ function hasMeasurementData(measurement) {
675
+ if (Object.keys(measurement).length === 0) {
676
+ return false;
677
+ }
678
+ const request = reportObject(measurement, "request", "Request");
679
+ if (asInt(reportValue(request, "count", "Count")) !== 0 ||
680
+ asInt(reportValue(request, "percent", "Percent")) !== 0 ||
681
+ asFloat(reportValue(request, "rps", "RPS")) !== 0) {
682
+ return true;
683
+ }
684
+ const latency = reportObject(measurement, "latency", "Latency");
685
+ const latencyKeys = [
686
+ ["minMs", "MinMs"],
687
+ ["meanMs", "MeanMs"],
688
+ ["percent50", "Percent50"],
689
+ ["percent75", "Percent75"],
690
+ ["percent95", "Percent95"],
691
+ ["percent99", "Percent99"],
692
+ ["maxMs", "MaxMs"],
693
+ ["stdDev", "StdDev"]
694
+ ];
695
+ if (latencyKeys.some(([camelKey, pascalKey]) => asFloat(reportValue(latency, camelKey, pascalKey)) !== 0)) {
696
+ return true;
697
+ }
698
+ const latencyCount = reportObject(latency, "latencyCount", "LatencyCount");
699
+ const latencyCountKeys = [
700
+ ["lessOrEq800", "LessOrEq800"],
701
+ ["more800Less1200", "More800Less1200"],
702
+ ["moreOrEq1200", "MoreOrEq1200"]
703
+ ];
704
+ if (latencyCountKeys.some(([camelKey, pascalKey]) => asInt(reportValue(latencyCount, camelKey, pascalKey)) !== 0)) {
705
+ return true;
706
+ }
707
+ const dataTransfer = reportObject(measurement, "dataTransfer", "DataTransfer");
708
+ const dataTransferKeys = [
709
+ ["allBytes", "AllBytes"],
710
+ ["minBytes", "MinBytes"],
711
+ ["meanBytes", "MeanBytes"],
712
+ ["percent50", "Percent50"],
713
+ ["percent75", "Percent75"],
714
+ ["percent95", "Percent95"],
715
+ ["percent99", "Percent99"],
716
+ ["maxBytes", "MaxBytes"],
717
+ ["stdDev", "StdDev"]
718
+ ];
719
+ if (dataTransferKeys.some(([camelKey, pascalKey]) => asFloat(reportValue(dataTransfer, camelKey, pascalKey)) !== 0)) {
720
+ return true;
721
+ }
722
+ return reportArray(measurement, "statusCodes", "StatusCodes").some((code) => asInt(reportValue(code, "count", "Count")) !== 0 ||
723
+ asInt(reportValue(code, "percent", "Percent")) !== 0 ||
724
+ asBool(reportValue(code, "isError", "IsError")) ||
725
+ asString(reportValue(code, "statusCode", "StatusCode")).trim().length > 0 ||
726
+ asString(reportValue(code, "message", "Message")).trim().length > 0);
727
+ }
596
728
  function appendChartCard(parts, id, title) {
597
729
  appendReportLine(parts, `<div class="chart-card"><h3>${escapeHtml(title)}</h3><canvas id="${escapeHtml(id)}" class="chart-canvas"></canvas></div>`);
598
730
  }
@@ -611,24 +743,27 @@ function hasNonEmptyHints(plugin) {
611
743
  return reportArray(plugin, "hints", "Hints").some((hint) => asString(hint).trim().length > 0);
612
744
  }
613
745
  function buildDotnetScenarioRows(nodeStats) {
614
- return sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats")).map((scenario) => ({
615
- Scenario: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
616
- Simulation: asString(reportValue(reportObject(scenario, "loadSimulationStats", "LoadSimulationStats"), "simulationName", "SimulationName")),
617
- SimulationValue: asInt(reportValue(reportObject(scenario, "loadSimulationStats", "LoadSimulationStats"), "value", "Value")),
618
- Requests: asInt(reportValue(scenario, "allRequestCount", "AllRequestCount")),
619
- OK: asInt(reportValue(scenario, "allOkCount", "AllOkCount")),
620
- FAIL: asInt(reportValue(scenario, "allFailCount", "AllFailCount")),
621
- Duration: formatDotnetTimeSpan(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs")),
622
- RPS: formatReportNumber(reportDurationSeconds(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs")) <= 0 ? 0 : asInt(reportValue(scenario, "allRequestCount", "AllRequestCount")) / reportDurationSeconds(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs"))),
623
- LatencyP95Ms: formatReportNumber(Math.max(asFloat(reportValue(reportObject(reportObject(scenario, "ok", "Ok"), "latency", "Latency"), "percent95", "Percent95")), asFloat(reportValue(reportObject(reportObject(scenario, "fail", "Fail"), "latency", "Latency"), "percent95", "Percent95")))),
624
- LatencyP99Ms: formatReportNumber(Math.max(asFloat(reportValue(reportObject(reportObject(scenario, "ok", "Ok"), "latency", "Latency"), "percent99", "Percent99")), asFloat(reportValue(reportObject(reportObject(scenario, "fail", "Fail"), "latency", "Latency"), "percent99", "Percent99")))),
625
- CurrentOperation: asString(reportValue(scenario, "currentOperation", "CurrentOperation"))
626
- }));
746
+ return reportScenarios(nodeStats).map((scenario) => {
747
+ const requestCount = reportRequestCountValue(scenario);
748
+ return {
749
+ Scenario: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
750
+ Simulation: asString(reportValue(reportObject(scenario, "loadSimulationStats", "LoadSimulationStats"), "simulationName", "SimulationName")),
751
+ SimulationValue: asInt(reportValue(reportObject(scenario, "loadSimulationStats", "LoadSimulationStats"), "value", "Value")),
752
+ Requests: requestCount,
753
+ OK: reportOkCountValue(scenario),
754
+ FAIL: reportFailCountValue(scenario),
755
+ Duration: formatDotnetTimeSpan(reportDurationValue(scenario)),
756
+ RPS: formatReportNumber(reportDurationSeconds(reportDurationValue(scenario)) <= 0 ? 0 : requestCount / reportDurationSeconds(reportDurationValue(scenario))),
757
+ LatencyP95Ms: formatReportNumber(Math.max(asFloat(reportValue(reportObject(reportObject(scenario, "ok", "Ok"), "latency", "Latency"), "percent95", "Percent95")), asFloat(reportValue(reportObject(reportObject(scenario, "fail", "Fail"), "latency", "Latency"), "percent95", "Percent95")))),
758
+ LatencyP99Ms: formatReportNumber(Math.max(asFloat(reportValue(reportObject(reportObject(scenario, "ok", "Ok"), "latency", "Latency"), "percent99", "Percent99")), asFloat(reportValue(reportObject(reportObject(scenario, "fail", "Fail"), "latency", "Latency"), "percent99", "Percent99")))),
759
+ CurrentOperation: asString(reportValue(scenario, "currentOperation", "CurrentOperation"))
760
+ };
761
+ });
627
762
  }
628
763
  function buildDotnetStepRows(nodeStats) {
629
764
  const rows = [];
630
- for (const scenario of sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"))) {
631
- for (const step of sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"))) {
765
+ for (const scenario of reportScenarios(nodeStats)) {
766
+ for (const step of reportSteps(scenario)) {
632
767
  rows.push({
633
768
  Scenario: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
634
769
  Step: asString(reportValue(step, "stepName", "StepName")),
@@ -646,28 +781,28 @@ function buildDotnetStepRows(nodeStats) {
646
781
  }
647
782
  function buildDotnetScenarioMeasurementRows(nodeStats) {
648
783
  const rows = [];
649
- for (const scenario of sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"))) {
784
+ for (const scenario of reportScenarios(nodeStats)) {
650
785
  const scenarioName = asString(reportValue(scenario, "scenarioName", "ScenarioName"));
651
- rows.push(buildMeasurementRow("Scenario", scenarioName, "", "OK", reportObject(scenario, "ok", "Ok")));
652
- rows.push(buildMeasurementRow("Scenario", scenarioName, "", "FAIL", reportObject(scenario, "fail", "Fail")));
786
+ pushMeasurementRowIfData(rows, "Scenario", scenarioName, "", "OK", reportObject(scenario, "ok", "Ok"));
787
+ pushMeasurementRowIfData(rows, "Scenario", scenarioName, "", "FAIL", reportObject(scenario, "fail", "Fail"));
653
788
  }
654
789
  return rows;
655
790
  }
656
791
  function buildDotnetStepMeasurementRows(nodeStats) {
657
792
  const rows = [];
658
- for (const scenario of sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"))) {
793
+ for (const scenario of reportScenarios(nodeStats)) {
659
794
  const scenarioName = asString(reportValue(scenario, "scenarioName", "ScenarioName"));
660
- for (const step of sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"))) {
795
+ for (const step of reportSteps(scenario)) {
661
796
  const stepName = asString(reportValue(step, "stepName", "StepName"));
662
- rows.push(buildMeasurementRow("Step", scenarioName, stepName, "OK", reportObject(step, "ok", "Ok")));
663
- rows.push(buildMeasurementRow("Step", scenarioName, stepName, "FAIL", reportObject(step, "fail", "Fail")));
797
+ pushMeasurementRowIfData(rows, "Step", scenarioName, stepName, "OK", reportObject(step, "ok", "Ok"));
798
+ pushMeasurementRowIfData(rows, "Step", scenarioName, stepName, "FAIL", reportObject(step, "fail", "Fail"));
664
799
  }
665
800
  }
666
801
  return rows;
667
802
  }
668
803
  function buildDotnetStatusCodeRows(nodeStats) {
669
804
  const rows = [];
670
- for (const scenario of sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"))) {
805
+ for (const scenario of reportScenarios(nodeStats)) {
671
806
  const scenarioName = asString(reportValue(scenario, "scenarioName", "ScenarioName"));
672
807
  for (const code of reportArray(reportObject(scenario, "ok", "Ok"), "statusCodes", "StatusCodes")) {
673
808
  rows.push({ Scope: "Scenario", Scenario: scenarioName, Step: "", Result: "OK", StatusCode: asString(reportValue(code, "statusCode", "StatusCode")), Message: asString(reportValue(code, "message", "Message")), Count: asInt(reportValue(code, "count", "Count")), Percent: asInt(reportValue(code, "percent", "Percent")), IsError: Boolean(reportValue(code, "isError", "IsError")) });
@@ -675,7 +810,7 @@ function buildDotnetStatusCodeRows(nodeStats) {
675
810
  for (const code of reportArray(reportObject(scenario, "fail", "Fail"), "statusCodes", "StatusCodes")) {
676
811
  rows.push({ Scope: "Scenario", Scenario: scenarioName, Step: "", Result: "FAIL", StatusCode: asString(reportValue(code, "statusCode", "StatusCode")), Message: asString(reportValue(code, "message", "Message")), Count: asInt(reportValue(code, "count", "Count")), Percent: asInt(reportValue(code, "percent", "Percent")), IsError: Boolean(reportValue(code, "isError", "IsError")) });
677
812
  }
678
- for (const step of sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"))) {
813
+ for (const step of reportSteps(scenario)) {
679
814
  const stepName = asString(reportValue(step, "stepName", "StepName"));
680
815
  for (const code of reportArray(reportObject(step, "ok", "Ok"), "statusCodes", "StatusCodes")) {
681
816
  rows.push({ Scope: "Step", Scenario: scenarioName, Step: stepName, Result: "OK", StatusCode: asString(reportValue(code, "statusCode", "StatusCode")), Message: asString(reportValue(code, "message", "Message")), Count: asInt(reportValue(code, "count", "Count")), Percent: asInt(reportValue(code, "percent", "Percent")), IsError: Boolean(reportValue(code, "isError", "IsError")) });
@@ -688,7 +823,7 @@ function buildDotnetStatusCodeRows(nodeStats) {
688
823
  return rows;
689
824
  }
690
825
  function buildDotnetThresholdRows(nodeStats) {
691
- return reportArray(nodeStats, "thresholds", "Thresholds").map((threshold) => ({
826
+ return reportThresholds(nodeStats).map((threshold) => ({
692
827
  Scenario: asString(reportValue(threshold, "scenarioName", "ScenarioName")),
693
828
  Step: asString(reportValue(threshold, "stepName", "StepName")),
694
829
  Check: asString(reportValue(threshold, "checkExpression", "CheckExpression")),
@@ -698,13 +833,13 @@ function buildDotnetThresholdRows(nodeStats) {
698
833
  }));
699
834
  }
700
835
  function buildDotnetMetricRows(nodeStats) {
701
- const metrics = reportObject(nodeStats, "metrics", "Metrics");
836
+ const metrics = reportMetrics(nodeStats);
702
837
  const rows = [];
703
838
  for (const counter of reportArray(metrics, "counters", "Counters")) {
704
839
  rows.push({
705
840
  Type: "Counter",
706
841
  Scenario: asString(reportValue(counter, "scenarioName", "ScenarioName")),
707
- Name: asString(reportValue(counter, "metricName", "MetricName")),
842
+ Name: reportMetricName(counter),
708
843
  Unit: asString(reportValue(counter, "unitOfMeasure", "UnitOfMeasure")),
709
844
  Value: asInt(reportValue(counter, "value", "Value"))
710
845
  });
@@ -713,7 +848,7 @@ function buildDotnetMetricRows(nodeStats) {
713
848
  rows.push({
714
849
  Type: "Gauge",
715
850
  Scenario: asString(reportValue(gauge, "scenarioName", "ScenarioName")),
716
- Name: asString(reportValue(gauge, "metricName", "MetricName")),
851
+ Name: reportMetricName(gauge),
717
852
  Unit: asString(reportValue(gauge, "unitOfMeasure", "UnitOfMeasure")),
718
853
  Value: formatReportNumber(reportValue(gauge, "value", "Value"))
719
854
  });
@@ -738,15 +873,15 @@ function buildDotnetStatusCodeClassChart(scenarios) {
738
873
  ].filter((entry) => entry.value > 0);
739
874
  }
740
875
  function buildDotnetChartData(nodeStats) {
741
- const scenarios = sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"));
876
+ const scenarios = reportScenarios(nodeStats);
742
877
  return {
743
878
  overallOutcome: [
744
- { label: "OK", value: asInt(reportValue(nodeStats, "allOkCount", "AllOkCount")), color: "#18a957" },
745
- { label: "FAIL", value: asInt(reportValue(nodeStats, "allFailCount", "AllFailCount")), color: "#d14343" }
879
+ { label: "OK", value: reportTotalOkCount(nodeStats, scenarios), color: "#18a957" },
880
+ { label: "FAIL", value: reportTotalFailCount(nodeStats, scenarios), color: "#d14343" }
746
881
  ],
747
882
  scenarioRequests: scenarios.map((scenario) => ({
748
883
  label: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
749
- value: asInt(reportValue(scenario, "allRequestCount", "AllRequestCount")),
884
+ value: reportRequestCountValue(scenario),
750
885
  color: "#3b82f6"
751
886
  })),
752
887
  scenarioP95Latency: scenarios.map((scenario) => ({
@@ -756,21 +891,21 @@ function buildDotnetChartData(nodeStats) {
756
891
  })),
757
892
  scenarioRps: scenarios.map((scenario) => ({
758
893
  label: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
759
- value: reportDurationSeconds(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs")) <= 0
894
+ value: reportDurationSeconds(reportDurationValue(scenario)) <= 0
760
895
  ? 0
761
- : asInt(reportValue(scenario, "allRequestCount", "AllRequestCount")) / reportDurationSeconds(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs")),
896
+ : reportRequestCountValue(scenario) / reportDurationSeconds(reportDurationValue(scenario)),
762
897
  color: "#10b981"
763
898
  })),
764
899
  scenarioFailRate: scenarios.map((scenario) => ({
765
900
  label: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
766
- value: asInt(reportValue(scenario, "allRequestCount", "AllRequestCount")) <= 0
901
+ value: reportRequestCountValue(scenario) <= 0
767
902
  ? 0
768
- : (asInt(reportValue(scenario, "allFailCount", "AllFailCount")) * 100 / asInt(reportValue(scenario, "allRequestCount", "AllRequestCount"))),
903
+ : (reportFailCountValue(scenario) * 100 / reportRequestCountValue(scenario)),
769
904
  color: "#ef4444"
770
905
  })),
771
906
  scenarioBytes: scenarios.map((scenario) => ({
772
907
  label: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
773
- value: asInt(reportValue(scenario, "allBytes", "AllBytes")),
908
+ value: asInt(reportBytesValue(scenario)),
774
909
  color: "#0ea5e9"
775
910
  })),
776
911
  statusCodeClasses: buildDotnetStatusCodeClassChart(scenarios),
@@ -786,21 +921,24 @@ function buildDotnetChartData(nodeStats) {
786
921
  };
787
922
  }
788
923
  function buildDotnetSummaryHtml(nodeStats) {
789
- const scenarios = sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"));
790
- const successRate = asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount")) <= 0
924
+ const scenarios = reportScenarios(nodeStats);
925
+ const allRequests = reportTotalRequestCount(nodeStats, scenarios);
926
+ const allOk = reportTotalOkCount(nodeStats, scenarios);
927
+ const allFail = reportTotalFailCount(nodeStats, scenarios);
928
+ const successRate = allRequests <= 0
791
929
  ? 0
792
- : (asInt(reportValue(nodeStats, "allOkCount", "AllOkCount")) * 100 / asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount")));
793
- const failRate = asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount")) <= 0
930
+ : (allOk * 100 / allRequests);
931
+ const failRate = allRequests <= 0
794
932
  ? 0
795
- : (asInt(reportValue(nodeStats, "allFailCount", "AllFailCount")) * 100 / asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount")));
796
- const overallRps = reportDurationSeconds(reportValue(nodeStats, "duration", "Duration", "durationMs", "DurationMs")) <= 0
933
+ : (allFail * 100 / allRequests);
934
+ const overallRps = reportDurationSeconds(reportDurationValue(nodeStats)) <= 0
797
935
  ? 0
798
- : asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount")) / reportDurationSeconds(reportValue(nodeStats, "duration", "Duration", "durationMs", "DurationMs"));
936
+ : allRequests / reportDurationSeconds(reportDurationValue(nodeStats));
799
937
  const topScenario = scenarios.reduce((winner, scenario) => {
800
938
  if (!winner) {
801
939
  return scenario;
802
940
  }
803
- return asInt(reportValue(scenario, "allRequestCount", "AllRequestCount")) > asInt(reportValue(winner, "allRequestCount", "AllRequestCount"))
941
+ return reportRequestCountValue(scenario) > reportRequestCountValue(winner)
804
942
  ? scenario
805
943
  : winner;
806
944
  }, undefined);
@@ -811,14 +949,15 @@ function buildDotnetSummaryHtml(nodeStats) {
811
949
  const chartData = buildDotnetChartData(nodeStats);
812
950
  const testInfo = reportObject(nodeStats, "testInfo", "TestInfo");
813
951
  const nodeInfo = reportObject(nodeStats, "nodeInfo", "NodeInfo");
952
+ const totalBytes = reportTotalBytes(nodeStats, scenarios);
814
953
  const parts = [];
815
954
  appendReportLine(parts, "<div class=\"card-grid\">");
816
- appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Total Requests</div><div class="stat-value">${asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount"))}</div></div>`);
817
- appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Success</div><div class="stat-value value-ok">${asInt(reportValue(nodeStats, "allOkCount", "AllOkCount"))} (${formatDotnetPercent(successRate)}%)</div></div>`);
818
- appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Fail</div><div class="stat-value value-fail">${asInt(reportValue(nodeStats, "allFailCount", "AllFailCount"))} (${formatDotnetPercent(failRate)}%)</div></div>`);
955
+ appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Total Requests</div><div class="stat-value">${allRequests}</div></div>`);
956
+ appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Success</div><div class="stat-value value-ok">${allOk} (${formatDotnetPercent(successRate)}%)</div></div>`);
957
+ appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Fail</div><div class="stat-value value-fail">${allFail} (${formatDotnetPercent(failRate)}%)</div></div>`);
819
958
  appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Overall RPS</div><div class="stat-value">${formatReportNumber(overallRps)}</div></div>`);
820
- appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Duration</div><div class="stat-value">${escapeHtml(formatDotnetTimeSpan(reportValue(nodeStats, "duration", "Duration", "durationMs", "DurationMs")))}</div></div>`);
821
- appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Total Bytes</div><div class="stat-value">${asInt(reportValue(nodeStats, "allBytes", "AllBytes"))}</div></div>`);
959
+ appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Duration</div><div class="stat-value">${escapeHtml(formatDotnetTimeSpan(reportDurationValue(nodeStats)))}</div></div>`);
960
+ appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Total Bytes</div><div class="stat-value">${totalBytes}</div></div>`);
822
961
  appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Top Scenario</div><div class="stat-value">${escapeHtml(topScenario ? reportValue(topScenario, "scenarioName", "ScenarioName") : "n/a")}</div></div>`);
823
962
  appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Node</div><div class="stat-value">${loadStrikeNodeTypeTag(reportValue(nodeInfo, "nodeType", "NodeType"))}</div></div>`);
824
963
  appendReportLine(parts, "</div>");
@@ -893,7 +1032,7 @@ function buildDotnetStatusCodeHtml(nodeStats) {
893
1032
  return buildDotnetTableHtml(buildDotnetStatusCodeRows(nodeStats));
894
1033
  }
895
1034
  function buildDotnetFailedResponseHtml(nodeStats) {
896
- const failedStatusRows = buildFailedStatusRows(sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats")));
1035
+ const failedStatusRows = buildFailedStatusRows(reportScenarios(nodeStats));
897
1036
  const failedEventRows = buildFailedEventRows(reportArray(nodeStats, "pluginsData", "PluginsData"));
898
1037
  return buildDotnetFailedResponseContent(failedStatusRows, failedEventRows);
899
1038
  }
@@ -984,7 +1123,7 @@ function buildDotnetHtmlTabs(nodeStats) {
984
1123
  if (statusCodeRows.length) {
985
1124
  tabs.push(["status-codes", "Status Codes", buildDotnetTableHtml(statusCodeRows)]);
986
1125
  }
987
- const failedStatusRows = buildFailedStatusRows(sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats")));
1126
+ const failedStatusRows = buildFailedStatusRows(reportScenarios(nodeStats));
988
1127
  const failedEventRows = buildFailedEventRows(reportArray(nodeStats, "pluginsData", "PluginsData"));
989
1128
  if (failedStatusRows.length || failedEventRows.length) {
990
1129
  tabs.push(["failed-responses", "Failed Responses", buildDotnetFailedResponseContent(failedStatusRows, failedEventRows)]);
@@ -1042,7 +1181,7 @@ function buildDotnetHtmlTabs(nodeStats) {
1042
1181
  * Exposes the build dotnet txt report operation. Use this when interacting with the SDK through this surface.
1043
1182
  */
1044
1183
  function buildDotnetTxtReport(nodeStats) {
1045
- const scenarios = sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"));
1184
+ const scenarios = reportScenarios(nodeStats);
1046
1185
  const testInfo = reportObject(nodeStats, "testInfo", "TestInfo");
1047
1186
  const nodeInfo = reportObject(nodeStats, "nodeInfo", "NodeInfo");
1048
1187
  const lines = [
@@ -1050,25 +1189,25 @@ function buildDotnetTxtReport(nodeStats) {
1050
1189
  `TestName: ${asString(reportValue(testInfo, "testName", "TestName"))}`,
1051
1190
  `SessionId: ${asString(reportValue(testInfo, "sessionId", "SessionId"))}`,
1052
1191
  `NodeType: ${loadStrikeNodeTypeTag(reportValue(nodeInfo, "nodeType", "NodeType"))}`,
1053
- `Duration: ${formatDotnetTimeSpan(reportValue(nodeStats, "duration", "Duration", "durationMs", "DurationMs"))}`,
1054
- `Requests: ${asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount"))} OK: ${asInt(reportValue(nodeStats, "allOkCount", "AllOkCount"))} FAIL: ${asInt(reportValue(nodeStats, "allFailCount", "AllFailCount"))}`,
1192
+ `Duration: ${formatDotnetTimeSpan(reportDurationValue(nodeStats))}`,
1193
+ `Requests: ${reportTotalRequestCount(nodeStats, scenarios)} OK: ${reportTotalOkCount(nodeStats, scenarios)} FAIL: ${reportTotalFailCount(nodeStats, scenarios)}`,
1055
1194
  "",
1056
1195
  "Scenarios:"
1057
1196
  ];
1058
1197
  for (const scenario of scenarios) {
1059
- lines.push(`- ${asString(reportValue(scenario, "scenarioName", "ScenarioName"))}: req=${asInt(reportValue(scenario, "allRequestCount", "AllRequestCount"))} ok=${asInt(reportValue(scenario, "allOkCount", "AllOkCount"))} fail=${asInt(reportValue(scenario, "allFailCount", "AllFailCount"))} duration=${formatDotnetTimeSpan(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs"))}`);
1198
+ lines.push(`- ${asString(reportValue(scenario, "scenarioName", "ScenarioName"))}: req=${reportRequestCountValue(scenario)} ok=${reportOkCountValue(scenario)} fail=${reportFailCountValue(scenario)} duration=${formatDotnetTimeSpan(reportDurationValue(scenario))}`);
1060
1199
  }
1061
- if (scenarios.some((scenario) => reportArray(scenario, "stepStats", "StepStats").length > 0)) {
1200
+ if (scenarios.some((scenario) => reportSteps(scenario).length > 0)) {
1062
1201
  lines.push("", "Steps:");
1063
1202
  for (const scenario of scenarios) {
1064
- for (const step of sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"))) {
1203
+ for (const step of reportSteps(scenario)) {
1065
1204
  const ok = asInt(reportValue(reportObject(reportObject(step, "ok", "Ok"), "request", "Request"), "count", "Count"));
1066
1205
  const fail = asInt(reportValue(reportObject(reportObject(step, "fail", "Fail"), "request", "Request"), "count", "Count"));
1067
1206
  lines.push(`- ${asString(reportValue(scenario, "scenarioName", "ScenarioName"))}.${asString(reportValue(step, "stepName", "StepName"))}: req=${ok + fail} ok=${ok} fail=${fail}`);
1068
1207
  }
1069
1208
  }
1070
1209
  }
1071
- const thresholds = reportArray(nodeStats, "thresholds", "Thresholds");
1210
+ const thresholds = reportThresholds(nodeStats);
1072
1211
  if (thresholds.length) {
1073
1212
  lines.push("", "Thresholds:");
1074
1213
  for (const threshold of thresholds) {
@@ -1092,10 +1231,10 @@ function buildDotnetTxtReport(nodeStats) {
1092
1231
  */
1093
1232
  function buildDotnetCsvReport(nodeStats) {
1094
1233
  const lines = ["ScenarioName,Requests,Ok,Fail,DurationSeconds,Rps"];
1095
- for (const scenario of sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"))) {
1096
- const durationSeconds = reportDurationSeconds(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs"));
1097
- const requests = asInt(reportValue(scenario, "allRequestCount", "AllRequestCount"));
1098
- lines.push(`${escapeCsv(reportValue(scenario, "scenarioName", "ScenarioName"))},${requests},${asInt(reportValue(scenario, "allOkCount", "AllOkCount"))},${asInt(reportValue(scenario, "allFailCount", "AllFailCount"))},${formatReportNumber(durationSeconds)},${formatReportNumber(durationSeconds <= 0 ? 0 : requests / durationSeconds)}`);
1234
+ for (const scenario of reportScenarios(nodeStats)) {
1235
+ const durationSeconds = reportDurationSeconds(reportDurationValue(scenario));
1236
+ const requests = reportRequestCountValue(scenario);
1237
+ lines.push(`${escapeCsv(reportValue(scenario, "scenarioName", "ScenarioName"))},${requests},${reportOkCountValue(scenario)},${reportFailCountValue(scenario)},${formatReportNumber(durationSeconds)},${formatReportNumber(durationSeconds <= 0 ? 0 : requests / durationSeconds)}`);
1099
1238
  }
1100
1239
  return reportLines(lines);
1101
1240
  }
@@ -1103,16 +1242,16 @@ function buildDotnetCsvReport(nodeStats) {
1103
1242
  * Exposes the build dotnet markdown report operation. Use this when interacting with the SDK through this surface.
1104
1243
  */
1105
1244
  function buildDotnetMarkdownReport(nodeStats) {
1106
- const scenarios = sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"));
1245
+ const scenarios = reportScenarios(nodeStats);
1107
1246
  const testInfo = reportObject(nodeStats, "testInfo", "TestInfo");
1108
1247
  const lines = [
1109
1248
  `# ${asString(reportValue(testInfo, "testSuite", "TestSuite"))} / ${asString(reportValue(testInfo, "testName", "TestName"))}`,
1110
1249
  "",
1111
1250
  `- Session: \`${asString(reportValue(testInfo, "sessionId", "SessionId"))}\``,
1112
- `- Duration: \`${formatDotnetTimeSpan(reportValue(nodeStats, "duration", "Duration", "durationMs", "DurationMs"))}\``,
1113
- `- Total Requests: \`${asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount"))}\``,
1114
- `- OK: \`${asInt(reportValue(nodeStats, "allOkCount", "AllOkCount"))}\``,
1115
- `- FAIL: \`${asInt(reportValue(nodeStats, "allFailCount", "AllFailCount"))}\``,
1251
+ `- Duration: \`${formatDotnetTimeSpan(reportDurationValue(nodeStats))}\``,
1252
+ `- Total Requests: \`${reportTotalRequestCount(nodeStats, scenarios)}\``,
1253
+ `- OK: \`${reportTotalOkCount(nodeStats, scenarios)}\``,
1254
+ `- FAIL: \`${reportTotalFailCount(nodeStats, scenarios)}\``,
1116
1255
  "",
1117
1256
  "## Scenarios",
1118
1257
  "",
@@ -1120,19 +1259,19 @@ function buildDotnetMarkdownReport(nodeStats) {
1120
1259
  "|---|---:|---:|---:|---:|"
1121
1260
  ];
1122
1261
  for (const scenario of scenarios) {
1123
- lines.push(`| ${asString(reportValue(scenario, "scenarioName", "ScenarioName"))} | ${asInt(reportValue(scenario, "allRequestCount", "AllRequestCount"))} | ${asInt(reportValue(scenario, "allOkCount", "AllOkCount"))} | ${asInt(reportValue(scenario, "allFailCount", "AllFailCount"))} | ${formatReportNumber(reportDurationSeconds(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs")))}s |`);
1262
+ lines.push(`| ${asString(reportValue(scenario, "scenarioName", "ScenarioName"))} | ${reportRequestCountValue(scenario)} | ${reportOkCountValue(scenario)} | ${reportFailCountValue(scenario)} | ${formatReportNumber(reportDurationSeconds(reportDurationValue(scenario)))}s |`);
1124
1263
  }
1125
- if (scenarios.some((scenario) => reportArray(scenario, "stepStats", "StepStats").length > 0)) {
1264
+ if (scenarios.some((scenario) => reportSteps(scenario).length > 0)) {
1126
1265
  lines.push("", "## Steps", "", "| Scenario | Step | Requests | OK | FAIL |", "|---|---|---:|---:|---:|");
1127
1266
  for (const scenario of scenarios) {
1128
- for (const step of sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"))) {
1267
+ for (const step of reportSteps(scenario)) {
1129
1268
  const ok = asInt(reportValue(reportObject(reportObject(step, "ok", "Ok"), "request", "Request"), "count", "Count"));
1130
1269
  const fail = asInt(reportValue(reportObject(reportObject(step, "fail", "Fail"), "request", "Request"), "count", "Count"));
1131
1270
  lines.push(`| ${asString(reportValue(scenario, "scenarioName", "ScenarioName"))} | ${asString(reportValue(step, "stepName", "StepName"))} | ${ok + fail} | ${ok} | ${fail} |`);
1132
1271
  }
1133
1272
  }
1134
1273
  }
1135
- const thresholds = reportArray(nodeStats, "thresholds", "Thresholds");
1274
+ const thresholds = reportThresholds(nodeStats);
1136
1275
  if (thresholds.length) {
1137
1276
  lines.push("", "## Thresholds", "", "| Scenario | Step | Check | Failed | Errors | Exception |", "|---|---|---|---:|---:|---|");
1138
1277
  for (const threshold of thresholds) {
@@ -1268,7 +1407,7 @@ if(btns.length>0){show(btns[0].dataset.tab);}renderAllCharts();initPanePan();win
1268
1407
  .split("__TEST_SUITE__").join(escapeHtml(reportValue(testInfo, "testSuite", "TestSuite")))
1269
1408
  .split("__TEST_NAME__").join(escapeHtml(reportValue(testInfo, "testName", "TestName")))
1270
1409
  .split("__SESSION_ID__").join(escapeHtml(reportValue(testInfo, "sessionId", "SessionId")))
1271
- .split("__DURATION__").join(escapeHtml(formatDotnetTimeSpan(reportValue(nodeStats, "duration", "Duration", "durationMs", "DurationMs"))))
1410
+ .split("__DURATION__").join(escapeHtml(formatDotnetTimeSpan(reportDurationValue(nodeStats))))
1272
1411
  .split("__BUTTONS__").join(buttonsHtml)
1273
1412
  .split("__SECTIONS__").join(sectionsHtml)
1274
1413
  .split("__CHART_DATA__").join(chartDataJson)
@@ -8034,7 +8034,7 @@ function buildRichHtmlReport(result) {
8034
8034
  <section id="scenarios" class="panel active">
8035
8035
  ${bars || "<div>No scenario data.</div>"}
8036
8036
  <table>
8037
- <thead><tr><th>Scenario</th><th>Requests</th><th>OK</th><th>Fail</th><th>TotalBytes</th><th>AvgLatencyMs</th></tr></thead>
8037
+ <thead><tr><th>Scenario</th><th>Requests</th><th>OK</th><th>Fail</th><th>TotalBytes</th><th>AvgLatency (ms)</th></tr></thead>
8038
8038
  <tbody>${scenarioRows || "<tr><td colspan='6'>No rows</td></tr>"}</tbody>
8039
8039
  </table>
8040
8040
  </section>
@@ -44,6 +44,61 @@ function reportValue(source, ...keys) {
44
44
  }
45
45
  return undefined;
46
46
  }
47
+ function reportDurationValue(source) {
48
+ return reportValue(source, "durationMs", "DurationMs", "duration", "Duration");
49
+ }
50
+ function reportBytesValue(source) {
51
+ return reportValue(source, "allBytes", "AllBytes", "totalBytes", "TotalBytes");
52
+ }
53
+ function reportTotalBytes(nodeStats, scenarios) {
54
+ const totalBytes = asInt(reportBytesValue(nodeStats));
55
+ if (totalBytes !== 0) {
56
+ return totalBytes;
57
+ }
58
+ return scenarios.reduce((sum, scenario) => sum + asInt(reportBytesValue(scenario)), 0);
59
+ }
60
+ function reportRequestCountValue(source) {
61
+ return asInt(reportValue(source, "allRequestCount", "AllRequestCount", "requestCount", "RequestCount"));
62
+ }
63
+ function reportOkCountValue(source) {
64
+ return asInt(reportValue(source, "allOkCount", "AllOkCount", "okCount", "OkCount"));
65
+ }
66
+ function reportFailCountValue(source) {
67
+ return asInt(reportValue(source, "allFailCount", "AllFailCount", "failCount", "FailCount"));
68
+ }
69
+ function reportTotalRequestCount(nodeStats, scenarios) {
70
+ const total = reportRequestCountValue(nodeStats);
71
+ return total !== 0 || scenarios.length === 0
72
+ ? total
73
+ : scenarios.reduce((sum, scenario) => sum + reportRequestCountValue(scenario), 0);
74
+ }
75
+ function reportTotalOkCount(nodeStats, scenarios) {
76
+ const total = reportOkCountValue(nodeStats);
77
+ return total !== 0 || scenarios.length === 0
78
+ ? total
79
+ : scenarios.reduce((sum, scenario) => sum + reportOkCountValue(scenario), 0);
80
+ }
81
+ function reportTotalFailCount(nodeStats, scenarios) {
82
+ const total = reportFailCountValue(nodeStats);
83
+ return total !== 0 || scenarios.length === 0
84
+ ? total
85
+ : scenarios.reduce((sum, scenario) => sum + reportFailCountValue(scenario), 0);
86
+ }
87
+ function reportScenarios(nodeStats) {
88
+ return sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"));
89
+ }
90
+ function reportSteps(scenario) {
91
+ return sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"));
92
+ }
93
+ function reportThresholds(nodeStats) {
94
+ return reportArray(nodeStats, "thresholds", "Thresholds", "thresholdResults", "ThresholdResults");
95
+ }
96
+ function reportMetrics(nodeStats) {
97
+ return reportObject(nodeStats, "metrics", "Metrics", "metricStats", "MetricStats");
98
+ }
99
+ function reportMetricName(source) {
100
+ return asString(reportValue(source, "metricName", "MetricName", "name", "Name"));
101
+ }
47
102
  function reportObject(source, ...keys) {
48
103
  const value = reportValue(source, ...keys);
49
104
  return value && typeof value === "object" && !Array.isArray(value)
@@ -77,6 +132,16 @@ function asFloat(value) {
77
132
  const parsed = Number.parseFloat(asString(value));
78
133
  return Number.isFinite(parsed) ? parsed : 0;
79
134
  }
135
+ function asBool(value) {
136
+ if (typeof value === "boolean") {
137
+ return value;
138
+ }
139
+ if (typeof value === "number" && Number.isFinite(value)) {
140
+ return value !== 0;
141
+ }
142
+ const normalized = asString(value).trim().toLowerCase();
143
+ return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "y" || normalized === "on";
144
+ }
80
145
  function formatCellValue(value) {
81
146
  if (value == null) {
82
147
  return "";
@@ -296,7 +361,7 @@ function buildDotnetTableHtml(rows, wrapInCard = true) {
296
361
  appendReportLine(parts, "<table>");
297
362
  appendReportLine(parts, "<thead><tr>");
298
363
  for (const header of headers) {
299
- appendReportLine(parts, `<th>${escapeHtml(header)}</th>`);
364
+ appendReportLine(parts, `<th>${escapeHtml(formatReportTableHeader(header))}</th>`);
300
365
  }
301
366
  appendReportLine(parts, "</tr></thead><tbody>");
302
367
  for (const row of rows) {
@@ -312,6 +377,14 @@ function buildDotnetTableHtml(rows, wrapInCard = true) {
312
377
  }
313
378
  return parts.join("");
314
379
  }
380
+ function formatReportTableHeader(header) {
381
+ if (header === "LatencyStdDev") {
382
+ return "LatencyStdDev (ms)";
383
+ }
384
+ return header.endsWith("Ms") && header.length > "Ms".length
385
+ ? `${header.slice(0, -"Ms".length)} (ms)`
386
+ : header;
387
+ }
315
388
  function buildFailedStatusRows(scenarios) {
316
389
  const rows = [];
317
390
  for (const scenario of scenarios) {
@@ -334,7 +407,7 @@ function buildFailedStatusRows(scenarios) {
334
407
  IsError: isError
335
408
  });
336
409
  }
337
- for (const step of sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"))) {
410
+ for (const step of reportSteps(scenario)) {
338
411
  const stepName = asString(reportValue(step, "stepName", "StepName"));
339
412
  const stepFail = reportObject(step, "fail", "Fail");
340
413
  for (const code of reportArray(stepFail, "statusCodes", "StatusCodes")) {
@@ -586,6 +659,65 @@ function buildMeasurementRow(scope, scenarioName, stepName, resultName, measurem
586
659
  BytesStdDev: formatReportNumber(reportValue(dataTransfer, "stdDev", "StdDev"))
587
660
  };
588
661
  }
662
+ function pushMeasurementRowIfData(rows, scope, scenarioName, stepName, resultName, measurement) {
663
+ if (hasMeasurementData(measurement)) {
664
+ rows.push(buildMeasurementRow(scope, scenarioName, stepName, resultName, measurement));
665
+ }
666
+ }
667
+ function hasMeasurementData(measurement) {
668
+ if (Object.keys(measurement).length === 0) {
669
+ return false;
670
+ }
671
+ const request = reportObject(measurement, "request", "Request");
672
+ if (asInt(reportValue(request, "count", "Count")) !== 0 ||
673
+ asInt(reportValue(request, "percent", "Percent")) !== 0 ||
674
+ asFloat(reportValue(request, "rps", "RPS")) !== 0) {
675
+ return true;
676
+ }
677
+ const latency = reportObject(measurement, "latency", "Latency");
678
+ const latencyKeys = [
679
+ ["minMs", "MinMs"],
680
+ ["meanMs", "MeanMs"],
681
+ ["percent50", "Percent50"],
682
+ ["percent75", "Percent75"],
683
+ ["percent95", "Percent95"],
684
+ ["percent99", "Percent99"],
685
+ ["maxMs", "MaxMs"],
686
+ ["stdDev", "StdDev"]
687
+ ];
688
+ if (latencyKeys.some(([camelKey, pascalKey]) => asFloat(reportValue(latency, camelKey, pascalKey)) !== 0)) {
689
+ return true;
690
+ }
691
+ const latencyCount = reportObject(latency, "latencyCount", "LatencyCount");
692
+ const latencyCountKeys = [
693
+ ["lessOrEq800", "LessOrEq800"],
694
+ ["more800Less1200", "More800Less1200"],
695
+ ["moreOrEq1200", "MoreOrEq1200"]
696
+ ];
697
+ if (latencyCountKeys.some(([camelKey, pascalKey]) => asInt(reportValue(latencyCount, camelKey, pascalKey)) !== 0)) {
698
+ return true;
699
+ }
700
+ const dataTransfer = reportObject(measurement, "dataTransfer", "DataTransfer");
701
+ const dataTransferKeys = [
702
+ ["allBytes", "AllBytes"],
703
+ ["minBytes", "MinBytes"],
704
+ ["meanBytes", "MeanBytes"],
705
+ ["percent50", "Percent50"],
706
+ ["percent75", "Percent75"],
707
+ ["percent95", "Percent95"],
708
+ ["percent99", "Percent99"],
709
+ ["maxBytes", "MaxBytes"],
710
+ ["stdDev", "StdDev"]
711
+ ];
712
+ if (dataTransferKeys.some(([camelKey, pascalKey]) => asFloat(reportValue(dataTransfer, camelKey, pascalKey)) !== 0)) {
713
+ return true;
714
+ }
715
+ return reportArray(measurement, "statusCodes", "StatusCodes").some((code) => asInt(reportValue(code, "count", "Count")) !== 0 ||
716
+ asInt(reportValue(code, "percent", "Percent")) !== 0 ||
717
+ asBool(reportValue(code, "isError", "IsError")) ||
718
+ asString(reportValue(code, "statusCode", "StatusCode")).trim().length > 0 ||
719
+ asString(reportValue(code, "message", "Message")).trim().length > 0);
720
+ }
589
721
  function appendChartCard(parts, id, title) {
590
722
  appendReportLine(parts, `<div class="chart-card"><h3>${escapeHtml(title)}</h3><canvas id="${escapeHtml(id)}" class="chart-canvas"></canvas></div>`);
591
723
  }
@@ -604,24 +736,27 @@ function hasNonEmptyHints(plugin) {
604
736
  return reportArray(plugin, "hints", "Hints").some((hint) => asString(hint).trim().length > 0);
605
737
  }
606
738
  function buildDotnetScenarioRows(nodeStats) {
607
- return sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats")).map((scenario) => ({
608
- Scenario: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
609
- Simulation: asString(reportValue(reportObject(scenario, "loadSimulationStats", "LoadSimulationStats"), "simulationName", "SimulationName")),
610
- SimulationValue: asInt(reportValue(reportObject(scenario, "loadSimulationStats", "LoadSimulationStats"), "value", "Value")),
611
- Requests: asInt(reportValue(scenario, "allRequestCount", "AllRequestCount")),
612
- OK: asInt(reportValue(scenario, "allOkCount", "AllOkCount")),
613
- FAIL: asInt(reportValue(scenario, "allFailCount", "AllFailCount")),
614
- Duration: formatDotnetTimeSpan(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs")),
615
- RPS: formatReportNumber(reportDurationSeconds(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs")) <= 0 ? 0 : asInt(reportValue(scenario, "allRequestCount", "AllRequestCount")) / reportDurationSeconds(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs"))),
616
- LatencyP95Ms: formatReportNumber(Math.max(asFloat(reportValue(reportObject(reportObject(scenario, "ok", "Ok"), "latency", "Latency"), "percent95", "Percent95")), asFloat(reportValue(reportObject(reportObject(scenario, "fail", "Fail"), "latency", "Latency"), "percent95", "Percent95")))),
617
- LatencyP99Ms: formatReportNumber(Math.max(asFloat(reportValue(reportObject(reportObject(scenario, "ok", "Ok"), "latency", "Latency"), "percent99", "Percent99")), asFloat(reportValue(reportObject(reportObject(scenario, "fail", "Fail"), "latency", "Latency"), "percent99", "Percent99")))),
618
- CurrentOperation: asString(reportValue(scenario, "currentOperation", "CurrentOperation"))
619
- }));
739
+ return reportScenarios(nodeStats).map((scenario) => {
740
+ const requestCount = reportRequestCountValue(scenario);
741
+ return {
742
+ Scenario: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
743
+ Simulation: asString(reportValue(reportObject(scenario, "loadSimulationStats", "LoadSimulationStats"), "simulationName", "SimulationName")),
744
+ SimulationValue: asInt(reportValue(reportObject(scenario, "loadSimulationStats", "LoadSimulationStats"), "value", "Value")),
745
+ Requests: requestCount,
746
+ OK: reportOkCountValue(scenario),
747
+ FAIL: reportFailCountValue(scenario),
748
+ Duration: formatDotnetTimeSpan(reportDurationValue(scenario)),
749
+ RPS: formatReportNumber(reportDurationSeconds(reportDurationValue(scenario)) <= 0 ? 0 : requestCount / reportDurationSeconds(reportDurationValue(scenario))),
750
+ LatencyP95Ms: formatReportNumber(Math.max(asFloat(reportValue(reportObject(reportObject(scenario, "ok", "Ok"), "latency", "Latency"), "percent95", "Percent95")), asFloat(reportValue(reportObject(reportObject(scenario, "fail", "Fail"), "latency", "Latency"), "percent95", "Percent95")))),
751
+ LatencyP99Ms: formatReportNumber(Math.max(asFloat(reportValue(reportObject(reportObject(scenario, "ok", "Ok"), "latency", "Latency"), "percent99", "Percent99")), asFloat(reportValue(reportObject(reportObject(scenario, "fail", "Fail"), "latency", "Latency"), "percent99", "Percent99")))),
752
+ CurrentOperation: asString(reportValue(scenario, "currentOperation", "CurrentOperation"))
753
+ };
754
+ });
620
755
  }
621
756
  function buildDotnetStepRows(nodeStats) {
622
757
  const rows = [];
623
- for (const scenario of sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"))) {
624
- for (const step of sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"))) {
758
+ for (const scenario of reportScenarios(nodeStats)) {
759
+ for (const step of reportSteps(scenario)) {
625
760
  rows.push({
626
761
  Scenario: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
627
762
  Step: asString(reportValue(step, "stepName", "StepName")),
@@ -639,28 +774,28 @@ function buildDotnetStepRows(nodeStats) {
639
774
  }
640
775
  function buildDotnetScenarioMeasurementRows(nodeStats) {
641
776
  const rows = [];
642
- for (const scenario of sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"))) {
777
+ for (const scenario of reportScenarios(nodeStats)) {
643
778
  const scenarioName = asString(reportValue(scenario, "scenarioName", "ScenarioName"));
644
- rows.push(buildMeasurementRow("Scenario", scenarioName, "", "OK", reportObject(scenario, "ok", "Ok")));
645
- rows.push(buildMeasurementRow("Scenario", scenarioName, "", "FAIL", reportObject(scenario, "fail", "Fail")));
779
+ pushMeasurementRowIfData(rows, "Scenario", scenarioName, "", "OK", reportObject(scenario, "ok", "Ok"));
780
+ pushMeasurementRowIfData(rows, "Scenario", scenarioName, "", "FAIL", reportObject(scenario, "fail", "Fail"));
646
781
  }
647
782
  return rows;
648
783
  }
649
784
  function buildDotnetStepMeasurementRows(nodeStats) {
650
785
  const rows = [];
651
- for (const scenario of sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"))) {
786
+ for (const scenario of reportScenarios(nodeStats)) {
652
787
  const scenarioName = asString(reportValue(scenario, "scenarioName", "ScenarioName"));
653
- for (const step of sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"))) {
788
+ for (const step of reportSteps(scenario)) {
654
789
  const stepName = asString(reportValue(step, "stepName", "StepName"));
655
- rows.push(buildMeasurementRow("Step", scenarioName, stepName, "OK", reportObject(step, "ok", "Ok")));
656
- rows.push(buildMeasurementRow("Step", scenarioName, stepName, "FAIL", reportObject(step, "fail", "Fail")));
790
+ pushMeasurementRowIfData(rows, "Step", scenarioName, stepName, "OK", reportObject(step, "ok", "Ok"));
791
+ pushMeasurementRowIfData(rows, "Step", scenarioName, stepName, "FAIL", reportObject(step, "fail", "Fail"));
657
792
  }
658
793
  }
659
794
  return rows;
660
795
  }
661
796
  function buildDotnetStatusCodeRows(nodeStats) {
662
797
  const rows = [];
663
- for (const scenario of sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"))) {
798
+ for (const scenario of reportScenarios(nodeStats)) {
664
799
  const scenarioName = asString(reportValue(scenario, "scenarioName", "ScenarioName"));
665
800
  for (const code of reportArray(reportObject(scenario, "ok", "Ok"), "statusCodes", "StatusCodes")) {
666
801
  rows.push({ Scope: "Scenario", Scenario: scenarioName, Step: "", Result: "OK", StatusCode: asString(reportValue(code, "statusCode", "StatusCode")), Message: asString(reportValue(code, "message", "Message")), Count: asInt(reportValue(code, "count", "Count")), Percent: asInt(reportValue(code, "percent", "Percent")), IsError: Boolean(reportValue(code, "isError", "IsError")) });
@@ -668,7 +803,7 @@ function buildDotnetStatusCodeRows(nodeStats) {
668
803
  for (const code of reportArray(reportObject(scenario, "fail", "Fail"), "statusCodes", "StatusCodes")) {
669
804
  rows.push({ Scope: "Scenario", Scenario: scenarioName, Step: "", Result: "FAIL", StatusCode: asString(reportValue(code, "statusCode", "StatusCode")), Message: asString(reportValue(code, "message", "Message")), Count: asInt(reportValue(code, "count", "Count")), Percent: asInt(reportValue(code, "percent", "Percent")), IsError: Boolean(reportValue(code, "isError", "IsError")) });
670
805
  }
671
- for (const step of sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"))) {
806
+ for (const step of reportSteps(scenario)) {
672
807
  const stepName = asString(reportValue(step, "stepName", "StepName"));
673
808
  for (const code of reportArray(reportObject(step, "ok", "Ok"), "statusCodes", "StatusCodes")) {
674
809
  rows.push({ Scope: "Step", Scenario: scenarioName, Step: stepName, Result: "OK", StatusCode: asString(reportValue(code, "statusCode", "StatusCode")), Message: asString(reportValue(code, "message", "Message")), Count: asInt(reportValue(code, "count", "Count")), Percent: asInt(reportValue(code, "percent", "Percent")), IsError: Boolean(reportValue(code, "isError", "IsError")) });
@@ -681,7 +816,7 @@ function buildDotnetStatusCodeRows(nodeStats) {
681
816
  return rows;
682
817
  }
683
818
  function buildDotnetThresholdRows(nodeStats) {
684
- return reportArray(nodeStats, "thresholds", "Thresholds").map((threshold) => ({
819
+ return reportThresholds(nodeStats).map((threshold) => ({
685
820
  Scenario: asString(reportValue(threshold, "scenarioName", "ScenarioName")),
686
821
  Step: asString(reportValue(threshold, "stepName", "StepName")),
687
822
  Check: asString(reportValue(threshold, "checkExpression", "CheckExpression")),
@@ -691,13 +826,13 @@ function buildDotnetThresholdRows(nodeStats) {
691
826
  }));
692
827
  }
693
828
  function buildDotnetMetricRows(nodeStats) {
694
- const metrics = reportObject(nodeStats, "metrics", "Metrics");
829
+ const metrics = reportMetrics(nodeStats);
695
830
  const rows = [];
696
831
  for (const counter of reportArray(metrics, "counters", "Counters")) {
697
832
  rows.push({
698
833
  Type: "Counter",
699
834
  Scenario: asString(reportValue(counter, "scenarioName", "ScenarioName")),
700
- Name: asString(reportValue(counter, "metricName", "MetricName")),
835
+ Name: reportMetricName(counter),
701
836
  Unit: asString(reportValue(counter, "unitOfMeasure", "UnitOfMeasure")),
702
837
  Value: asInt(reportValue(counter, "value", "Value"))
703
838
  });
@@ -706,7 +841,7 @@ function buildDotnetMetricRows(nodeStats) {
706
841
  rows.push({
707
842
  Type: "Gauge",
708
843
  Scenario: asString(reportValue(gauge, "scenarioName", "ScenarioName")),
709
- Name: asString(reportValue(gauge, "metricName", "MetricName")),
844
+ Name: reportMetricName(gauge),
710
845
  Unit: asString(reportValue(gauge, "unitOfMeasure", "UnitOfMeasure")),
711
846
  Value: formatReportNumber(reportValue(gauge, "value", "Value"))
712
847
  });
@@ -731,15 +866,15 @@ function buildDotnetStatusCodeClassChart(scenarios) {
731
866
  ].filter((entry) => entry.value > 0);
732
867
  }
733
868
  function buildDotnetChartData(nodeStats) {
734
- const scenarios = sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"));
869
+ const scenarios = reportScenarios(nodeStats);
735
870
  return {
736
871
  overallOutcome: [
737
- { label: "OK", value: asInt(reportValue(nodeStats, "allOkCount", "AllOkCount")), color: "#18a957" },
738
- { label: "FAIL", value: asInt(reportValue(nodeStats, "allFailCount", "AllFailCount")), color: "#d14343" }
872
+ { label: "OK", value: reportTotalOkCount(nodeStats, scenarios), color: "#18a957" },
873
+ { label: "FAIL", value: reportTotalFailCount(nodeStats, scenarios), color: "#d14343" }
739
874
  ],
740
875
  scenarioRequests: scenarios.map((scenario) => ({
741
876
  label: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
742
- value: asInt(reportValue(scenario, "allRequestCount", "AllRequestCount")),
877
+ value: reportRequestCountValue(scenario),
743
878
  color: "#3b82f6"
744
879
  })),
745
880
  scenarioP95Latency: scenarios.map((scenario) => ({
@@ -749,21 +884,21 @@ function buildDotnetChartData(nodeStats) {
749
884
  })),
750
885
  scenarioRps: scenarios.map((scenario) => ({
751
886
  label: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
752
- value: reportDurationSeconds(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs")) <= 0
887
+ value: reportDurationSeconds(reportDurationValue(scenario)) <= 0
753
888
  ? 0
754
- : asInt(reportValue(scenario, "allRequestCount", "AllRequestCount")) / reportDurationSeconds(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs")),
889
+ : reportRequestCountValue(scenario) / reportDurationSeconds(reportDurationValue(scenario)),
755
890
  color: "#10b981"
756
891
  })),
757
892
  scenarioFailRate: scenarios.map((scenario) => ({
758
893
  label: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
759
- value: asInt(reportValue(scenario, "allRequestCount", "AllRequestCount")) <= 0
894
+ value: reportRequestCountValue(scenario) <= 0
760
895
  ? 0
761
- : (asInt(reportValue(scenario, "allFailCount", "AllFailCount")) * 100 / asInt(reportValue(scenario, "allRequestCount", "AllRequestCount"))),
896
+ : (reportFailCountValue(scenario) * 100 / reportRequestCountValue(scenario)),
762
897
  color: "#ef4444"
763
898
  })),
764
899
  scenarioBytes: scenarios.map((scenario) => ({
765
900
  label: asString(reportValue(scenario, "scenarioName", "ScenarioName")),
766
- value: asInt(reportValue(scenario, "allBytes", "AllBytes")),
901
+ value: asInt(reportBytesValue(scenario)),
767
902
  color: "#0ea5e9"
768
903
  })),
769
904
  statusCodeClasses: buildDotnetStatusCodeClassChart(scenarios),
@@ -779,21 +914,24 @@ function buildDotnetChartData(nodeStats) {
779
914
  };
780
915
  }
781
916
  function buildDotnetSummaryHtml(nodeStats) {
782
- const scenarios = sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"));
783
- const successRate = asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount")) <= 0
917
+ const scenarios = reportScenarios(nodeStats);
918
+ const allRequests = reportTotalRequestCount(nodeStats, scenarios);
919
+ const allOk = reportTotalOkCount(nodeStats, scenarios);
920
+ const allFail = reportTotalFailCount(nodeStats, scenarios);
921
+ const successRate = allRequests <= 0
784
922
  ? 0
785
- : (asInt(reportValue(nodeStats, "allOkCount", "AllOkCount")) * 100 / asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount")));
786
- const failRate = asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount")) <= 0
923
+ : (allOk * 100 / allRequests);
924
+ const failRate = allRequests <= 0
787
925
  ? 0
788
- : (asInt(reportValue(nodeStats, "allFailCount", "AllFailCount")) * 100 / asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount")));
789
- const overallRps = reportDurationSeconds(reportValue(nodeStats, "duration", "Duration", "durationMs", "DurationMs")) <= 0
926
+ : (allFail * 100 / allRequests);
927
+ const overallRps = reportDurationSeconds(reportDurationValue(nodeStats)) <= 0
790
928
  ? 0
791
- : asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount")) / reportDurationSeconds(reportValue(nodeStats, "duration", "Duration", "durationMs", "DurationMs"));
929
+ : allRequests / reportDurationSeconds(reportDurationValue(nodeStats));
792
930
  const topScenario = scenarios.reduce((winner, scenario) => {
793
931
  if (!winner) {
794
932
  return scenario;
795
933
  }
796
- return asInt(reportValue(scenario, "allRequestCount", "AllRequestCount")) > asInt(reportValue(winner, "allRequestCount", "AllRequestCount"))
934
+ return reportRequestCountValue(scenario) > reportRequestCountValue(winner)
797
935
  ? scenario
798
936
  : winner;
799
937
  }, undefined);
@@ -804,14 +942,15 @@ function buildDotnetSummaryHtml(nodeStats) {
804
942
  const chartData = buildDotnetChartData(nodeStats);
805
943
  const testInfo = reportObject(nodeStats, "testInfo", "TestInfo");
806
944
  const nodeInfo = reportObject(nodeStats, "nodeInfo", "NodeInfo");
945
+ const totalBytes = reportTotalBytes(nodeStats, scenarios);
807
946
  const parts = [];
808
947
  appendReportLine(parts, "<div class=\"card-grid\">");
809
- appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Total Requests</div><div class="stat-value">${asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount"))}</div></div>`);
810
- appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Success</div><div class="stat-value value-ok">${asInt(reportValue(nodeStats, "allOkCount", "AllOkCount"))} (${formatDotnetPercent(successRate)}%)</div></div>`);
811
- appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Fail</div><div class="stat-value value-fail">${asInt(reportValue(nodeStats, "allFailCount", "AllFailCount"))} (${formatDotnetPercent(failRate)}%)</div></div>`);
948
+ appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Total Requests</div><div class="stat-value">${allRequests}</div></div>`);
949
+ appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Success</div><div class="stat-value value-ok">${allOk} (${formatDotnetPercent(successRate)}%)</div></div>`);
950
+ appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Fail</div><div class="stat-value value-fail">${allFail} (${formatDotnetPercent(failRate)}%)</div></div>`);
812
951
  appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Overall RPS</div><div class="stat-value">${formatReportNumber(overallRps)}</div></div>`);
813
- appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Duration</div><div class="stat-value">${escapeHtml(formatDotnetTimeSpan(reportValue(nodeStats, "duration", "Duration", "durationMs", "DurationMs")))}</div></div>`);
814
- appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Total Bytes</div><div class="stat-value">${asInt(reportValue(nodeStats, "allBytes", "AllBytes"))}</div></div>`);
952
+ appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Duration</div><div class="stat-value">${escapeHtml(formatDotnetTimeSpan(reportDurationValue(nodeStats)))}</div></div>`);
953
+ appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Total Bytes</div><div class="stat-value">${totalBytes}</div></div>`);
815
954
  appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Top Scenario</div><div class="stat-value">${escapeHtml(topScenario ? reportValue(topScenario, "scenarioName", "ScenarioName") : "n/a")}</div></div>`);
816
955
  appendReportLine(parts, `<div class="stat-card"><div class="stat-label">Node</div><div class="stat-value">${loadStrikeNodeTypeTag(reportValue(nodeInfo, "nodeType", "NodeType"))}</div></div>`);
817
956
  appendReportLine(parts, "</div>");
@@ -886,7 +1025,7 @@ function buildDotnetStatusCodeHtml(nodeStats) {
886
1025
  return buildDotnetTableHtml(buildDotnetStatusCodeRows(nodeStats));
887
1026
  }
888
1027
  function buildDotnetFailedResponseHtml(nodeStats) {
889
- const failedStatusRows = buildFailedStatusRows(sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats")));
1028
+ const failedStatusRows = buildFailedStatusRows(reportScenarios(nodeStats));
890
1029
  const failedEventRows = buildFailedEventRows(reportArray(nodeStats, "pluginsData", "PluginsData"));
891
1030
  return buildDotnetFailedResponseContent(failedStatusRows, failedEventRows);
892
1031
  }
@@ -977,7 +1116,7 @@ function buildDotnetHtmlTabs(nodeStats) {
977
1116
  if (statusCodeRows.length) {
978
1117
  tabs.push(["status-codes", "Status Codes", buildDotnetTableHtml(statusCodeRows)]);
979
1118
  }
980
- const failedStatusRows = buildFailedStatusRows(sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats")));
1119
+ const failedStatusRows = buildFailedStatusRows(reportScenarios(nodeStats));
981
1120
  const failedEventRows = buildFailedEventRows(reportArray(nodeStats, "pluginsData", "PluginsData"));
982
1121
  if (failedStatusRows.length || failedEventRows.length) {
983
1122
  tabs.push(["failed-responses", "Failed Responses", buildDotnetFailedResponseContent(failedStatusRows, failedEventRows)]);
@@ -1035,7 +1174,7 @@ function buildDotnetHtmlTabs(nodeStats) {
1035
1174
  * Exposes the build dotnet txt report operation. Use this when interacting with the SDK through this surface.
1036
1175
  */
1037
1176
  export function buildDotnetTxtReport(nodeStats) {
1038
- const scenarios = sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"));
1177
+ const scenarios = reportScenarios(nodeStats);
1039
1178
  const testInfo = reportObject(nodeStats, "testInfo", "TestInfo");
1040
1179
  const nodeInfo = reportObject(nodeStats, "nodeInfo", "NodeInfo");
1041
1180
  const lines = [
@@ -1043,25 +1182,25 @@ export function buildDotnetTxtReport(nodeStats) {
1043
1182
  `TestName: ${asString(reportValue(testInfo, "testName", "TestName"))}`,
1044
1183
  `SessionId: ${asString(reportValue(testInfo, "sessionId", "SessionId"))}`,
1045
1184
  `NodeType: ${loadStrikeNodeTypeTag(reportValue(nodeInfo, "nodeType", "NodeType"))}`,
1046
- `Duration: ${formatDotnetTimeSpan(reportValue(nodeStats, "duration", "Duration", "durationMs", "DurationMs"))}`,
1047
- `Requests: ${asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount"))} OK: ${asInt(reportValue(nodeStats, "allOkCount", "AllOkCount"))} FAIL: ${asInt(reportValue(nodeStats, "allFailCount", "AllFailCount"))}`,
1185
+ `Duration: ${formatDotnetTimeSpan(reportDurationValue(nodeStats))}`,
1186
+ `Requests: ${reportTotalRequestCount(nodeStats, scenarios)} OK: ${reportTotalOkCount(nodeStats, scenarios)} FAIL: ${reportTotalFailCount(nodeStats, scenarios)}`,
1048
1187
  "",
1049
1188
  "Scenarios:"
1050
1189
  ];
1051
1190
  for (const scenario of scenarios) {
1052
- lines.push(`- ${asString(reportValue(scenario, "scenarioName", "ScenarioName"))}: req=${asInt(reportValue(scenario, "allRequestCount", "AllRequestCount"))} ok=${asInt(reportValue(scenario, "allOkCount", "AllOkCount"))} fail=${asInt(reportValue(scenario, "allFailCount", "AllFailCount"))} duration=${formatDotnetTimeSpan(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs"))}`);
1191
+ lines.push(`- ${asString(reportValue(scenario, "scenarioName", "ScenarioName"))}: req=${reportRequestCountValue(scenario)} ok=${reportOkCountValue(scenario)} fail=${reportFailCountValue(scenario)} duration=${formatDotnetTimeSpan(reportDurationValue(scenario))}`);
1053
1192
  }
1054
- if (scenarios.some((scenario) => reportArray(scenario, "stepStats", "StepStats").length > 0)) {
1193
+ if (scenarios.some((scenario) => reportSteps(scenario).length > 0)) {
1055
1194
  lines.push("", "Steps:");
1056
1195
  for (const scenario of scenarios) {
1057
- for (const step of sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"))) {
1196
+ for (const step of reportSteps(scenario)) {
1058
1197
  const ok = asInt(reportValue(reportObject(reportObject(step, "ok", "Ok"), "request", "Request"), "count", "Count"));
1059
1198
  const fail = asInt(reportValue(reportObject(reportObject(step, "fail", "Fail"), "request", "Request"), "count", "Count"));
1060
1199
  lines.push(`- ${asString(reportValue(scenario, "scenarioName", "ScenarioName"))}.${asString(reportValue(step, "stepName", "StepName"))}: req=${ok + fail} ok=${ok} fail=${fail}`);
1061
1200
  }
1062
1201
  }
1063
1202
  }
1064
- const thresholds = reportArray(nodeStats, "thresholds", "Thresholds");
1203
+ const thresholds = reportThresholds(nodeStats);
1065
1204
  if (thresholds.length) {
1066
1205
  lines.push("", "Thresholds:");
1067
1206
  for (const threshold of thresholds) {
@@ -1085,10 +1224,10 @@ export function buildDotnetTxtReport(nodeStats) {
1085
1224
  */
1086
1225
  export function buildDotnetCsvReport(nodeStats) {
1087
1226
  const lines = ["ScenarioName,Requests,Ok,Fail,DurationSeconds,Rps"];
1088
- for (const scenario of sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"))) {
1089
- const durationSeconds = reportDurationSeconds(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs"));
1090
- const requests = asInt(reportValue(scenario, "allRequestCount", "AllRequestCount"));
1091
- lines.push(`${escapeCsv(reportValue(scenario, "scenarioName", "ScenarioName"))},${requests},${asInt(reportValue(scenario, "allOkCount", "AllOkCount"))},${asInt(reportValue(scenario, "allFailCount", "AllFailCount"))},${formatReportNumber(durationSeconds)},${formatReportNumber(durationSeconds <= 0 ? 0 : requests / durationSeconds)}`);
1227
+ for (const scenario of reportScenarios(nodeStats)) {
1228
+ const durationSeconds = reportDurationSeconds(reportDurationValue(scenario));
1229
+ const requests = reportRequestCountValue(scenario);
1230
+ lines.push(`${escapeCsv(reportValue(scenario, "scenarioName", "ScenarioName"))},${requests},${reportOkCountValue(scenario)},${reportFailCountValue(scenario)},${formatReportNumber(durationSeconds)},${formatReportNumber(durationSeconds <= 0 ? 0 : requests / durationSeconds)}`);
1092
1231
  }
1093
1232
  return reportLines(lines);
1094
1233
  }
@@ -1096,16 +1235,16 @@ export function buildDotnetCsvReport(nodeStats) {
1096
1235
  * Exposes the build dotnet markdown report operation. Use this when interacting with the SDK through this surface.
1097
1236
  */
1098
1237
  export function buildDotnetMarkdownReport(nodeStats) {
1099
- const scenarios = sortBySortIndex(reportArray(nodeStats, "scenarioStats", "ScenarioStats"));
1238
+ const scenarios = reportScenarios(nodeStats);
1100
1239
  const testInfo = reportObject(nodeStats, "testInfo", "TestInfo");
1101
1240
  const lines = [
1102
1241
  `# ${asString(reportValue(testInfo, "testSuite", "TestSuite"))} / ${asString(reportValue(testInfo, "testName", "TestName"))}`,
1103
1242
  "",
1104
1243
  `- Session: \`${asString(reportValue(testInfo, "sessionId", "SessionId"))}\``,
1105
- `- Duration: \`${formatDotnetTimeSpan(reportValue(nodeStats, "duration", "Duration", "durationMs", "DurationMs"))}\``,
1106
- `- Total Requests: \`${asInt(reportValue(nodeStats, "allRequestCount", "AllRequestCount"))}\``,
1107
- `- OK: \`${asInt(reportValue(nodeStats, "allOkCount", "AllOkCount"))}\``,
1108
- `- FAIL: \`${asInt(reportValue(nodeStats, "allFailCount", "AllFailCount"))}\``,
1244
+ `- Duration: \`${formatDotnetTimeSpan(reportDurationValue(nodeStats))}\``,
1245
+ `- Total Requests: \`${reportTotalRequestCount(nodeStats, scenarios)}\``,
1246
+ `- OK: \`${reportTotalOkCount(nodeStats, scenarios)}\``,
1247
+ `- FAIL: \`${reportTotalFailCount(nodeStats, scenarios)}\``,
1109
1248
  "",
1110
1249
  "## Scenarios",
1111
1250
  "",
@@ -1113,19 +1252,19 @@ export function buildDotnetMarkdownReport(nodeStats) {
1113
1252
  "|---|---:|---:|---:|---:|"
1114
1253
  ];
1115
1254
  for (const scenario of scenarios) {
1116
- lines.push(`| ${asString(reportValue(scenario, "scenarioName", "ScenarioName"))} | ${asInt(reportValue(scenario, "allRequestCount", "AllRequestCount"))} | ${asInt(reportValue(scenario, "allOkCount", "AllOkCount"))} | ${asInt(reportValue(scenario, "allFailCount", "AllFailCount"))} | ${formatReportNumber(reportDurationSeconds(reportValue(scenario, "duration", "Duration", "durationMs", "DurationMs")))}s |`);
1255
+ lines.push(`| ${asString(reportValue(scenario, "scenarioName", "ScenarioName"))} | ${reportRequestCountValue(scenario)} | ${reportOkCountValue(scenario)} | ${reportFailCountValue(scenario)} | ${formatReportNumber(reportDurationSeconds(reportDurationValue(scenario)))}s |`);
1117
1256
  }
1118
- if (scenarios.some((scenario) => reportArray(scenario, "stepStats", "StepStats").length > 0)) {
1257
+ if (scenarios.some((scenario) => reportSteps(scenario).length > 0)) {
1119
1258
  lines.push("", "## Steps", "", "| Scenario | Step | Requests | OK | FAIL |", "|---|---|---:|---:|---:|");
1120
1259
  for (const scenario of scenarios) {
1121
- for (const step of sortBySortIndex(reportArray(scenario, "stepStats", "StepStats"))) {
1260
+ for (const step of reportSteps(scenario)) {
1122
1261
  const ok = asInt(reportValue(reportObject(reportObject(step, "ok", "Ok"), "request", "Request"), "count", "Count"));
1123
1262
  const fail = asInt(reportValue(reportObject(reportObject(step, "fail", "Fail"), "request", "Request"), "count", "Count"));
1124
1263
  lines.push(`| ${asString(reportValue(scenario, "scenarioName", "ScenarioName"))} | ${asString(reportValue(step, "stepName", "StepName"))} | ${ok + fail} | ${ok} | ${fail} |`);
1125
1264
  }
1126
1265
  }
1127
1266
  }
1128
- const thresholds = reportArray(nodeStats, "thresholds", "Thresholds");
1267
+ const thresholds = reportThresholds(nodeStats);
1129
1268
  if (thresholds.length) {
1130
1269
  lines.push("", "## Thresholds", "", "| Scenario | Step | Check | Failed | Errors | Exception |", "|---|---|---|---:|---:|---|");
1131
1270
  for (const threshold of thresholds) {
@@ -1261,7 +1400,7 @@ if(btns.length>0){show(btns[0].dataset.tab);}renderAllCharts();initPanePan();win
1261
1400
  .split("__TEST_SUITE__").join(escapeHtml(reportValue(testInfo, "testSuite", "TestSuite")))
1262
1401
  .split("__TEST_NAME__").join(escapeHtml(reportValue(testInfo, "testName", "TestName")))
1263
1402
  .split("__SESSION_ID__").join(escapeHtml(reportValue(testInfo, "sessionId", "SessionId")))
1264
- .split("__DURATION__").join(escapeHtml(formatDotnetTimeSpan(reportValue(nodeStats, "duration", "Duration", "durationMs", "DurationMs"))))
1403
+ .split("__DURATION__").join(escapeHtml(formatDotnetTimeSpan(reportDurationValue(nodeStats))))
1265
1404
  .split("__BUTTONS__").join(buttonsHtml)
1266
1405
  .split("__SECTIONS__").join(sectionsHtml)
1267
1406
  .split("__CHART_DATA__").join(chartDataJson)
@@ -8014,7 +8014,7 @@ function buildRichHtmlReport(result) {
8014
8014
  <section id="scenarios" class="panel active">
8015
8015
  ${bars || "<div>No scenario data.</div>"}
8016
8016
  <table>
8017
- <thead><tr><th>Scenario</th><th>Requests</th><th>OK</th><th>Fail</th><th>TotalBytes</th><th>AvgLatencyMs</th></tr></thead>
8017
+ <thead><tr><th>Scenario</th><th>Requests</th><th>OK</th><th>Fail</th><th>TotalBytes</th><th>AvgLatency (ms)</th></tr></thead>
8018
8018
  <tbody>${scenarioRows || "<tr><td colspan='6'>No rows</td></tr>"}</tbody>
8019
8019
  </table>
8020
8020
  </section>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loadstrike/loadstrike-sdk",
3
- "version": "1.0.21401",
3
+ "version": "1.0.22001",
4
4
  "description": "TypeScript and JavaScript SDK for in-process load execution, traffic correlation, and reporting.",
5
5
  "keywords": [
6
6
  "load-testing",