@testsmith/perfornium 0.1.0

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 (164) hide show
  1. package/README.md +360 -0
  2. package/dist/cli/cli.d.ts +2 -0
  3. package/dist/cli/cli.js +192 -0
  4. package/dist/cli/commands/distributed.d.ts +11 -0
  5. package/dist/cli/commands/distributed.js +179 -0
  6. package/dist/cli/commands/import.d.ts +23 -0
  7. package/dist/cli/commands/import.js +461 -0
  8. package/dist/cli/commands/init.d.ts +7 -0
  9. package/dist/cli/commands/init.js +923 -0
  10. package/dist/cli/commands/mock.d.ts +7 -0
  11. package/dist/cli/commands/mock.js +281 -0
  12. package/dist/cli/commands/report.d.ts +5 -0
  13. package/dist/cli/commands/report.js +70 -0
  14. package/dist/cli/commands/run.d.ts +12 -0
  15. package/dist/cli/commands/run.js +260 -0
  16. package/dist/cli/commands/validate.d.ts +3 -0
  17. package/dist/cli/commands/validate.js +35 -0
  18. package/dist/cli/commands/worker.d.ts +27 -0
  19. package/dist/cli/commands/worker.js +320 -0
  20. package/dist/config/index.d.ts +2 -0
  21. package/dist/config/index.js +20 -0
  22. package/dist/config/parser.d.ts +19 -0
  23. package/dist/config/parser.js +330 -0
  24. package/dist/config/types/global-config.d.ts +74 -0
  25. package/dist/config/types/global-config.js +2 -0
  26. package/dist/config/types/hooks.d.ts +58 -0
  27. package/dist/config/types/hooks.js +3 -0
  28. package/dist/config/types/import-types.d.ts +33 -0
  29. package/dist/config/types/import-types.js +2 -0
  30. package/dist/config/types/index.d.ts +11 -0
  31. package/dist/config/types/index.js +27 -0
  32. package/dist/config/types/load-config.d.ts +32 -0
  33. package/dist/config/types/load-config.js +9 -0
  34. package/dist/config/types/output-config.d.ts +10 -0
  35. package/dist/config/types/output-config.js +2 -0
  36. package/dist/config/types/report-config.d.ts +10 -0
  37. package/dist/config/types/report-config.js +2 -0
  38. package/dist/config/types/runtime-types.d.ts +6 -0
  39. package/dist/config/types/runtime-types.js +2 -0
  40. package/dist/config/types/scenario-config.d.ts +30 -0
  41. package/dist/config/types/scenario-config.js +2 -0
  42. package/dist/config/types/step-types.d.ts +139 -0
  43. package/dist/config/types/step-types.js +2 -0
  44. package/dist/config/types/test-configuration.d.ts +18 -0
  45. package/dist/config/types/test-configuration.js +2 -0
  46. package/dist/config/types/worker-config.d.ts +12 -0
  47. package/dist/config/types/worker-config.js +2 -0
  48. package/dist/config/validator.d.ts +19 -0
  49. package/dist/config/validator.js +198 -0
  50. package/dist/core/csv-data-provider.d.ts +47 -0
  51. package/dist/core/csv-data-provider.js +265 -0
  52. package/dist/core/hooks-manager.d.ts +33 -0
  53. package/dist/core/hooks-manager.js +129 -0
  54. package/dist/core/index.d.ts +5 -0
  55. package/dist/core/index.js +11 -0
  56. package/dist/core/script-executor.d.ts +14 -0
  57. package/dist/core/script-executor.js +290 -0
  58. package/dist/core/step-executor.d.ts +41 -0
  59. package/dist/core/step-executor.js +680 -0
  60. package/dist/core/test-runner.d.ts +34 -0
  61. package/dist/core/test-runner.js +465 -0
  62. package/dist/core/threshold-evaluator.d.ts +43 -0
  63. package/dist/core/threshold-evaluator.js +170 -0
  64. package/dist/core/virtual-user-pool.d.ts +42 -0
  65. package/dist/core/virtual-user-pool.js +136 -0
  66. package/dist/core/virtual-user.d.ts +51 -0
  67. package/dist/core/virtual-user.js +488 -0
  68. package/dist/distributed/coordinator.d.ts +34 -0
  69. package/dist/distributed/coordinator.js +158 -0
  70. package/dist/distributed/health-monitor.d.ts +18 -0
  71. package/dist/distributed/health-monitor.js +72 -0
  72. package/dist/distributed/load-distributor.d.ts +17 -0
  73. package/dist/distributed/load-distributor.js +106 -0
  74. package/dist/distributed/remote-worker.d.ts +37 -0
  75. package/dist/distributed/remote-worker.js +241 -0
  76. package/dist/distributed/result-aggregator.d.ts +43 -0
  77. package/dist/distributed/result-aggregator.js +146 -0
  78. package/dist/dsl/index.d.ts +3 -0
  79. package/dist/dsl/index.js +11 -0
  80. package/dist/dsl/test-builder.d.ts +111 -0
  81. package/dist/dsl/test-builder.js +514 -0
  82. package/dist/importers/har-importer.d.ts +17 -0
  83. package/dist/importers/har-importer.js +172 -0
  84. package/dist/importers/open-api-importer.d.ts +23 -0
  85. package/dist/importers/open-api-importer.js +181 -0
  86. package/dist/importers/wsdl-importer.d.ts +42 -0
  87. package/dist/importers/wsdl-importer.js +440 -0
  88. package/dist/index.d.ts +5 -0
  89. package/dist/index.js +17 -0
  90. package/dist/load-patterns/arrivals.d.ts +7 -0
  91. package/dist/load-patterns/arrivals.js +118 -0
  92. package/dist/load-patterns/base.d.ts +9 -0
  93. package/dist/load-patterns/base.js +2 -0
  94. package/dist/load-patterns/basic.d.ts +7 -0
  95. package/dist/load-patterns/basic.js +117 -0
  96. package/dist/load-patterns/stepping.d.ts +6 -0
  97. package/dist/load-patterns/stepping.js +122 -0
  98. package/dist/metrics/collector.d.ts +72 -0
  99. package/dist/metrics/collector.js +662 -0
  100. package/dist/metrics/types.d.ts +135 -0
  101. package/dist/metrics/types.js +2 -0
  102. package/dist/outputs/base.d.ts +7 -0
  103. package/dist/outputs/base.js +2 -0
  104. package/dist/outputs/csv.d.ts +13 -0
  105. package/dist/outputs/csv.js +163 -0
  106. package/dist/outputs/graphite.d.ts +13 -0
  107. package/dist/outputs/graphite.js +126 -0
  108. package/dist/outputs/influxdb.d.ts +12 -0
  109. package/dist/outputs/influxdb.js +82 -0
  110. package/dist/outputs/json.d.ts +14 -0
  111. package/dist/outputs/json.js +107 -0
  112. package/dist/outputs/streaming-csv.d.ts +37 -0
  113. package/dist/outputs/streaming-csv.js +254 -0
  114. package/dist/outputs/streaming-json.d.ts +43 -0
  115. package/dist/outputs/streaming-json.js +353 -0
  116. package/dist/outputs/webhook.d.ts +16 -0
  117. package/dist/outputs/webhook.js +96 -0
  118. package/dist/protocols/base.d.ts +33 -0
  119. package/dist/protocols/base.js +2 -0
  120. package/dist/protocols/rest/handler.d.ts +67 -0
  121. package/dist/protocols/rest/handler.js +776 -0
  122. package/dist/protocols/soap/handler.d.ts +12 -0
  123. package/dist/protocols/soap/handler.js +165 -0
  124. package/dist/protocols/web/core-web-vitals.d.ts +121 -0
  125. package/dist/protocols/web/core-web-vitals.js +373 -0
  126. package/dist/protocols/web/handler.d.ts +50 -0
  127. package/dist/protocols/web/handler.js +706 -0
  128. package/dist/recorder/native-recorder.d.ts +14 -0
  129. package/dist/recorder/native-recorder.js +533 -0
  130. package/dist/recorder/scenario-recorder.d.ts +55 -0
  131. package/dist/recorder/scenario-recorder.js +296 -0
  132. package/dist/reporting/constants.d.ts +94 -0
  133. package/dist/reporting/constants.js +82 -0
  134. package/dist/reporting/enhanced-html-generator.d.ts +55 -0
  135. package/dist/reporting/enhanced-html-generator.js +965 -0
  136. package/dist/reporting/generator.d.ts +42 -0
  137. package/dist/reporting/generator.js +1217 -0
  138. package/dist/reporting/statistics.d.ts +144 -0
  139. package/dist/reporting/statistics.js +742 -0
  140. package/dist/reporting/templates/enhanced-report.hbs +2812 -0
  141. package/dist/reporting/templates/html.hbs +2453 -0
  142. package/dist/utils/faker-manager.d.ts +55 -0
  143. package/dist/utils/faker-manager.js +166 -0
  144. package/dist/utils/file-manager.d.ts +33 -0
  145. package/dist/utils/file-manager.js +154 -0
  146. package/dist/utils/handlebars-manager.d.ts +42 -0
  147. package/dist/utils/handlebars-manager.js +172 -0
  148. package/dist/utils/logger.d.ts +16 -0
  149. package/dist/utils/logger.js +46 -0
  150. package/dist/utils/template.d.ts +80 -0
  151. package/dist/utils/template.js +513 -0
  152. package/dist/utils/test-output-writer.d.ts +56 -0
  153. package/dist/utils/test-output-writer.js +643 -0
  154. package/dist/utils/time.d.ts +3 -0
  155. package/dist/utils/time.js +23 -0
  156. package/dist/utils/timestamp-helper.d.ts +17 -0
  157. package/dist/utils/timestamp-helper.js +53 -0
  158. package/dist/workers/manager.d.ts +18 -0
  159. package/dist/workers/manager.js +95 -0
  160. package/dist/workers/server.d.ts +21 -0
  161. package/dist/workers/server.js +205 -0
  162. package/dist/workers/worker.d.ts +19 -0
  163. package/dist/workers/worker.js +147 -0
  164. package/package.json +102 -0
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.JSONOutput = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ class JSONOutput {
40
+ constructor(filePath) {
41
+ this.results = [];
42
+ this.firstWrite = true;
43
+ this.filePath = filePath;
44
+ }
45
+ async initialize() {
46
+ // Ensure directory exists
47
+ const dir = path.dirname(this.filePath);
48
+ if (!fs.existsSync(dir)) {
49
+ fs.mkdirSync(dir, { recursive: true });
50
+ }
51
+ // Create file and write opening structure
52
+ this.fileStream = fs.createWriteStream(this.filePath, { flags: 'w' });
53
+ this.fileStream.write('{\n');
54
+ this.fileStream.write(' "metadata": {\n');
55
+ this.fileStream.write(' "version": "1.0.0",\n');
56
+ this.fileStream.write(` "generated_at": "${new Date().toISOString()}",\n`);
57
+ this.fileStream.write(' "test_status": "running"\n');
58
+ this.fileStream.write(' },\n');
59
+ this.fileStream.write(' "results": [\n');
60
+ this.firstWrite = true;
61
+ }
62
+ async writeResult(result) {
63
+ // Write incrementally to file during test
64
+ this.results.push(result); // Keep for summary calculation
65
+ if (!this.fileStream) {
66
+ throw new Error('JSON output not initialized');
67
+ }
68
+ // Add comma before all results except the first
69
+ if (!this.firstWrite) {
70
+ this.fileStream.write(',\n');
71
+ }
72
+ this.firstWrite = false;
73
+ // Write result as indented JSON
74
+ const resultJson = JSON.stringify(result, null, 2);
75
+ const indentedResult = resultJson.split('\n').map((line, idx) => {
76
+ if (idx === 0)
77
+ return ` ${line}`;
78
+ return ` ${line}`;
79
+ }).join('\n');
80
+ this.fileStream.write(indentedResult);
81
+ }
82
+ async writeSummary(summary) {
83
+ this.summary = summary;
84
+ }
85
+ async finalize() {
86
+ if (!this.fileStream)
87
+ return;
88
+ // Close results array
89
+ this.fileStream.write('\n ]');
90
+ // Write summary if available
91
+ if (this.summary) {
92
+ this.fileStream.write(',\n "summary": ');
93
+ const summaryJson = JSON.stringify(this.summary, null, 2);
94
+ const indentedSummary = summaryJson.split('\n').map((line, idx) => {
95
+ if (idx === 0)
96
+ return line;
97
+ return ` ${line}`;
98
+ }).join('\n');
99
+ this.fileStream.write(indentedSummary);
100
+ }
101
+ // Close root object
102
+ this.fileStream.write('\n}\n');
103
+ // Close file stream
104
+ this.fileStream.end();
105
+ }
106
+ }
107
+ exports.JSONOutput = JSONOutput;
@@ -0,0 +1,37 @@
1
+ import { OutputHandler } from './base';
2
+ import { TestResult, MetricsSummary } from '../metrics/types';
3
+ export interface StreamingCSVConfig {
4
+ batchSize?: number;
5
+ flushInterval?: number;
6
+ immediateWrite?: boolean;
7
+ rotateSize?: number;
8
+ maxFiles?: number;
9
+ }
10
+ export declare class StreamingCSVOutput implements OutputHandler {
11
+ private filePath;
12
+ private csvWriter;
13
+ private pendingResults;
14
+ private config;
15
+ private flushTimer?;
16
+ private fileStream?;
17
+ private headerWritten;
18
+ private totalResults;
19
+ private currentFileSize;
20
+ private rotationIndex;
21
+ private readonly DEFAULT_CONFIG;
22
+ constructor(filePath: string, config?: StreamingCSVConfig);
23
+ initialize(): Promise<void>;
24
+ writeResult(result: TestResult): Promise<void>;
25
+ private flushPending;
26
+ private rotateFile;
27
+ private cleanupOldFiles;
28
+ private extractRotationIndex;
29
+ writeSummary(summary: MetricsSummary): Promise<void>;
30
+ finalize(): Promise<void>;
31
+ getStats(): {
32
+ totalResults: number;
33
+ pendingResults: number;
34
+ currentFileSize: number;
35
+ rotationIndex: number;
36
+ };
37
+ }
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.StreamingCSVOutput = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const csv_writer_1 = require("csv-writer");
40
+ const file_manager_1 = require("../utils/file-manager");
41
+ const logger_1 = require("../utils/logger");
42
+ class StreamingCSVOutput {
43
+ constructor(filePath, config) {
44
+ this.pendingResults = [];
45
+ this.headerWritten = false;
46
+ this.totalResults = 0;
47
+ this.currentFileSize = 0;
48
+ this.rotationIndex = 0;
49
+ this.DEFAULT_CONFIG = {
50
+ batchSize: 100,
51
+ flushInterval: 5000,
52
+ immediateWrite: false,
53
+ rotateSize: 50 * 1024 * 1024, // 50MB
54
+ maxFiles: 5
55
+ };
56
+ this.filePath = filePath;
57
+ this.config = { ...this.DEFAULT_CONFIG, ...config };
58
+ }
59
+ async initialize() {
60
+ // Process template in file path
61
+ this.filePath = file_manager_1.FileManager.processFilePath(this.filePath);
62
+ // Ensure directory exists
63
+ const dir = path.dirname(this.filePath);
64
+ if (!fs.existsSync(dir)) {
65
+ fs.mkdirSync(dir, { recursive: true });
66
+ }
67
+ // Initialize CSV writer
68
+ this.csvWriter = (0, csv_writer_1.createObjectCsvWriter)({
69
+ path: this.filePath,
70
+ header: [
71
+ { id: 'timestamp', title: 'timestamp' },
72
+ { id: 'vu_id', title: 'vu_id' },
73
+ { id: 'iteration', title: 'iteration' },
74
+ { id: 'scenario', title: 'scenario' },
75
+ { id: 'name', title: 'request_name' },
76
+ { id: 'response_time', title: 'response_time_ms' },
77
+ { id: 'success', title: 'success' },
78
+ { id: 'status', title: 'http_status' },
79
+ { id: 'error', title: 'error' },
80
+ { id: 'response_size', title: 'response_size_bytes' },
81
+ { id: 'request_url', title: 'url' }
82
+ ]
83
+ });
84
+ // Start flush timer if configured
85
+ if (this.config.flushInterval > 0 && !this.config.immediateWrite) {
86
+ this.flushTimer = setInterval(() => {
87
+ this.flushPending();
88
+ }, this.config.flushInterval);
89
+ }
90
+ logger_1.logger.info(`📊 Streaming CSV output initialized: ${this.filePath}`);
91
+ logger_1.logger.debug(`📊 Config: batch=${this.config.batchSize}, flush=${this.config.flushInterval}ms`);
92
+ }
93
+ async writeResult(result) {
94
+ // Add processed timestamp and normalize data
95
+ const processedResult = {
96
+ ...result,
97
+ timestamp: result.timestamp || Date.now(),
98
+ response_time: result.response_time || result.duration || 0,
99
+ name: result.name || result.action || 'request',
100
+ iteration: result.iteration || 0
101
+ };
102
+ this.pendingResults.push(processedResult);
103
+ this.totalResults++;
104
+ if (this.config.immediateWrite) {
105
+ await this.flushPending();
106
+ }
107
+ else if (this.pendingResults.length >= this.config.batchSize) {
108
+ await this.flushPending();
109
+ }
110
+ }
111
+ async flushPending() {
112
+ if (this.pendingResults.length === 0)
113
+ return;
114
+ try {
115
+ // Check if we need to rotate the file
116
+ if (this.config.rotateSize > 0 && this.currentFileSize > this.config.rotateSize) {
117
+ await this.rotateFile();
118
+ }
119
+ // Write the batch
120
+ const recordsToWrite = [...this.pendingResults];
121
+ this.pendingResults = [];
122
+ await this.csvWriter.writeRecords(recordsToWrite);
123
+ // Update file size estimate
124
+ this.currentFileSize += recordsToWrite.length * 200; // Rough estimate
125
+ logger_1.logger.debug(`📊 Wrote ${recordsToWrite.length} results to CSV (total: ${this.totalResults})`);
126
+ }
127
+ catch (error) {
128
+ logger_1.logger.error('❌ Failed to write CSV batch:', error);
129
+ // Re-add results to pending for retry
130
+ this.pendingResults.unshift(...this.pendingResults);
131
+ }
132
+ }
133
+ async rotateFile() {
134
+ try {
135
+ // Close current file
136
+ if (this.fileStream) {
137
+ this.fileStream.end();
138
+ this.fileStream = undefined;
139
+ }
140
+ // Create rotated filename
141
+ const ext = path.extname(this.filePath);
142
+ const base = this.filePath.slice(0, -ext.length);
143
+ const rotatedPath = `${base}.${this.rotationIndex}${ext}`;
144
+ // Rename current file
145
+ if (fs.existsSync(this.filePath)) {
146
+ fs.renameSync(this.filePath, rotatedPath);
147
+ }
148
+ // Clean up old files if needed
149
+ if (this.config.maxFiles > 0) {
150
+ this.cleanupOldFiles(base, ext);
151
+ }
152
+ // Reset for new file
153
+ this.rotationIndex++;
154
+ this.currentFileSize = 0;
155
+ // Recreate CSV writer for new file
156
+ await this.initialize();
157
+ logger_1.logger.info(`🔄 Rotated CSV file: ${this.filePath}`);
158
+ }
159
+ catch (error) {
160
+ logger_1.logger.error('❌ Failed to rotate CSV file:', error);
161
+ }
162
+ }
163
+ cleanupOldFiles(basePath, extension) {
164
+ try {
165
+ const dir = path.dirname(basePath);
166
+ const basename = path.basename(basePath);
167
+ const files = fs.readdirSync(dir)
168
+ .filter(file => file.startsWith(basename) && file.endsWith(extension))
169
+ .map(file => ({
170
+ name: file,
171
+ path: path.join(dir, file),
172
+ index: this.extractRotationIndex(file)
173
+ }))
174
+ .filter(file => file.index >= 0)
175
+ .sort((a, b) => b.index - a.index);
176
+ // Remove excess files
177
+ const filesToRemove = files.slice(this.config.maxFiles);
178
+ for (const file of filesToRemove) {
179
+ fs.unlinkSync(file.path);
180
+ logger_1.logger.debug(`🗑️ Removed old CSV file: ${file.name}`);
181
+ }
182
+ }
183
+ catch (error) {
184
+ logger_1.logger.warn('⚠️ Failed to cleanup old CSV files:', error);
185
+ }
186
+ }
187
+ extractRotationIndex(filename) {
188
+ const match = filename.match(/\.(\d+)\.csv$/);
189
+ return match ? parseInt(match[1], 10) : -1;
190
+ }
191
+ async writeSummary(summary) {
192
+ // Write summary to separate file
193
+ const summaryPath = this.filePath.replace('.csv', '_summary.csv');
194
+ const summaryWriter = (0, csv_writer_1.createObjectCsvWriter)({
195
+ path: summaryPath,
196
+ header: [
197
+ { id: 'metric', title: 'metric' },
198
+ { id: 'value', title: 'value' },
199
+ { id: 'unit', title: 'unit' },
200
+ { id: 'timestamp', title: 'timestamp' }
201
+ ]
202
+ });
203
+ const timestamp = Date.now();
204
+ const summaryData = [
205
+ { metric: 'total_requests', value: summary.total_requests, unit: 'count', timestamp },
206
+ { metric: 'successful_requests', value: summary.successful_requests, unit: 'count', timestamp },
207
+ { metric: 'failed_requests', value: summary.failed_requests, unit: 'count', timestamp },
208
+ { metric: 'success_rate', value: summary.success_rate?.toFixed(2) || '0', unit: 'percent', timestamp },
209
+ { metric: 'avg_response_time', value: summary.avg_response_time?.toFixed(2) || '0', unit: 'ms', timestamp },
210
+ { metric: 'min_response_time', value: summary.min_response_time || 0, unit: 'ms', timestamp },
211
+ { metric: 'max_response_time', value: summary.max_response_time || 0, unit: 'ms', timestamp },
212
+ { metric: 'requests_per_second', value: summary.requests_per_second?.toFixed(2) || '0', unit: 'req/s', timestamp },
213
+ { metric: 'total_duration', value: summary.total_duration || 0, unit: 'ms', timestamp }
214
+ ];
215
+ // Add percentiles
216
+ if (summary.percentiles) {
217
+ Object.entries(summary.percentiles).forEach(([p, value]) => {
218
+ summaryData.push({
219
+ metric: `p${p}_response_time`,
220
+ value: typeof value === 'number' ? value.toFixed(2) : '0',
221
+ unit: 'ms',
222
+ timestamp
223
+ });
224
+ });
225
+ }
226
+ await summaryWriter.writeRecords(summaryData);
227
+ logger_1.logger.info(`📊 Written CSV summary: ${summaryPath}`);
228
+ }
229
+ async finalize() {
230
+ // Flush any remaining results
231
+ await this.flushPending();
232
+ // Clear flush timer
233
+ if (this.flushTimer) {
234
+ clearInterval(this.flushTimer);
235
+ this.flushTimer = undefined;
236
+ }
237
+ // Close file stream
238
+ if (this.fileStream) {
239
+ this.fileStream.end();
240
+ this.fileStream = undefined;
241
+ }
242
+ logger_1.logger.info(`📊 Streaming CSV finalized. Total results: ${this.totalResults}`);
243
+ }
244
+ // Real-time statistics
245
+ getStats() {
246
+ return {
247
+ totalResults: this.totalResults,
248
+ pendingResults: this.pendingResults.length,
249
+ currentFileSize: this.currentFileSize,
250
+ rotationIndex: this.rotationIndex
251
+ };
252
+ }
253
+ }
254
+ exports.StreamingCSVOutput = StreamingCSVOutput;
@@ -0,0 +1,43 @@
1
+ import { OutputHandler } from './base';
2
+ import { TestResult, MetricsSummary } from '../metrics/types';
3
+ export interface StreamingJSONConfig {
4
+ format?: 'ndjson' | 'json-array' | 'json-stream';
5
+ batchSize?: number;
6
+ flushInterval?: number;
7
+ prettify?: boolean;
8
+ includeMetadata?: boolean;
9
+ rotateSize?: number;
10
+ maxFiles?: number;
11
+ }
12
+ export declare class StreamingJSONOutput implements OutputHandler {
13
+ private filePath;
14
+ private config;
15
+ private pendingResults;
16
+ private fileStream?;
17
+ private flushTimer?;
18
+ private totalResults;
19
+ private currentFileSize;
20
+ private rotationIndex;
21
+ private isFirstWrite;
22
+ private testStartTime;
23
+ private readonly DEFAULT_CONFIG;
24
+ constructor(filePath: string, config?: StreamingJSONConfig);
25
+ initialize(): Promise<void>;
26
+ private writeHeader;
27
+ writeResult(result: TestResult): Promise<void>;
28
+ private flushPending;
29
+ private writeRecords;
30
+ private rotateFile;
31
+ private closeCurrentFile;
32
+ private cleanupOldFiles;
33
+ private extractRotationIndex;
34
+ writeSummary(summary: MetricsSummary): Promise<void>;
35
+ finalize(): Promise<void>;
36
+ getStats(): {
37
+ totalResults: number;
38
+ pendingResults: number;
39
+ currentFileSize: number;
40
+ rotationIndex: number;
41
+ format: string;
42
+ };
43
+ }