blaze-performance-tester 3.2.26 → 3.2.28

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.
Files changed (2) hide show
  1. package/dist/cli.js +196 -362
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env ts-node
2
1
  import { createRequire as __esbuild_createRequire } from 'module'; const require = __esbuild_createRequire(import.meta.url);
3
2
  var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
@@ -5727,6 +5726,153 @@ var i18n = {
5727
5726
  pass: "\u0646\u062C\u0627\u062D",
5728
5727
  fail: "\u0641\u0634\u0644"
5729
5728
  }
5729
+ },
5730
+ "hi-IN": {
5731
+ rtl: false,
5732
+ title: "\u092C\u094D\u0932\u0947\u091C\u093C \u0915\u094B\u0930 \u092A\u094D\u0930\u0926\u0930\u094D\u0936\u0928 \u0935\u093F\u0936\u094D\u0932\u0947\u0937\u0923",
5733
+ targetScript: "\u0932\u0915\u094D\u0937\u094D\u092F \u0938\u094D\u0915\u094D\u0930\u093F\u092A\u094D\u091F",
5734
+ generatedAt: "\u092A\u0930 \u0909\u0924\u094D\u092A\u0928\u094D\u0928",
5735
+ rampUp: "\u0930\u0948\u0902\u092A-\u0905\u092A",
5736
+ rampDown: "\u0930\u0948\u0902\u092A-\u0921\u093E\u0909\u0928",
5737
+ metrics: {
5738
+ apdex: "\u090F\u092A\u0921\u0947\u0915\u094D\u0938 \u0938\u094D\u0915\u094B\u0930 (T: 50ms)",
5739
+ throughput: "\u0925\u094D\u0930\u0942\u092A\u0941\u091F",
5740
+ bandwidth: "\u0928\u0947\u091F\u0935\u0930\u094D\u0915 \u092C\u0948\u0902\u0921\u0935\u093F\u0921\u094D\u0925",
5741
+ ttfb: "\u0914\u0938\u0924 \u0938\u092E\u092F (TTFB)",
5742
+ dns: "DNS \u0932\u0941\u0915\u0905\u092A",
5743
+ tcp: "TCP \u0915\u0928\u0947\u0915\u094D\u0936\u0928",
5744
+ tls: "TLS \u0939\u0948\u0902\u0921\u0936\u0947\u0915",
5745
+ stability: "\u0938\u094D\u0925\u093F\u0930\u0924\u093E (Std Dev)",
5746
+ good: "(\u0905\u091A\u094D\u091B\u093E)"
5747
+ },
5748
+ verdict: "\u{1F916} \u090F\u0906\u0908 \u0938\u094D\u091F\u094D\u0930\u0947\u0938-\u091F\u0947\u0938\u094D\u091F\u093F\u0902\u0917 \u090F\u091C\u0947\u0902\u091F \u0915\u093E \u0928\u093F\u0930\u094D\u0923\u092F",
5749
+ health: { title: "\u092C\u094D\u0932\u0947\u091C\u093C \u0930\u0928 \u0939\u0947\u0932\u094D\u0925 \u0938\u094D\u0915\u094B\u0930", subtitle: "\u0915\u0902\u092A\u094B\u091C\u093F\u091F \u0930\u0928 \u0915\u094D\u0935\u093E\u0932\u093F\u091F\u0940 \u0907\u0902\u091C\u0928 \u0930\u0947\u091F\u093F\u0902\u0917" },
5750
+ matrix: {
5751
+ title: "\u{1F4CB} HTTP \u0930\u093F\u0938\u094D\u092A\u0949\u0928\u094D\u0938 \u092E\u0948\u091F\u094D\u0930\u093F\u0915\u094D\u0938",
5752
+ info: "\u0938\u0942\u091A\u0928\u093E\u0924\u094D\u092E\u0915",
5753
+ success: "\u0938\u092B\u0932",
5754
+ redirects: "\u0930\u0940\u0921\u093E\u092F\u0930\u0947\u0915\u094D\u091F\u094D\u0938",
5755
+ clientErr: "\u0915\u094D\u0932\u093E\u0907\u0902\u091F \u0924\u094D\u0930\u0941\u091F\u093F",
5756
+ serverErr: "\u0938\u0930\u094D\u0935\u0930 \u0924\u094D\u0930\u0941\u091F\u093F",
5757
+ breakdown: "\u{1F522} HTTP \u0938\u094D\u091F\u0947\u091F\u0938 \u0915\u094B\u0921 \u092C\u094D\u0930\u0947\u0915\u0921\u093E\u0909\u0928 \u0926\u0930",
5758
+ code: "\u0915\u094B\u0921"
5759
+ },
5760
+ rca: "\u26A1 \u091C\u0947\u092E\u093F\u0928\u0940 2.5 \u092B\u094D\u0932\u0948\u0936 \u0938\u094D\u0935\u093E\u092F\u0924\u094D\u0924 \u0930\u0942\u091F \u0915\u0949\u091C\u093C \u090F\u0928\u093E\u0932\u093F\u0938\u093F\u0938 (RCA)",
5761
+ charts: {
5762
+ volume: "\u{1F4CA} \u0925\u094D\u0930\u0942\u092A\u0941\u091F \u0935\u0947\u0917 \u0914\u0930 \u0935\u0930\u094D\u0915\u0932\u094B\u0921 \u0935\u0949\u0932\u094D\u092F\u0942\u092E",
5763
+ concurrency: "\u{1F504} \u0938\u0915\u094D\u0930\u093F\u092F \u0938\u092E\u0935\u0930\u094D\u0924\u0940 (VUs) \u092C\u0928\u093E\u092E TTF \u091F\u094D\u0930\u0947\u0902\u0921",
5764
+ status: "\u{1F369} HTTP \u0938\u094D\u091F\u0947\u091F\u0938 \u0915\u094B\u0921 \u0905\u0928\u0941\u092A\u093E\u0924",
5765
+ scatter: "\u{1F4C8} \u091F\u093E\u0907\u092E-\u091F\u0942-\u092B\u0930\u094D\u0938\u094D\u091F-\u092C\u093E\u0907\u091F \u0938\u094D\u0915\u0948\u091F\u0930 \u092A\u094D\u0932\u0949\u091F"
5766
+ },
5767
+ telemetry: {
5768
+ title: "\u091F\u0947\u0932\u0940\u092E\u0947\u091F\u094D\u0930\u0940 \u092E\u0948\u091F\u094D\u0930\u093F\u0915\u094D\u0938 \u0932\u0949\u0917\u094D\u0938",
5769
+ threads: "\u0938\u092E\u0935\u0930\u094D\u0924\u0940 (\u0925\u094D\u0930\u0947\u0921\u094D\u0938)",
5770
+ throughput: "\u0925\u094D\u0930\u0942\u092A\u0941\u091F",
5771
+ avgLatency: "\u0914\u0938\u0924 \u0935\u093F\u0932\u0902\u092C\u0924\u093E",
5772
+ p95: "P95 \u0935\u093F\u0932\u0902\u092C\u0924\u093E",
5773
+ errors: "\u0924\u094D\u0930\u0941\u091F\u093F \u0926\u0930",
5774
+ status: "\u0938\u094D\u0925\u093F\u0924\u093F",
5775
+ pass: "SLO \u092A\u093E\u0938",
5776
+ fail: "SLO \u092B\u0947\u0932"
5777
+ }
5778
+ },
5779
+ "ta-IN": {
5780
+ rtl: false,
5781
+ title: "\u0BAA\u0BBF\u0BB3\u0BC7\u0BB8\u0BCD \u0B95\u0BCB\u0BB0\u0BCD \u0B9A\u0BC6\u0BAF\u0BB2\u0BCD\u0BA4\u0BBF\u0BB1\u0BA9\u0BCD \u0BAA\u0B95\u0BC1\u0BAA\u0BCD\u0BAA\u0BBE\u0BAF\u0BCD\u0BB5\u0BC1",
5782
+ targetScript: "\u0B87\u0BB2\u0B95\u0BCD\u0B95\u0BC1 \u0BB8\u0BCD\u0B95\u0BBF\u0BB0\u0BBF\u0BAA\u0BCD\u0B9F\u0BCD",
5783
+ generatedAt: "\u0B89\u0BB0\u0BC1\u0BB5\u0BBE\u0B95\u0BCD\u0B95\u0BAA\u0BCD\u0BAA\u0B9F\u0BCD\u0B9F \u0BA8\u0BC7\u0BB0\u0BAE\u0BCD",
5784
+ rampUp: "\u0BB0\u0BBE\u0BAE\u0BCD\u0BAA\u0BCD-\u0B85\u0BAA\u0BCD",
5785
+ rampDown: "\u0BB0\u0BBE\u0BAE\u0BCD\u0BAA\u0BCD-\u0B9F\u0BB5\u0BC1\u0BA9\u0BCD",
5786
+ metrics: {
5787
+ apdex: "Apdex \u0BAE\u0BA4\u0BBF\u0BAA\u0BCD\u0BAA\u0BC6\u0BA3\u0BCD (T: 50ms)",
5788
+ throughput: "\u0B9A\u0BC6\u0BAF\u0BB2\u0BCD\u0BA4\u0BBF\u0BB1\u0BA9\u0BCD (Throughput)",
5789
+ bandwidth: "\u0BA8\u0BC6\u0B9F\u0BCD\u0BB5\u0BCA\u0BB0\u0BCD\u0B95\u0BCD \u0B85\u0BB2\u0BC8\u0BB5\u0BB0\u0BBF\u0B9A\u0BC8",
5790
+ ttfb: "\u0B9A\u0BB0\u0BBE\u0B9A\u0BB0\u0BBF TTFB",
5791
+ dns: "DNS \u0BA4\u0BC7\u0B9F\u0BB2\u0BCD",
5792
+ tcp: "TCP \u0B87\u0BA3\u0BC8\u0BAA\u0BCD\u0BAA\u0BC1",
5793
+ tls: "TLS \u0B95\u0BC8\u0B95\u0BCD\u0B95\u0BC1\u0BB2\u0BC1\u0B95\u0BCD\u0B95\u0BB2\u0BCD",
5794
+ stability: "\u0BA8\u0BBF\u0BB2\u0BC8\u0BA4\u0BCD\u0BA4\u0BA9\u0BCD\u0BAE\u0BC8 (Std Dev)",
5795
+ good: "(\u0BA8\u0BB2\u0BCD\u0BB2\u0BA4\u0BC1)"
5796
+ },
5797
+ verdict: "\u{1F916} AI \u0B85\u0BB4\u0BC1\u0BA4\u0BCD\u0BA4-\u0B9A\u0BCB\u0BA4\u0BA9\u0BC8 \u0BAE\u0BC1\u0B95\u0BB5\u0BB0\u0BCD \u0BA4\u0BC0\u0BB0\u0BCD\u0BAA\u0BCD\u0BAA\u0BC1",
5798
+ health: { title: "\u0BAA\u0BBF\u0BB3\u0BC7\u0BB8\u0BCD \u0BB0\u0BA9\u0BCD \u0BB9\u0BC6\u0BB2\u0BCD\u0BA4\u0BCD \u0BB8\u0BCD\u0B95\u0BCB\u0BB0\u0BCD", subtitle: "\u0B95\u0BC2\u0B9F\u0BCD\u0B9F\u0BC1 \u0BB0\u0BA9\u0BCD \u0BA4\u0BB0 \u0BAE\u0BA4\u0BBF\u0BAA\u0BCD\u0BAA\u0BC0\u0B9F\u0BC1" },
5799
+ matrix: {
5800
+ title: "\u{1F4CB} HTTP \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B85\u0BA3\u0BBF",
5801
+ info: "\u0BA4\u0B95\u0BB5\u0BB2\u0BCD",
5802
+ success: "\u0BB5\u0BC6\u0BB1\u0BCD\u0BB1\u0BBF",
5803
+ redirects: "\u0BB5\u0BB4\u0BBF\u0BAE\u0BBE\u0BB1\u0BCD\u0BB1\u0BC1\u0B95\u0BB3\u0BCD",
5804
+ clientErr: "\u0B95\u0BBF\u0BB3\u0BC8\u0BAF\u0BA3\u0BCD\u0B9F\u0BCD \u0BAA\u0BBF\u0BB4\u0BC8",
5805
+ serverErr: "\u0B9A\u0BB0\u0BCD\u0BB5\u0BB0\u0BCD \u0BAA\u0BBF\u0BB4\u0BC8",
5806
+ breakdown: "\u{1F522} HTTP \u0BA8\u0BBF\u0BB2\u0BC8 \u0B95\u0BC1\u0BB1\u0BBF\u0BAF\u0BC0\u0B9F\u0BC1 \u0BAA\u0B95\u0BC1\u0BAA\u0BCD\u0BAA\u0BBE\u0BAF\u0BCD\u0BB5\u0BC1",
5807
+ code: "\u0B95\u0BC1\u0BB1\u0BBF\u0BAF\u0BC0\u0B9F\u0BC1"
5808
+ },
5809
+ rca: "\u26A1 \u0B9C\u0BC6\u0BAE\u0BBF\u0BA9\u0BBF 2.5 \u0B83\u0BAA\u0BCD\u0BB3\u0BBE\u0BB7\u0BCD \u0BA4\u0BA9\u0BCD\u0BA9\u0BBE\u0B9F\u0BCD\u0B9A\u0BBF \u0BAE\u0BC2\u0BB2 \u0B95\u0BBE\u0BB0\u0BA3 \u0BAA\u0B95\u0BC1\u0BAA\u0BCD\u0BAA\u0BBE\u0BAF\u0BCD\u0BB5\u0BC1 (RCA)",
5810
+ charts: {
5811
+ volume: "\u{1F4CA} \u0B9A\u0BC6\u0BAF\u0BB2\u0BCD\u0BA4\u0BBF\u0BB1\u0BA9\u0BCD \u0BB5\u0BC7\u0B95\u0BAE\u0BCD \u0BAE\u0BB1\u0BCD\u0BB1\u0BC1\u0BAE\u0BCD \u0BAA\u0BA3\u0BBF\u0B9A\u0BCD\u0B9A\u0BC1\u0BAE\u0BC8",
5812
+ concurrency: "\u{1F504} \u0B9A\u0BC6\u0BAF\u0BB2\u0BBF\u0BB2\u0BCD \u0B89\u0BB3\u0BCD\u0BB3 \u0B87\u0BA3\u0BC8\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BCD (VUs) \u0BAE\u0BB1\u0BCD\u0BB1\u0BC1\u0BAE\u0BCD TTF \u0BAA\u0BCB\u0B95\u0BCD\u0B95\u0BC1",
5813
+ status: "\u{1F369} HTTP \u0BA8\u0BBF\u0BB2\u0BC8 \u0B95\u0BC1\u0BB1\u0BBF\u0BAF\u0BC0\u0B9F\u0BC1 \u0BB5\u0BBF\u0B95\u0BBF\u0BA4\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
5814
+ scatter: "\u{1F4C8} TTFB \u0B9A\u0BBF\u0BA4\u0BB1\u0BB2\u0BCD \u0BB5\u0BB0\u0BC8\u0BAA\u0B9F\u0BAE\u0BCD"
5815
+ },
5816
+ telemetry: {
5817
+ title: "\u0B9F\u0BC6\u0BB2\u0BBF\u0BAE\u0BC6\u0B9F\u0BCD\u0BB0\u0BBF \u0B85\u0BA3\u0BBF \u0BAA\u0BA4\u0BBF\u0BB5\u0BC1\u0B95\u0BB3\u0BCD",
5818
+ threads: "\u0B87\u0BA3\u0BC8\u0BAA\u0BCD\u0BAA\u0BC1\u0B95\u0BB3\u0BCD (Threads)",
5819
+ throughput: "\u0B9A\u0BC6\u0BAF\u0BB2\u0BCD\u0BA4\u0BBF\u0BB1\u0BA9\u0BCD",
5820
+ avgLatency: "\u0B9A\u0BB0\u0BBE\u0B9A\u0BB0\u0BBF \u0BA4\u0BBE\u0BAE\u0BA4\u0BAE\u0BCD",
5821
+ p95: "P95 \u0BA4\u0BBE\u0BAE\u0BA4\u0BAE\u0BCD",
5822
+ errors: "\u0BAA\u0BBF\u0BB4\u0BC8 \u0BB5\u0BBF\u0B95\u0BBF\u0BA4\u0BAE\u0BCD",
5823
+ status: "\u0BA8\u0BBF\u0BB2\u0BC8",
5824
+ pass: "SLO \u0BB5\u0BC6\u0BB1\u0BCD\u0BB1\u0BBF",
5825
+ fail: "SLO \u0BA4\u0BCB\u0BB2\u0BCD\u0BB5\u0BBF"
5826
+ }
5827
+ },
5828
+ "si-LK": {
5829
+ rtl: false,
5830
+ title: "\u0DB6\u0DCA\u0DBD\u0DDA\u0DC3\u0DCA \u0D9A\u0DDD\u0DBB\u0DCA \u0D9A\u0DCF\u0DBB\u0DCA\u0DBA\u0DC3\u0DCF\u0DB0\u0DB1 \u0DC0\u0DD2\u0DC1\u0DCA\u0DBD\u0DDA\u0DC2\u0DAB\u0DBA",
5831
+ targetScript: "\u0D89\u0DBD\u0D9A\u0DCA\u0D9A\u0D9C\u0DAD \u0DC3\u0DCA\u0D9A\u0DCA\u200D\u0DBB\u0DD2\u0DB4\u0DCA\u0DA7\u0DCA",
5832
+ generatedAt: "\u0DA2\u0DB1\u0DB1\u0DBA \u0D9A\u0DBB\u0DB1 \u0DBD\u0DAF \u0DAF\u0DD2\u0DB1\u0DBA",
5833
+ rampUp: "\u0DBB\u0DCF\u0DB8\u0DCA\u0DB4\u0DCA-\u0D85\u0DB4\u0DCA",
5834
+ rampDown: "\u0DBB\u0DCF\u0DB8\u0DCA\u0DB4\u0DCA-\u0DA9\u0DC0\u0DD4\u0DB1\u0DCA",
5835
+ metrics: {
5836
+ apdex: "Apdex \u0DBD\u0D9A\u0DD4\u0DAB\u0DD4 (T: 50ms)",
5837
+ throughput: "\u0DB4\u0DCA\u200D\u0DBB\u0DC0\u0DCF\u0DC4 \u0D85\u0DB1\u0DD4\u0DB4\u0DCF\u0DAD\u0DBA (Throughput)",
5838
+ bandwidth: "\u0DA2\u0DCF\u0DBD \u0D9A\u0DBD\u0DCF\u0DB4 \u0DB4\u0DC5\u0DBD",
5839
+ ttfb: "\u0DC3\u0DCF\u0DB8\u0DCF\u0DB1\u0DCA\u200D\u0DBA TTFB",
5840
+ dns: "DNS \u0DC3\u0DD9\u0DC0\u0DD3\u0DB8",
5841
+ tcp: "TCP \u0DC3\u0DB8\u0DCA\u0DB6\u0DB1\u0DCA\u0DB0\u0DAD\u0DCF\u0DC0\u0DBA",
5842
+ tls: "TLS \u0DC4\u0DD1\u0DB1\u0DCA\u0DA9\u0DCA\u0DC2\u0DDA\u0D9A\u0DCA",
5843
+ stability: "\u0DC3\u0DCA\u0DAE\u0DCF\u0DC0\u0DBB\u0DAD\u0DCA\u0DC0\u0DBA (Std Dev)",
5844
+ good: "(\u0DBA\u0DC4\u0DB4\u0DAD\u0DCA)"
5845
+ },
5846
+ verdict: "\u{1F916} AI \u0D86\u0DAD\u0DAD\u0DD2-\u0DB4\u0DBB\u0DD3\u0D9A\u0DCA\u0DC2\u0DAB \u0DB1\u0DD2\u0DBA\u0DDD\u0DA2\u0DD2\u0DAD \u0DAD\u0DD3\u0DB1\u0DCA\u0DAF\u0DD4\u0DC0",
5847
+ health: { title: "\u0DB6\u0DCA\u0DBD\u0DDA\u0DC3\u0DCA \u0DB0\u0DCF\u0DC0\u0DB1 \u0DC3\u0DDE\u0D9B\u0DCA\u200D\u0DBA \u0DBD\u0D9A\u0DD4\u0DAB\u0DD4", subtitle: "\u0DC3\u0D82\u0DBA\u0DD4\u0D9A\u0DCA\u0DAD \u0DB0\u0DCF\u0DC0\u0DB1 \u0D9C\u0DD4\u0DAB\u0DCF\u0DAD\u0DCA\u0DB8\u0D9A \u0DC1\u0DCA\u200D\u0DBB\u0DDA\u0DAB\u0DD2\u0D9C\u0DAD \u0D9A\u0DD2\u0DBB\u0DD3\u0DB8" },
5848
+ matrix: {
5849
+ title: "\u{1F4CB} HTTP \u0DB4\u0DCA\u200D\u0DBB\u0DAD\u0DD2\u0DA0\u0DCF\u0DBB \u0D85\u0DB1\u0DD4\u0D9A\u0DD8\u0DAD\u0DD2\u0DBA",
5850
+ info: "\u0DAD\u0DDC\u0DBB\u0DAD\u0DD4\u0DBB\u0DD4",
5851
+ success: "\u0DC3\u0DCF\u0DBB\u0DCA\u0DAE\u0D9A",
5852
+ redirects: "\u0DBA\u0DC5\u0DD2 \u0DBA\u0DDC\u0DB8\u0DD4 \u0D9A\u0DD2\u0DBB\u0DD3\u0DB8\u0DCA",
5853
+ clientErr: "\u0DC3\u0DDA\u0DC0\u0DCF\u0DBD\u0DCF\u0DB7\u0DD3 \u0DAF\u0DDD\u0DC2\u0DBA",
5854
+ serverErr: "\u0DC3\u0DDA\u0DC0\u0DCF\u0DAF\u0DCF\u0DBA\u0D9A \u0DAF\u0DDD\u0DC2\u0DBA",
5855
+ breakdown: "\u{1F522} HTTP \u0DAD\u0DAD\u0DCA\u0DC0 \u0D9A\u0DDA\u0DAD \u0DB6\u0DD2\u0DB3\u0DC0\u0DD0\u0DA7\u0DD3\u0DB8",
5856
+ code: "\u0D9A\u0DDA\u0DAD\u0DBA"
5857
+ },
5858
+ rca: "\u26A1 \u0DA2\u0DD9\u0DB8\u0DD2\u0DB1\u0DD3 2.5 \u0DC6\u0DCA\u0DBD\u0DD1\u0DC2\u0DCA \u0DC3\u0DCA\u0DC0\u0DBA\u0D82\u0D9A\u0DCA\u200D\u0DBB\u0DD3\u0DBA \u0DB8\u0DD6\u0DBD \u0DC4\u0DDA\u0DAD\u0DD4 \u0DC0\u0DD2\u0DC1\u0DCA\u0DBD\u0DDA\u0DC2\u0DAB\u0DBA (RCA)",
5859
+ charts: {
5860
+ volume: "\u{1F4CA} \u0DB4\u0DCA\u200D\u0DBB\u0DC0\u0DCF\u0DC4 \u0DC0\u0DDA\u0D9C\u0DBA \u0DC3\u0DC4 \u0DC0\u0DD0\u0DA9 \u0DB6\u0DBB",
5861
+ concurrency: "\u{1F504} \u0DC3\u0D9A\u0DCA\u200D\u0DBB\u0DD3\u0DBA \u0DC3\u0DB8\u0D9C\u0DCF\u0DB8\u0DD3\u0DAD\u0DCA\u0DC0\u0DBA (VUs) \u0DC3\u0DC4 TTF \u0DB4\u0DCA\u200D\u0DBB\u0DC0\u0DAB\u0DAD\u0DCF\u0DC0",
5862
+ status: "\u{1F369} HTTP \u0DAD\u0DAD\u0DCA\u0DC0 \u0D9A\u0DDA\u0DAD \u0D85\u0DB1\u0DD4\u0DB4\u0DCF\u0DAD",
5863
+ scatter: "\u{1F4C8} TTFB \u0DC3\u0DCA\u0D9A\u0DD0\u0DA7\u0DBB\u0DCA \u0DB4\u0DCA\u0DBD\u0DDC\u0DA7\u0DCA"
5864
+ },
5865
+ telemetry: {
5866
+ title: "\u0DA7\u0DD9\u0DBD\u0DD2\u0DB8\u0DD9\u0DA7\u0DCA\u200D\u0DBB\u0DD2 \u0D85\u0DB1\u0DD4\u0D9A\u0DD8\u0DAD\u0DD2 \u0DBD\u0DDC\u0D9C\u0DCA",
5867
+ threads: "\u0DC3\u0DB8\u0D9C\u0DCF\u0DB8\u0DD3\u0DAD\u0DCA\u0DC0\u0DBA (Threads)",
5868
+ throughput: "\u0DB4\u0DCA\u200D\u0DBB\u0DC0\u0DCF\u0DC4 \u0D85\u0DB1\u0DD4\u0DB4\u0DCF\u0DAD\u0DBA",
5869
+ avgLatency: "\u0DC3\u0DCF\u0DB8\u0DCF\u0DB1\u0DCA\u200D\u0DBA \u0DB4\u0DCA\u200D\u0DBB\u0DB8\u0DCF\u0DAF\u0DBA",
5870
+ p95: "P95 \u0DB4\u0DCA\u200D\u0DBB\u0DB8\u0DCF\u0DAF\u0DBA",
5871
+ errors: "\u0DAF\u0DDD\u0DC2 \u0D85\u0DB1\u0DD4\u0DB4\u0DCF\u0DAD\u0DBA",
5872
+ status: "\u0DAD\u0DAD\u0DCA\u0DC0\u0DBA",
5873
+ pass: "SLO \u0DC3\u0DCF\u0DBB\u0DCA\u0DAE\u0D9A",
5874
+ fail: "SLO \u0D85\u0DC3\u0DCF\u0DBB\u0DCA\u0DAE\u0D9A"
5875
+ }
5730
5876
  }
5731
5877
  };
5732
5878
  var mathUtils = {
@@ -6523,146 +6669,10 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
6523
6669
  const waveListItems = history.map((h) => {
6524
6670
  return `<div class="wave-item">Wave (${h.threads} VUs): P95 ${h.p95Latency.toFixed(1)}ms, Errors ${(h.errorRate * 100).toFixed(1)}%</div>`;
6525
6671
  }).join("");
6526
- const estimatedMonthlyCost = (cards.throughput * 0.045 * 24 * 30).toFixed(2);
6527
- const recommendedCpuCores = Math.max(2, Math.ceil(cards.saturationKneePoint / 75));
6528
- const recommendedRamGb = recommendedCpuCores * 2;
6529
- const firstTimestamp = rawRequests[0]?.timestampMs || Date.now();
6530
6672
  const scatterPoints = rawRequests.slice(0, 180).map((r) => ({
6531
- x: Number(((r.timestampMs - firstTimestamp) / 1e3).toFixed(2)),
6673
+ x: Number(((r.timestampMs - (rawRequests[0]?.timestampMs || Date.now())) / 1e3).toFixed(2)),
6532
6674
  y: Number(((r.dnsTimeMs || 0) + (r.tcpTimeMs || 0) + (r.tlsTimeMs || 0) + r.durationMs * 0.3).toFixed(1))
6533
6675
  }));
6534
- let baselineComparisonHtml = "";
6535
- if (baselineData) {
6536
- const diffApdex = cards.apdex - baselineData.apdex;
6537
- const diffThroughput = (cards.throughput - baselineData.throughput) / (baselineData.throughput || 1) * 100;
6538
- const diffTtfb = (cards.ttfb - baselineData.ttfb) / (baselineData.ttfb || 1) * 100;
6539
- const diffHealth = cards.healthScore - baselineData.healthScore;
6540
- const formatDelta = (val, isInverse = false) => {
6541
- const good = isInverse ? val <= 0 : val >= 0;
6542
- const color = good ? "#10b981" : "#ef4444";
6543
- const sign = val > 0 ? "+" : "";
6544
- return `<span style="color: ${color}; font-weight: bold;">${sign}${val.toFixed(1)}%</span>`;
6545
- };
6546
- baselineComparisonHtml = `
6547
- <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #7c3aed;">
6548
- <h3 style="color: #a78bfa; display: flex; align-items: center; gap: 0.6rem; font-size: 1.2rem; border-bottom: 1px solid #1e293b; padding-bottom: 0.5rem;">
6549
- \u{1F4CA} Baseline Regression Comparison (Delta Engine)
6550
- </h3>
6551
- <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem;">
6552
- <div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
6553
- <div class="card-title">Apdex Shift</div>
6554
- <div style="font-size: 1.4rem; font-weight: bold; color: #ffffff;">${cards.apdex.toFixed(2)}</div>
6555
- <div style="font-size: 0.8rem; margin-top: 0.2rem;">Delta: <span style="color: ${diffApdex >= 0 ? "#10b981" : "#ef4444"}; font-weight: bold;">${diffApdex >= 0 ? "+" : ""}${diffApdex.toFixed(2)}</span></div>
6556
- </div>
6557
- <div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
6558
- <div class="card-title">Throughput Delta</div>
6559
- <div style="font-size: 1.4rem; font-weight: bold; color: #ffffff;">${cards.throughput.toFixed(0)}/s</div>
6560
- <div style="font-size: 0.8rem; margin-top: 0.2rem;">Delta: ${formatDelta(diffThroughput, false)}</div>
6561
- </div>
6562
- <div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
6563
- <div class="card-title">TTFB Latency Shift</div>
6564
- <div style="font-size: 1.4rem; font-weight: bold; color: #ffffff;">${cards.ttfb.toFixed(1)}ms</div>
6565
- <div style="font-size: 0.8rem; margin-top: 0.2rem;">Delta: ${formatDelta(diffTtfb, true)}</div>
6566
- </div>
6567
- <div style="background: #090d16; padding: 1rem; border-radius: 6px; border: 1px solid #1e293b;">
6568
- <div class="card-title">Health Score Diff</div>
6569
- <div style="font-size: 1.4rem; font-weight: bold; color: #ffffff;">${cards.healthScore}/100</div>
6570
- <div style="font-size: 0.8rem; margin-top: 0.2rem;">Delta: <span style="color: ${diffHealth >= 0 ? "#10b981" : "#ef4444"}; font-weight: bold;">${diffHealth >= 0 ? "+" : ""}${diffHealth} pts</span></div>
6571
- </div>
6572
- </div>
6573
- </div>`;
6574
- }
6575
- const crossBrowserRows = getCrossBrowserMatrixData().map((m) => `
6576
- <tr>
6577
- <td><span class="engine-badge ${m.badgeClass}">${m.engine}</span></td>
6578
- <td>${m.fcp}</td>
6579
- <td>${m.lcp}</td>
6580
- <td>${m.journeyDuration}</td>
6581
- <td><span class="${m.statusClass}">${m.status}</span></td>
6582
- </tr>
6583
- `).join("");
6584
- const crossBrowserMatrixSectionHtml = `
6585
- <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #38bdf8;">
6586
- <h3 style="color: #38bdf8; display: flex; align-items: center; gap: 0.6rem; font-size: 1.2rem; border-bottom: 1px solid #1e293b; padding-bottom: 0.5rem;">
6587
- \u{1F310} Cross-Browser Rendering Performance Matrix
6588
- </h3>
6589
- <table class="matrix-table" style="width: 100%; border-collapse: collapse; text-align: start; font-size: 14px;">
6590
- <thead>
6591
- <tr style="border-bottom: 1px solid #1e293b;">
6592
- <th style="padding: 12px 14px; background: #090d16; color: #94a3b8; font-size: 12px; text-transform: uppercase;">Browser Engine</th>
6593
- <th style="padding: 12px 14px; background: #090d16; color: #94a3b8; font-size: 12px; text-transform: uppercase;">FCP</th>
6594
- <th style="padding: 12px 14px; background: #090d16; color: #94a3b8; font-size: 12px; text-transform: uppercase;">LCP</th>
6595
- <th style="padding: 12px 14px; background: #090d16; color: #94a3b8; font-size: 12px; text-transform: uppercase;">Journey Duration</th>
6596
- <th style="padding: 12px 14px; background: #090d16; color: #94a3b8; font-size: 12px; text-transform: uppercase;">Status</th>
6597
- </tr>
6598
- </thead>
6599
- <tbody>
6600
- ${crossBrowserRows}
6601
- </tbody>
6602
- </table>
6603
- </div>`;
6604
- let logClustersHtml = "";
6605
- if (semanticReport) {
6606
- const totalLogCount = semanticReport.clusters.reduce((sum, c) => sum + c.count, 0);
6607
- const uniquePatternsCount = semanticReport.clusters.length;
6608
- logClustersHtml = `
6609
- <div class="card" style="margin-top: 2rem; background: #131c2e; border: 1px solid #1e293b;">
6610
- <h3 style="border-bottom: none; padding-bottom: 0rem; margin-bottom: 0.25rem; display: flex; align-items: center; gap: 0.5rem; color: #ffffff; font-size: 1.2rem;">
6611
- \u{1F9E0} Semantic Log Clustering
6612
- </h3>
6613
- <div style="color: #94a3b8; font-size: 0.9rem; margin-bottom: 1.5rem;">
6614
- Captured <strong style="color: #f8fafc;">${totalLogCount}</strong> raw failures.
6615
- </div>
6616
-
6617
- <table style="width: 100%; border-collapse: collapse;">
6618
- <thead>
6619
- <tr style="border-bottom: 1px solid #1e293b;">
6620
- <th style="width: 8%; padding: 0.75rem; text-align: start; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Count</th>
6621
- <th style="width: 22%; padding: 0.75rem; text-align: start; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Category</th>
6622
- <th style="width: 10%; padding: 0.75rem; text-align: start; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Severity</th>
6623
- <th style="width: 60%; padding: 0.75rem; text-align: start; color: #64748b; font-size: 0.85rem; font-weight: bold; text-transform: none;">Structural Blueprint</th>
6624
- </tr>
6625
- </thead>
6626
- <tbody>
6627
- ${semanticReport.clusters.map((c) => {
6628
- let catColor = "#fb923c";
6629
- if (c.category === "Authentication_Error")
6630
- catColor = "#c084fc";
6631
- if (c.category === "Product_Error")
6632
- catColor = "#f87171";
6633
- let sevBg = c.severity === "CRITICAL" ? "#dc2626" : "#ea580c";
6634
- const rawTemplate = c.template || "";
6635
- const cleanedTemplate = rawTemplate.replace(/\[\d{4}-\d{2}-\d{2}.*?\]\s*/, "");
6636
- return `
6637
- <tr style="border-bottom: 1px solid #1e293b; vertical-align: top;">
6638
- <td style="padding: 1.25rem 0.75rem; font-size: 1.2rem; font-weight: bold; color: #f8fafc;">${c.count}</td>
6639
- <td style="padding: 1.25rem 0.75rem; color: ${catColor}; font-weight: 600; font-size: 0.95rem;">${c.category}</td>
6640
- <td style="padding: 1.25rem 0.75rem;">
6641
- <span style="background: ${sevBg}; color: #ffffff; padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.7rem; font-weight: bold;">${c.severity}</span>
6642
- </td>
6643
- <td style="padding: 1.25rem 0.75rem;">
6644
- <div style="background: #090d16; border-radius: 4px; padding: 0.6rem 0.8rem; border-inline-start: 3px solid #1e293b; margin-bottom: 0.5rem;">
6645
- <code style="color: #cbd5e1; font-family: monospace; font-size: 0.9rem; white-space: pre-wrap; word-break: break-all;">${cleanedTemplate}</code>
6646
- </div>
6647
- </td>
6648
- </tr>`;
6649
- }).join("")}
6650
- </tbody>
6651
- </table>
6652
- </div>`;
6653
- }
6654
- const breakdownRates = responseMatrix.breakdownRates || {};
6655
- const breakdownGridHtml = [400, 401, 403, 404, 429, 500, 502, 503, 504].map((code) => {
6656
- const rate = breakdownRates[code] || 0;
6657
- const displayColor = rate > 0 ? code >= 500 ? "#f87171" : "#fde047" : "#64748b";
6658
- return `
6659
- <div style="background: #090d16; border: 1px solid #1e293b; border-radius: 4px; padding: 0.4rem 0.2rem; text-align: center;">
6660
- <div style="font-size: 0.7rem; color: #64748b; font-weight: bold;">${dict.matrix.code} ${code}</div>
6661
- <div style="font-size: 0.85rem; font-weight: bold; color: ${displayColor};">${rate.toFixed(2)}%</div>
6662
- </div>
6663
- `;
6664
- }).join("");
6665
- const gaugeColor = cards.healthScore >= 90 ? "#10b981" : cards.healthScore >= 70 ? "#f59e0b" : "#ef4444";
6666
6676
  const htmlContent = `<!DOCTYPE html>
6667
6677
  <html lang="${config.locale}" dir="${dir}">
6668
6678
  <head>
@@ -6678,7 +6688,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
6678
6688
 
6679
6689
  .cards-wrapper { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
6680
6690
  .metric-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 6px; padding: 1.25rem; position: relative; }
6681
- .card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
6691
+ .card-title { font-size: 0.75rem; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; display: flex; justify-content: space-between; }
6682
6692
  .card-value { font-size: 1.6rem; font-weight: 700; color: #ffffff; }
6683
6693
  .card-value .unit { font-size: 0.9rem; font-weight: 500; color: #94a3b8; margin-inline-start: 0.2rem; }
6684
6694
  .card-value.green { color: #10b981; }
@@ -6686,6 +6696,11 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
6686
6696
  .card-value.purple { color: #a855f7; }
6687
6697
  .card-subtext { font-size: 0.8rem; color: #10b981; font-weight: 500; margin-inline-start: 0.4rem; display: inline-block; }
6688
6698
 
6699
+ /* Export Styles */
6700
+ .export-menu { position: absolute; top: 0.5rem; right: 0.5rem; }
6701
+ .export-btn { background: #1e293b; border: 1px solid #334155; color: #94a3b8; padding: 2px 8px; border-radius: 4px; font-size: 0.7rem; cursor: pointer; }
6702
+ .export-btn:hover { background: #334155; }
6703
+
6689
6704
  .top-layout-grid { display: grid; grid-template-columns: 1fr 280px 380px; gap: 1.5rem; margin-bottom: 2rem; }
6690
6705
  .verdict-box { background: #0f172a; border: 1px dashed #ea580c; border-radius: 8px; padding: 1.25rem; }
6691
6706
  .verdict-title { text-transform: uppercase; font-size: 0.85rem; font-weight: 700; color: #f97316; margin-bottom: 0.5rem; }
@@ -6697,7 +6712,7 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
6697
6712
  .gauge-title { font-size: 0.85rem; font-weight: bold; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 1rem; }
6698
6713
 
6699
6714
  .matrix-box { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.25rem; }
6700
- .matrix-title { font-size: 1.1rem; font-weight: bold; color: #ffffff; margin-bottom: 1rem; }
6715
+ .matrix-title { font-size: 1.1rem; font-weight: bold; color: #ffffff; margin-bottom: 1rem; display: flex; justify-content: space-between; align-items: center; }
6701
6716
  .matrix-box .matrix-row { display: flex; justify-content: space-between; align-items: center; padding: 0.6rem 0; border-bottom: 1px solid #1e293b; }
6702
6717
  .matrix-box .matrix-row:last-child { border-bottom: none; }
6703
6718
  .matrix-label { font-size: 0.9rem; color: #f8fafc; display: flex; align-items: center; gap: 0.5rem; }
@@ -6717,11 +6732,11 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
6717
6732
  .status-warning { color: #ca8a04; font-weight: 600; }
6718
6733
 
6719
6734
  .charts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; margin-bottom: 2rem; }
6720
- .graph-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }
6721
- .graph-title { font-size: 1.1rem; font-weight: 700; color: #ffffff; margin-bottom: 1.2rem; display: flex; align-items: center; gap: 0.5rem; }
6735
+ .graph-card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; position: relative; }
6736
+ .graph-title { font-size: 1.1rem; font-weight: 700; color: #ffffff; margin-bottom: 1.2rem; display: flex; align-items: center; justify-content: space-between; }
6722
6737
 
6723
- .card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; }
6724
- h3 { margin-top: 0; color: #cbd5e1; border-bottom: 1px solid #1e293b; padding-bottom: 0.5rem; }
6738
+ .card { background: #131c2e; border: 1px solid #1e293b; border-radius: 8px; padding: 1.5rem; position: relative; }
6739
+ h3 { margin-top: 0; color: #cbd5e1; border-bottom: 1px solid #1e293b; padding-bottom: 0.5rem; display: flex; justify-content: space-between; }
6725
6740
  table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
6726
6741
  th, td { padding: 0.75rem; text-align: start; border-bottom: 1px solid #1e293b; }
6727
6742
  th { color: #94a3b8; font-weight: 600; }
@@ -6752,27 +6767,8 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
6752
6767
  <div class="card-title">${dict.metrics.ttfb}</div>
6753
6768
  <div class="card-value">${cards.ttfb.toFixed(1)}<span class="unit">ms</span></div>
6754
6769
  </div>
6755
- <div class="metric-card">
6756
- <div class="card-title">${dict.metrics.dns}</div>
6757
- <div class="card-value">${cards.dns.toFixed(2)}<span class="unit">ms</span></div>
6758
- </div>
6759
- <div class="metric-card">
6760
- <div class="card-title">${dict.metrics.tcp}</div>
6761
- <div class="card-value">${cards.tcp.toFixed(2)}<span class="unit">ms</span></div>
6762
- </div>
6763
- <div class="metric-card">
6764
- <div class="card-title">${dict.metrics.tls}</div>
6765
- <div class="card-value">${cards.tls.toFixed(2)}<span class="unit">ms</span></div>
6766
- </div>
6767
- <div class="metric-card">
6768
- <div class="card-title">${dict.metrics.stability}</div>
6769
- <div class="card-value">&plusmn;${cards.stdDev.toFixed(1)}<span class="unit">ms</span></div>
6770
- </div>
6771
6770
  </div>
6772
6771
 
6773
- ${baselineComparisonHtml}
6774
- ${crossBrowserMatrixSectionHtml}
6775
-
6776
6772
  <div class="top-layout-grid" style="margin-top: 2rem;">
6777
6773
  <div class="verdict-box">
6778
6774
  <div class="verdict-title">${dict.verdict}</div>
@@ -6785,101 +6781,37 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
6785
6781
  <div style="position: relative; width: 140px; height: 140px; display: flex; align-items: center; justify-content: center;">
6786
6782
  <svg width="140" height="140" viewBox="0 0 140 140" style="transform: rotate(-90deg);">
6787
6783
  <circle cx="70" cy="70" r="58" stroke="#1e293b" stroke-width="12" fill="transparent"/>
6788
- <circle cx="70" cy="70" r="58" stroke="${gaugeColor}" stroke-width="12" fill="transparent"
6784
+ <circle cx="70" cy="70" r="58" stroke="#10b981" stroke-width="12" fill="transparent"
6789
6785
  stroke-dasharray="364.4" stroke-dashoffset="${364.4 - 364.4 * cards.healthScore / 100}"
6790
6786
  stroke-linecap="round" style="transition: stroke-dashoffset 1s ease-out;"/>
6791
6787
  </svg>
6792
6788
  <div style="position: absolute; text-align: center;">
6793
6789
  <div style="font-size: 2.2rem; font-weight: 800; color: #ffffff; line-height: 1;">${cards.healthScore}</div>
6794
- <div style="font-size: 0.75rem; color: #64748b; font-weight: bold; margin-top: 0.2rem; text-transform: uppercase;">/ 100</div>
6795
6790
  </div>
6796
6791
  </div>
6797
- <div style="margin-top: 0.75rem; font-size: 0.8rem; color: #94a3b8; font-weight: 500;">
6798
- ${dict.health.subtitle}
6799
- </div>
6800
6792
  </div>
6801
6793
 
6802
6794
  <div class="matrix-box">
6803
- <div class="matrix-title">${dict.matrix.title}</div>
6795
+ <div class="matrix-title">${dict.matrix.title} <button onclick="window.print()" class="export-btn">PDF</button></div>
6804
6796
  <div class="matrix-row">
6805
- <div class="matrix-label">${dict.matrix.info} <span class="matrix-badge badge-1xx">1xx</span></div>
6797
+ <div class="matrix-label">${dict.matrix.info}</div>
6806
6798
  <div class="matrix-value">${responseMatrix.total1xx}</div>
6807
6799
  </div>
6808
6800
  <div class="matrix-row">
6809
- <div class="matrix-label">${dict.matrix.success} <span class="matrix-badge badge-2xx">2xx</span></div>
6801
+ <div class="matrix-label">${dict.matrix.success}</div>
6810
6802
  <div class="matrix-value">${responseMatrix.total2xx}</div>
6811
6803
  </div>
6812
- <div class="matrix-row">
6813
- <div class="matrix-label">${dict.matrix.redirects} <span class="matrix-badge badge-3xx">3xx</span></div>
6814
- <div class="matrix-value">${responseMatrix.total3xx}</div>
6815
- </div>
6816
- <div class="matrix-row">
6817
- <div class="matrix-label">${dict.matrix.clientErr} <span class="matrix-badge badge-4xx">4xx</span></div>
6818
- <div class="matrix-value">${responseMatrix.total4xx}</div>
6819
- </div>
6820
- <div class="matrix-row">
6821
- <div class="matrix-label">${dict.matrix.serverErr} <span class="matrix-badge badge-5xx">5xx</span></div>
6822
- <div class="matrix-value">${responseMatrix.total5xx}</div>
6823
- </div>
6824
-
6825
- <div class="matrix-title" style="margin-top: 1.5rem; font-size: 0.85rem; border-top: 1px solid #1e293b; padding-top: 1rem; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">
6826
- ${dict.matrix.breakdown}
6827
- </div>
6828
- <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem; margin-top: 0.5rem;">
6829
- ${breakdownGridHtml}
6830
- </div>
6831
- </div>
6832
- </div>
6833
-
6834
- <div class="card" style="margin-top: 2rem; background: #0c172b; border: 1px solid #7c3aed;">
6835
- <h3 style="color: #a78bfa; display: flex; align-items: center; gap: 0.6rem; font-size: 1.2rem; border-bottom: 1px solid #1e293b; padding-bottom: 0.5rem;">
6836
- ${dict.rca}
6837
- </h3>
6838
- <div style="font-size: 0.95rem; line-height: 1.6; color: #cbd5e1; padding: 0.5rem 0; white-space: pre-wrap; word-break: break-word;">
6839
- ${geminiAnalysisHtml}
6840
6804
  </div>
6841
6805
  </div>
6842
6806
 
6843
- <div class="charts-grid" style="margin-top: 2rem;">
6844
- <div class="graph-card">
6845
- <div class="graph-title">${dict.charts.volume}</div>
6846
- <div style="height: 280px; position: relative;">
6847
- <canvas id="volumeChart"></canvas>
6848
- </div>
6849
- </div>
6850
-
6851
- <div class="graph-card">
6852
- <div class="graph-title">${dict.charts.concurrency}</div>
6853
- <div style="height: 280px; position: relative;">
6854
- <canvas id="concurrencyChart"></canvas>
6855
- </div>
6856
- </div>
6857
-
6858
- <div class="graph-card">
6859
- <div class="graph-title">${dict.charts.status}</div>
6860
- <div style="height: 280px; position: relative;">
6861
- <canvas id="statusDonutChart"></canvas>
6862
- </div>
6863
- </div>
6864
-
6865
- <div class="graph-card">
6866
- <div class="graph-title">${dict.charts.scatter}</div>
6867
- <div style="height: 280px; position: relative;">
6868
- <canvas id="ttfbScatterChart"></canvas>
6869
- </div>
6870
- </div>
6871
- </div>
6872
-
6873
6807
  <div class="card" style="margin-top: 2rem;">
6874
- <h3>${dict.telemetry.title}</h3>
6808
+ <h3>${dict.telemetry.title} <span id="telemetry-export"></span></h3>
6875
6809
  <table>
6876
6810
  <thead>
6877
6811
  <tr>
6878
6812
  <th>${dict.telemetry.threads}</th>
6879
6813
  <th>${dict.telemetry.throughput}</th>
6880
6814
  <th>${dict.telemetry.avgLatency}</th>
6881
- <th>${dict.telemetry.p95}</th>
6882
- <th>${dict.telemetry.errors}</th>
6883
6815
  <th>${dict.telemetry.status}</th>
6884
6816
  </tr>
6885
6817
  </thead>
@@ -6887,149 +6819,51 @@ function exportHtmlDashboard(history, config, verdictReason, semanticReport, res
6887
6819
  ${history.map((h) => {
6888
6820
  const isPassed = h.apdex >= config.targetApdex && h.errorRate <= config.targetErrorRate;
6889
6821
  return `<tr>
6890
- <td><strong>${h.threads}</strong></td>
6822
+ <td>${h.threads}</td>
6891
6823
  <td>${h.throughput.toFixed(0)} /s</td>
6892
6824
  <td>${h.avgLatency.toFixed(1)} ms</td>
6893
- <td>${h.p95Latency.toFixed(1)} ms</td>
6894
- <td>${(h.errorRate * 100).toFixed(2)}%</td>
6895
6825
  <td><span class="badge ${isPassed ? "badge-success" : "badge-fail"}">${isPassed ? dict.telemetry.pass : dict.telemetry.fail}</span></td>
6896
6826
  </tr>`;
6897
6827
  }).join("")}
6898
6828
  </tbody>
6899
6829
  </table>
6900
6830
  </div>
6901
- ${logClustersHtml}
6902
6831
  </div>
6903
-
6904
- <script>
6905
- const labels = ${JSON.stringify(labels)};
6906
- const successRequestsData = ${JSON.stringify(successRequestsData)};
6907
- const failedWorkloadsData = ${JSON.stringify(failedWorkloadsData)};
6908
-
6909
- new Chart(document.getElementById('volumeChart'), {
6910
- type: 'bar',
6911
- data: {
6912
- labels: labels,
6913
- datasets: [
6914
- {
6915
- label: '${dict.matrix.success}',
6916
- data: successRequestsData,
6917
- backgroundColor: '#10b981',
6918
- stack: 'requests',
6919
- barPercentage: 0.95,
6920
- categoryPercentage: 0.95
6921
- },
6922
- {
6923
- label: '${dict.matrix.serverErr}',
6924
- data: failedWorkloadsData,
6925
- backgroundColor: '#ef4444',
6926
- stack: 'requests',
6927
- barPercentage: 0.95,
6928
- categoryPercentage: 0.95
6929
- }
6930
- ]
6931
- },
6932
- options: {
6933
- responsive: true,
6934
- maintainAspectRatio: false,
6935
- plugins: {
6936
- legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
6937
- },
6938
- scales: {
6939
- x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
6940
- y: { stacked: true, grid: { color: '#1e293b' }, ticks: { color: '#64748b' } }
6941
- }
6942
- }
6943
- });
6944
6832
 
6945
- const baseActiveThreads = ${JSON.stringify(activeThreadsData)};
6946
- const baseTtfTrend = ${JSON.stringify(ttfTrendData)};
6947
-
6948
- new Chart(document.getElementById('concurrencyChart'), {
6949
- type: 'line',
6950
- data: {
6951
- labels: labels,
6952
- datasets: [
6953
- {
6954
- label: 'VUs',
6955
- data: baseActiveThreads,
6956
- borderColor: '#38bdf8',
6957
- backgroundColor: 'rgba(56, 189, 248, 0.1)',
6958
- borderWidth: 2.5,
6959
- pointRadius: 4,
6960
- fill: true,
6961
- yAxisID: 'yThreads'
6962
- },
6963
- {
6964
- label: 'TTF',
6965
- data: baseTtfTrend,
6966
- borderColor: '#f43f5e',
6967
- borderWidth: 2,
6968
- borderDash: [5, 5],
6969
- pointRadius: 3,
6970
- fill: false,
6971
- yAxisID: 'yTtf'
6972
- }
6973
- ]
6974
- },
6975
- options: {
6976
- responsive: true,
6977
- maintainAspectRatio: false,
6978
- plugins: {
6979
- legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
6980
- },
6981
- scales: {
6982
- x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
6983
- yThreads: { position: 'left', grid: { color: '#1e293b' }, ticks: { color: '#38bdf8' } },
6984
- yTtf: { position: 'right', grid: { drawOnChartArea: false }, ticks: { color: '#f43f5e' } }
6985
- }
6986
- }
6987
- });
6833
+ <script>
6834
+ // --- Export Data Logic ---
6835
+ function downloadBlob(content, fileName, contentType) {
6836
+ const a = document.createElement("a");
6837
+ const file = new Blob([content], { type: contentType });
6838
+ a.href = URL.createObjectURL(file);
6839
+ a.download = fileName;
6840
+ a.click();
6841
+ }
6988
6842
 
6989
- new Chart(document.getElementById('statusDonutChart'), {
6990
- type: 'doughnut',
6991
- data: {
6992
- labels: ['1xx', '2xx', '3xx', '4xx', '5xx'],
6993
- datasets: [{
6994
- data: [${responseMatrix.total1xx}, ${responseMatrix.total2xx}, ${responseMatrix.total3xx}, ${responseMatrix.total4xx}, ${responseMatrix.total5xx}],
6995
- backgroundColor: ['#38bdf8', '#4ade80', '#cbd5e1', '#fde047', '#f87171'],
6996
- borderWidth: 1,
6997
- borderColor: '#1e293b'
6998
- }]
6999
- },
7000
- options: {
7001
- responsive: true,
7002
- maintainAspectRatio: false,
7003
- plugins: {
7004
- legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } },
7005
- cutout: '65%'
6843
+ function exportToCSV(data, filename) {
6844
+ if (!data || data.length === 0) return;
6845
+ const csvRows = [];
6846
+ const headers = Object.keys(data[0]);
6847
+ csvRows.push(headers.join(','));
6848
+ for (const row of data) {
6849
+ csvRows.push(headers.map(h => JSON.stringify(row[h] || '')).join(','));
7006
6850
  }
7007
- }
7008
- });
6851
+ downloadBlob(csvRows.join('\\n'), filename + '.csv', 'text/csv');
6852
+ }
7009
6853
 
7010
- new Chart(document.getElementById('ttfbScatterChart'), {
7011
- type: 'scatter',
7012
- data: {
7013
- datasets: [{
7014
- label: 'Delay',
7015
- data: ${JSON.stringify(scatterPoints)},
7016
- backgroundColor: '#a855f7',
7017
- pointRadius: 4,
7018
- pointHoverRadius: 6
7019
- }]
7020
- },
7021
- options: {
7022
- responsive: true,
7023
- maintainAspectRatio: false,
7024
- plugins: {
7025
- legend: { position: 'top', labels: { color: '#94a3b8', font: { size: 11 } } }
7026
- },
7027
- scales: {
7028
- x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
7029
- y: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } }
7030
- }
7031
- }
7032
- });
6854
+ function exportToJSON(data, filename) {
6855
+ downloadBlob(JSON.stringify(data, null, 2), filename + '.json', 'application/json');
6856
+ }
6857
+
6858
+ // Inject dynamic data for export
6859
+ const dashboardHistoryData = ${JSON.stringify(history)};
6860
+
6861
+ // Create export buttons for table
6862
+ const tableExportContainer = document.getElementById('telemetry-export');
6863
+ tableExportContainer.innerHTML = \`
6864
+ <button onclick="exportToCSV(dashboardHistoryData, 'telemetry_data')" class="export-btn">CSV</button>
6865
+ <button onclick="exportToJSON(dashboardHistoryData, 'telemetry_data')" class="export-btn">JSON</button>
6866
+ \`;
7033
6867
  </script></body></html>`;
7034
6868
  try {
7035
6869
  fs.writeFileSync(config.outputHtml, htmlContent, "utf-8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blaze-performance-tester",
3
- "version": "3.2.26",
3
+ "version": "3.2.28",
4
4
  "description": "A high-performance, multi-threaded load testing engine built with Rust and QuickJS.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",