@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.
- package/README.md +360 -0
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.js +192 -0
- package/dist/cli/commands/distributed.d.ts +11 -0
- package/dist/cli/commands/distributed.js +179 -0
- package/dist/cli/commands/import.d.ts +23 -0
- package/dist/cli/commands/import.js +461 -0
- package/dist/cli/commands/init.d.ts +7 -0
- package/dist/cli/commands/init.js +923 -0
- package/dist/cli/commands/mock.d.ts +7 -0
- package/dist/cli/commands/mock.js +281 -0
- package/dist/cli/commands/report.d.ts +5 -0
- package/dist/cli/commands/report.js +70 -0
- package/dist/cli/commands/run.d.ts +12 -0
- package/dist/cli/commands/run.js +260 -0
- package/dist/cli/commands/validate.d.ts +3 -0
- package/dist/cli/commands/validate.js +35 -0
- package/dist/cli/commands/worker.d.ts +27 -0
- package/dist/cli/commands/worker.js +320 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +20 -0
- package/dist/config/parser.d.ts +19 -0
- package/dist/config/parser.js +330 -0
- package/dist/config/types/global-config.d.ts +74 -0
- package/dist/config/types/global-config.js +2 -0
- package/dist/config/types/hooks.d.ts +58 -0
- package/dist/config/types/hooks.js +3 -0
- package/dist/config/types/import-types.d.ts +33 -0
- package/dist/config/types/import-types.js +2 -0
- package/dist/config/types/index.d.ts +11 -0
- package/dist/config/types/index.js +27 -0
- package/dist/config/types/load-config.d.ts +32 -0
- package/dist/config/types/load-config.js +9 -0
- package/dist/config/types/output-config.d.ts +10 -0
- package/dist/config/types/output-config.js +2 -0
- package/dist/config/types/report-config.d.ts +10 -0
- package/dist/config/types/report-config.js +2 -0
- package/dist/config/types/runtime-types.d.ts +6 -0
- package/dist/config/types/runtime-types.js +2 -0
- package/dist/config/types/scenario-config.d.ts +30 -0
- package/dist/config/types/scenario-config.js +2 -0
- package/dist/config/types/step-types.d.ts +139 -0
- package/dist/config/types/step-types.js +2 -0
- package/dist/config/types/test-configuration.d.ts +18 -0
- package/dist/config/types/test-configuration.js +2 -0
- package/dist/config/types/worker-config.d.ts +12 -0
- package/dist/config/types/worker-config.js +2 -0
- package/dist/config/validator.d.ts +19 -0
- package/dist/config/validator.js +198 -0
- package/dist/core/csv-data-provider.d.ts +47 -0
- package/dist/core/csv-data-provider.js +265 -0
- package/dist/core/hooks-manager.d.ts +33 -0
- package/dist/core/hooks-manager.js +129 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +11 -0
- package/dist/core/script-executor.d.ts +14 -0
- package/dist/core/script-executor.js +290 -0
- package/dist/core/step-executor.d.ts +41 -0
- package/dist/core/step-executor.js +680 -0
- package/dist/core/test-runner.d.ts +34 -0
- package/dist/core/test-runner.js +465 -0
- package/dist/core/threshold-evaluator.d.ts +43 -0
- package/dist/core/threshold-evaluator.js +170 -0
- package/dist/core/virtual-user-pool.d.ts +42 -0
- package/dist/core/virtual-user-pool.js +136 -0
- package/dist/core/virtual-user.d.ts +51 -0
- package/dist/core/virtual-user.js +488 -0
- package/dist/distributed/coordinator.d.ts +34 -0
- package/dist/distributed/coordinator.js +158 -0
- package/dist/distributed/health-monitor.d.ts +18 -0
- package/dist/distributed/health-monitor.js +72 -0
- package/dist/distributed/load-distributor.d.ts +17 -0
- package/dist/distributed/load-distributor.js +106 -0
- package/dist/distributed/remote-worker.d.ts +37 -0
- package/dist/distributed/remote-worker.js +241 -0
- package/dist/distributed/result-aggregator.d.ts +43 -0
- package/dist/distributed/result-aggregator.js +146 -0
- package/dist/dsl/index.d.ts +3 -0
- package/dist/dsl/index.js +11 -0
- package/dist/dsl/test-builder.d.ts +111 -0
- package/dist/dsl/test-builder.js +514 -0
- package/dist/importers/har-importer.d.ts +17 -0
- package/dist/importers/har-importer.js +172 -0
- package/dist/importers/open-api-importer.d.ts +23 -0
- package/dist/importers/open-api-importer.js +181 -0
- package/dist/importers/wsdl-importer.d.ts +42 -0
- package/dist/importers/wsdl-importer.js +440 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +17 -0
- package/dist/load-patterns/arrivals.d.ts +7 -0
- package/dist/load-patterns/arrivals.js +118 -0
- package/dist/load-patterns/base.d.ts +9 -0
- package/dist/load-patterns/base.js +2 -0
- package/dist/load-patterns/basic.d.ts +7 -0
- package/dist/load-patterns/basic.js +117 -0
- package/dist/load-patterns/stepping.d.ts +6 -0
- package/dist/load-patterns/stepping.js +122 -0
- package/dist/metrics/collector.d.ts +72 -0
- package/dist/metrics/collector.js +662 -0
- package/dist/metrics/types.d.ts +135 -0
- package/dist/metrics/types.js +2 -0
- package/dist/outputs/base.d.ts +7 -0
- package/dist/outputs/base.js +2 -0
- package/dist/outputs/csv.d.ts +13 -0
- package/dist/outputs/csv.js +163 -0
- package/dist/outputs/graphite.d.ts +13 -0
- package/dist/outputs/graphite.js +126 -0
- package/dist/outputs/influxdb.d.ts +12 -0
- package/dist/outputs/influxdb.js +82 -0
- package/dist/outputs/json.d.ts +14 -0
- package/dist/outputs/json.js +107 -0
- package/dist/outputs/streaming-csv.d.ts +37 -0
- package/dist/outputs/streaming-csv.js +254 -0
- package/dist/outputs/streaming-json.d.ts +43 -0
- package/dist/outputs/streaming-json.js +353 -0
- package/dist/outputs/webhook.d.ts +16 -0
- package/dist/outputs/webhook.js +96 -0
- package/dist/protocols/base.d.ts +33 -0
- package/dist/protocols/base.js +2 -0
- package/dist/protocols/rest/handler.d.ts +67 -0
- package/dist/protocols/rest/handler.js +776 -0
- package/dist/protocols/soap/handler.d.ts +12 -0
- package/dist/protocols/soap/handler.js +165 -0
- package/dist/protocols/web/core-web-vitals.d.ts +121 -0
- package/dist/protocols/web/core-web-vitals.js +373 -0
- package/dist/protocols/web/handler.d.ts +50 -0
- package/dist/protocols/web/handler.js +706 -0
- package/dist/recorder/native-recorder.d.ts +14 -0
- package/dist/recorder/native-recorder.js +533 -0
- package/dist/recorder/scenario-recorder.d.ts +55 -0
- package/dist/recorder/scenario-recorder.js +296 -0
- package/dist/reporting/constants.d.ts +94 -0
- package/dist/reporting/constants.js +82 -0
- package/dist/reporting/enhanced-html-generator.d.ts +55 -0
- package/dist/reporting/enhanced-html-generator.js +965 -0
- package/dist/reporting/generator.d.ts +42 -0
- package/dist/reporting/generator.js +1217 -0
- package/dist/reporting/statistics.d.ts +144 -0
- package/dist/reporting/statistics.js +742 -0
- package/dist/reporting/templates/enhanced-report.hbs +2812 -0
- package/dist/reporting/templates/html.hbs +2453 -0
- package/dist/utils/faker-manager.d.ts +55 -0
- package/dist/utils/faker-manager.js +166 -0
- package/dist/utils/file-manager.d.ts +33 -0
- package/dist/utils/file-manager.js +154 -0
- package/dist/utils/handlebars-manager.d.ts +42 -0
- package/dist/utils/handlebars-manager.js +172 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +46 -0
- package/dist/utils/template.d.ts +80 -0
- package/dist/utils/template.js +513 -0
- package/dist/utils/test-output-writer.d.ts +56 -0
- package/dist/utils/test-output-writer.js +643 -0
- package/dist/utils/time.d.ts +3 -0
- package/dist/utils/time.js +23 -0
- package/dist/utils/timestamp-helper.d.ts +17 -0
- package/dist/utils/timestamp-helper.js +53 -0
- package/dist/workers/manager.d.ts +18 -0
- package/dist/workers/manager.js +95 -0
- package/dist/workers/server.d.ts +21 -0
- package/dist/workers/server.js +205 -0
- package/dist/workers/worker.d.ts +19 -0
- package/dist/workers/worker.js +147 -0
- package/package.json +102 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export interface TestResult {
|
|
2
|
+
shouldRecord?: any;
|
|
3
|
+
id?: string;
|
|
4
|
+
vu_id: number;
|
|
5
|
+
iteration?: number;
|
|
6
|
+
scenario: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
action?: string;
|
|
9
|
+
step_name?: string;
|
|
10
|
+
thread_name?: string;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
sample_start?: number;
|
|
13
|
+
duration?: number;
|
|
14
|
+
response_time?: number;
|
|
15
|
+
success: boolean;
|
|
16
|
+
status?: number;
|
|
17
|
+
status_text?: string;
|
|
18
|
+
error?: string;
|
|
19
|
+
error_code?: string;
|
|
20
|
+
response_size?: number;
|
|
21
|
+
response_headers?: Record<string, string>;
|
|
22
|
+
response_body?: string;
|
|
23
|
+
request_url?: string;
|
|
24
|
+
request_method?: string;
|
|
25
|
+
request_headers?: Record<string, string>;
|
|
26
|
+
request_body?: string;
|
|
27
|
+
connect_time?: number;
|
|
28
|
+
latency?: number;
|
|
29
|
+
sent_bytes?: number;
|
|
30
|
+
headers_size_sent?: number;
|
|
31
|
+
body_size_sent?: number;
|
|
32
|
+
headers_size_received?: number;
|
|
33
|
+
body_size_received?: number;
|
|
34
|
+
data_type?: 'text' | 'bin' | '';
|
|
35
|
+
custom_metrics?: Record<string, any>;
|
|
36
|
+
}
|
|
37
|
+
export interface VUStartEvent {
|
|
38
|
+
vu_id: number;
|
|
39
|
+
start_time: number;
|
|
40
|
+
load_pattern: string;
|
|
41
|
+
}
|
|
42
|
+
export interface StepStatistics {
|
|
43
|
+
step_name: string;
|
|
44
|
+
scenario: string;
|
|
45
|
+
total_requests: number;
|
|
46
|
+
successful_requests: number;
|
|
47
|
+
failed_requests: number;
|
|
48
|
+
success_rate: number;
|
|
49
|
+
avg_response_time: number;
|
|
50
|
+
min_response_time: number;
|
|
51
|
+
max_response_time: number;
|
|
52
|
+
percentiles: Record<number, number>;
|
|
53
|
+
response_times: number[];
|
|
54
|
+
error_distribution: Record<string, number>;
|
|
55
|
+
status_distribution: Record<number, number>;
|
|
56
|
+
}
|
|
57
|
+
export interface TestSummary {
|
|
58
|
+
total_requests: number;
|
|
59
|
+
successful_requests: number;
|
|
60
|
+
failed_requests: number;
|
|
61
|
+
success_rate: number;
|
|
62
|
+
avg_response_time: number;
|
|
63
|
+
min_response_time: number;
|
|
64
|
+
max_response_time: number;
|
|
65
|
+
percentiles: Record<string, number>;
|
|
66
|
+
requests_per_second: number;
|
|
67
|
+
bytes_per_second: number;
|
|
68
|
+
bytes_received?: number;
|
|
69
|
+
total_duration: number;
|
|
70
|
+
error_distribution?: Record<string, number>;
|
|
71
|
+
status_distribution: Record<number, number>;
|
|
72
|
+
error_details: ErrorDetail[];
|
|
73
|
+
}
|
|
74
|
+
export interface MetricsSummary {
|
|
75
|
+
total_requests: number;
|
|
76
|
+
successful_requests: number;
|
|
77
|
+
failed_requests: number;
|
|
78
|
+
success_rate: number;
|
|
79
|
+
avg_response_time: number;
|
|
80
|
+
min_response_time: number;
|
|
81
|
+
max_response_time: number;
|
|
82
|
+
percentiles: Record<number, number>;
|
|
83
|
+
requests_per_second: number;
|
|
84
|
+
bytes_per_second: number;
|
|
85
|
+
total_duration: number;
|
|
86
|
+
error_distribution: Record<string, number>;
|
|
87
|
+
status_distribution: Record<number, number>;
|
|
88
|
+
error_details: ErrorDetail[];
|
|
89
|
+
step_statistics: StepStatistics[];
|
|
90
|
+
vu_ramp_up: VUStartEvent[];
|
|
91
|
+
timeline_data: TimelineData[];
|
|
92
|
+
web_vitals_data?: {
|
|
93
|
+
lcp?: number;
|
|
94
|
+
fid?: number;
|
|
95
|
+
cls?: number;
|
|
96
|
+
fcp?: number;
|
|
97
|
+
ttfb?: number;
|
|
98
|
+
tti?: number;
|
|
99
|
+
tbt?: number;
|
|
100
|
+
speedIndex?: number;
|
|
101
|
+
};
|
|
102
|
+
vitals_score?: 'good' | 'needs-improvement' | 'poor';
|
|
103
|
+
vitals_details?: Record<string, {
|
|
104
|
+
value: number;
|
|
105
|
+
score: 'good' | 'needs-improvement' | 'poor';
|
|
106
|
+
}>;
|
|
107
|
+
verification_metrics?: {
|
|
108
|
+
total_verifications: number;
|
|
109
|
+
success_rate: number;
|
|
110
|
+
average_duration: number;
|
|
111
|
+
p95_duration: number;
|
|
112
|
+
slowest_step: any;
|
|
113
|
+
fastest_step: any;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export interface TimelineData {
|
|
117
|
+
timestamp: number;
|
|
118
|
+
time_label: string;
|
|
119
|
+
active_vus: number;
|
|
120
|
+
requests_count: number;
|
|
121
|
+
avg_response_time: number;
|
|
122
|
+
success_rate: number;
|
|
123
|
+
throughput: number;
|
|
124
|
+
}
|
|
125
|
+
export interface ErrorDetail {
|
|
126
|
+
timestamp: number;
|
|
127
|
+
vu_id: number;
|
|
128
|
+
scenario: string;
|
|
129
|
+
action: string;
|
|
130
|
+
status?: number;
|
|
131
|
+
error: string;
|
|
132
|
+
request_url?: string;
|
|
133
|
+
response_body?: string;
|
|
134
|
+
count: number;
|
|
135
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { OutputHandler } from './base';
|
|
2
|
+
import { TestResult, MetricsSummary } from '../metrics/types';
|
|
3
|
+
export declare class CSVOutput implements OutputHandler {
|
|
4
|
+
private filePath;
|
|
5
|
+
private csvWriter;
|
|
6
|
+
private results;
|
|
7
|
+
constructor(filePath: string);
|
|
8
|
+
initialize(): Promise<void>;
|
|
9
|
+
writeResult(result: TestResult): Promise<void>;
|
|
10
|
+
private transformResult;
|
|
11
|
+
writeSummary(summary: MetricsSummary): Promise<void>;
|
|
12
|
+
finalize(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
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.CSVOutput = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const csv_writer_1 = require("csv-writer");
|
|
40
|
+
class CSVOutput {
|
|
41
|
+
constructor(filePath) {
|
|
42
|
+
this.results = [];
|
|
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
|
+
this.csvWriter = (0, csv_writer_1.createObjectCsvWriter)({
|
|
52
|
+
path: this.filePath,
|
|
53
|
+
header: [
|
|
54
|
+
{ id: 'timestamp', title: 'timestamp' },
|
|
55
|
+
{ id: 'vu_id', title: 'vu_id' },
|
|
56
|
+
{ id: 'iteration', title: 'iteration' },
|
|
57
|
+
{ id: 'scenario', title: 'scenario' },
|
|
58
|
+
{ id: 'action', title: 'action' },
|
|
59
|
+
{ id: 'step_name', title: 'step_name' },
|
|
60
|
+
{ id: 'duration', title: 'duration_ms' },
|
|
61
|
+
{ id: 'success', title: 'success' },
|
|
62
|
+
{ id: 'status', title: 'http_status' },
|
|
63
|
+
{ id: 'error', title: 'error' },
|
|
64
|
+
{ id: 'response_size', title: 'response_size_bytes' },
|
|
65
|
+
// JMeter-style metrics
|
|
66
|
+
{ id: 'latency', title: 'latency_ms' },
|
|
67
|
+
{ id: 'connect_time', title: 'connect_time_ms' },
|
|
68
|
+
{ id: 'sent_bytes', title: 'sent_bytes' },
|
|
69
|
+
{ id: 'data_type', title: 'data_type' },
|
|
70
|
+
// Web Vitals columns
|
|
71
|
+
{ id: 'lcp', title: 'lcp_ms' },
|
|
72
|
+
{ id: 'cls', title: 'cls_score' },
|
|
73
|
+
{ id: 'inp', title: 'inp_ms' },
|
|
74
|
+
{ id: 'ttfb', title: 'ttfb_ms' },
|
|
75
|
+
{ id: 'fcp', title: 'fcp_ms' },
|
|
76
|
+
{ id: 'fid', title: 'fid_ms' },
|
|
77
|
+
{ id: 'vitals_score', title: 'vitals_score' },
|
|
78
|
+
// Verification metrics
|
|
79
|
+
{ id: 'verification_duration', title: 'verification_duration_ms' },
|
|
80
|
+
{ id: 'verification_success', title: 'verification_success' },
|
|
81
|
+
{ id: 'verification_step_name', title: 'verification_step_name' },
|
|
82
|
+
// Page info
|
|
83
|
+
{ id: 'page_url', title: 'page_url' },
|
|
84
|
+
{ id: 'page_title', title: 'page_title' }
|
|
85
|
+
]
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async writeResult(result) {
|
|
89
|
+
// Write immediately to file (incremental during test)
|
|
90
|
+
this.results.push(result); // Keep for finalize summary
|
|
91
|
+
// Transform and write single result
|
|
92
|
+
const record = this.transformResult(result);
|
|
93
|
+
await this.csvWriter.writeRecords([record]);
|
|
94
|
+
}
|
|
95
|
+
transformResult(result) {
|
|
96
|
+
return {
|
|
97
|
+
timestamp: result.timestamp,
|
|
98
|
+
vu_id: result.vu_id,
|
|
99
|
+
iteration: result.iteration,
|
|
100
|
+
scenario: result.scenario,
|
|
101
|
+
action: result.action,
|
|
102
|
+
step_name: result.step_name || result.action,
|
|
103
|
+
duration: result.duration,
|
|
104
|
+
success: result.success,
|
|
105
|
+
status: result.status,
|
|
106
|
+
error: result.error || '',
|
|
107
|
+
response_size: result.response_size || 0,
|
|
108
|
+
// JMeter-style metrics
|
|
109
|
+
latency: result.latency || 0,
|
|
110
|
+
connect_time: result.connect_time || 0,
|
|
111
|
+
sent_bytes: result.sent_bytes || 0,
|
|
112
|
+
data_type: result.data_type || '',
|
|
113
|
+
// Web Vitals
|
|
114
|
+
lcp: result.custom_metrics?.web_vitals?.lcp || null,
|
|
115
|
+
cls: result.custom_metrics?.web_vitals?.cls || null,
|
|
116
|
+
inp: result.custom_metrics?.web_vitals?.inp || null,
|
|
117
|
+
ttfb: result.custom_metrics?.web_vitals?.ttfb || null,
|
|
118
|
+
fcp: result.custom_metrics?.web_vitals?.fcp || null,
|
|
119
|
+
fid: result.custom_metrics?.web_vitals?.fid || null,
|
|
120
|
+
vitals_score: result.custom_metrics?.web_vitals?.score || null,
|
|
121
|
+
// Verification
|
|
122
|
+
verification_duration: result.custom_metrics?.verification?.duration || null,
|
|
123
|
+
verification_success: result.custom_metrics?.verification?.success || null,
|
|
124
|
+
verification_step_name: result.custom_metrics?.verification?.step_name || null,
|
|
125
|
+
// Page info
|
|
126
|
+
page_url: result.custom_metrics?.page_url || null,
|
|
127
|
+
page_title: result.custom_metrics?.page_title || null
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async writeSummary(summary) {
|
|
131
|
+
// Summary will be written to a separate file
|
|
132
|
+
const summaryPath = this.filePath.replace('.csv', '_summary.csv');
|
|
133
|
+
const summaryWriter = (0, csv_writer_1.createObjectCsvWriter)({
|
|
134
|
+
path: summaryPath,
|
|
135
|
+
header: [
|
|
136
|
+
{ id: 'metric', title: 'metric' },
|
|
137
|
+
{ id: 'value', title: 'value' },
|
|
138
|
+
{ id: 'unit', title: 'unit' }
|
|
139
|
+
]
|
|
140
|
+
});
|
|
141
|
+
const summaryData = [
|
|
142
|
+
{ metric: 'total_requests', value: summary.total_requests, unit: 'count' },
|
|
143
|
+
{ metric: 'successful_requests', value: summary.successful_requests, unit: 'count' },
|
|
144
|
+
{ metric: 'failed_requests', value: summary.failed_requests, unit: 'count' },
|
|
145
|
+
{ metric: 'success_rate', value: summary.success_rate.toFixed(2), unit: 'percent' },
|
|
146
|
+
{ metric: 'avg_response_time', value: summary.avg_response_time.toFixed(2), unit: 'ms' },
|
|
147
|
+
{ metric: 'min_response_time', value: summary.min_response_time, unit: 'ms' },
|
|
148
|
+
{ metric: 'max_response_time', value: summary.max_response_time, unit: 'ms' },
|
|
149
|
+
{ metric: 'requests_per_second', value: summary.requests_per_second.toFixed(2), unit: 'req/s' },
|
|
150
|
+
{ metric: 'total_duration', value: summary.total_duration, unit: 'ms' },
|
|
151
|
+
{ metric: 'p50_response_time', value: summary.percentiles[50] || 0, unit: 'ms' },
|
|
152
|
+
{ metric: 'p90_response_time', value: summary.percentiles[90] || 0, unit: 'ms' },
|
|
153
|
+
{ metric: 'p95_response_time', value: summary.percentiles[95] || 0, unit: 'ms' },
|
|
154
|
+
{ metric: 'p99_response_time', value: summary.percentiles[99] || 0, unit: 'ms' }
|
|
155
|
+
];
|
|
156
|
+
await summaryWriter.writeRecords(summaryData);
|
|
157
|
+
}
|
|
158
|
+
async finalize() {
|
|
159
|
+
// Results already written incrementally during test
|
|
160
|
+
// No need to write them again
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.CSVOutput = CSVOutput;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { OutputHandler } from './base';
|
|
2
|
+
import { TestResult, MetricsSummary } from '../metrics/types';
|
|
3
|
+
export declare class GraphiteOutput implements OutputHandler {
|
|
4
|
+
private host;
|
|
5
|
+
private port;
|
|
6
|
+
private prefix;
|
|
7
|
+
private client?;
|
|
8
|
+
constructor(host?: string, port?: number, prefix?: string);
|
|
9
|
+
initialize(): Promise<void>;
|
|
10
|
+
writeResult(result: TestResult): Promise<void>;
|
|
11
|
+
writeSummary(summary: MetricsSummary): Promise<void>;
|
|
12
|
+
finalize(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
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.GraphiteOutput = void 0;
|
|
37
|
+
const net = __importStar(require("net"));
|
|
38
|
+
const logger_1 = require("../utils/logger");
|
|
39
|
+
class GraphiteOutput {
|
|
40
|
+
constructor(host = 'localhost', port = 2003, prefix = 'perfornium') {
|
|
41
|
+
this.host = host;
|
|
42
|
+
this.port = port;
|
|
43
|
+
this.prefix = prefix;
|
|
44
|
+
}
|
|
45
|
+
async initialize() {
|
|
46
|
+
try {
|
|
47
|
+
this.client = new net.Socket();
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const timeout = setTimeout(() => {
|
|
50
|
+
reject(new Error('Graphite connection timeout'));
|
|
51
|
+
}, 5000);
|
|
52
|
+
this.client.connect(this.port, this.host, () => {
|
|
53
|
+
clearTimeout(timeout);
|
|
54
|
+
logger_1.logger.debug(`📊 Connected to Graphite at ${this.host}:${this.port}`);
|
|
55
|
+
resolve();
|
|
56
|
+
});
|
|
57
|
+
this.client.on('error', (error) => {
|
|
58
|
+
clearTimeout(timeout);
|
|
59
|
+
logger_1.logger.warn('⚠️ Graphite connection error:', error);
|
|
60
|
+
reject(error);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
logger_1.logger.warn('⚠️ Failed to connect to Graphite:', error);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async writeResult(result) {
|
|
70
|
+
if (!this.client || this.client.destroyed) {
|
|
71
|
+
return; // Skip if not connected
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const timestamp = Math.floor(result.timestamp / 1000);
|
|
75
|
+
const metricPrefix = `${this.prefix}.${result.scenario}.${result.action}`;
|
|
76
|
+
const metrics = [
|
|
77
|
+
`${metricPrefix}.duration ${result.duration} ${timestamp}`,
|
|
78
|
+
`${metricPrefix}.success ${result.success ? 1 : 0} ${timestamp}`
|
|
79
|
+
];
|
|
80
|
+
if (result.status) {
|
|
81
|
+
metrics.push(`${metricPrefix}.status ${result.status} ${timestamp}`);
|
|
82
|
+
}
|
|
83
|
+
if (result.response_size) {
|
|
84
|
+
metrics.push(`${metricPrefix}.response_size ${result.response_size} ${timestamp}`);
|
|
85
|
+
}
|
|
86
|
+
const data = metrics.join('\n') + '\n';
|
|
87
|
+
this.client.write(data);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
logger_1.logger.warn('⚠️ Failed to write to Graphite:', error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async writeSummary(summary) {
|
|
94
|
+
if (!this.client || this.client.destroyed) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
99
|
+
const summaryPrefix = `${this.prefix}.summary`;
|
|
100
|
+
const metrics = [
|
|
101
|
+
`${summaryPrefix}.total_requests ${summary.total_requests} ${timestamp}`,
|
|
102
|
+
`${summaryPrefix}.successful_requests ${summary.successful_requests} ${timestamp}`,
|
|
103
|
+
`${summaryPrefix}.failed_requests ${summary.failed_requests} ${timestamp}`,
|
|
104
|
+
`${summaryPrefix}.success_rate ${summary.success_rate} ${timestamp}`,
|
|
105
|
+
`${summaryPrefix}.avg_response_time ${summary.avg_response_time} ${timestamp}`,
|
|
106
|
+
`${summaryPrefix}.requests_per_second ${summary.requests_per_second} ${timestamp}`,
|
|
107
|
+
`${summaryPrefix}.total_duration ${summary.total_duration} ${timestamp}`
|
|
108
|
+
];
|
|
109
|
+
// Add percentiles
|
|
110
|
+
Object.entries(summary.percentiles).forEach(([percentile, value]) => {
|
|
111
|
+
metrics.push(`${summaryPrefix}.p${percentile} ${value} ${timestamp}`);
|
|
112
|
+
});
|
|
113
|
+
const data = metrics.join('\n') + '\n';
|
|
114
|
+
this.client.write(data);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
logger_1.logger.warn('⚠️ Failed to write summary to Graphite:', error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async finalize() {
|
|
121
|
+
if (this.client && !this.client.destroyed) {
|
|
122
|
+
this.client.end();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.GraphiteOutput = GraphiteOutput;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { OutputHandler } from './base';
|
|
2
|
+
import { TestResult, MetricsSummary } from '../metrics/types';
|
|
3
|
+
export declare class InfluxDBOutput implements OutputHandler {
|
|
4
|
+
private client;
|
|
5
|
+
private database;
|
|
6
|
+
private tags;
|
|
7
|
+
constructor(url: string, database: string, tags?: Record<string, string>);
|
|
8
|
+
initialize(): Promise<void>;
|
|
9
|
+
writeResult(result: TestResult): Promise<void>;
|
|
10
|
+
writeSummary(summary: MetricsSummary): Promise<void>;
|
|
11
|
+
finalize(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InfluxDBOutput = void 0;
|
|
4
|
+
const influx_1 = require("influx");
|
|
5
|
+
const logger_1 = require("../utils/logger");
|
|
6
|
+
class InfluxDBOutput {
|
|
7
|
+
constructor(url, database, tags = {}) {
|
|
8
|
+
this.client = new influx_1.InfluxDB(url);
|
|
9
|
+
this.database = database;
|
|
10
|
+
this.tags = tags;
|
|
11
|
+
}
|
|
12
|
+
async initialize() {
|
|
13
|
+
try {
|
|
14
|
+
const databases = await this.client.getDatabaseNames();
|
|
15
|
+
if (!databases.includes(this.database)) {
|
|
16
|
+
await this.client.createDatabase(this.database);
|
|
17
|
+
}
|
|
18
|
+
logger_1.logger.debug(`📊 InfluxDB output initialized: ${this.database}`);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
logger_1.logger.warn('⚠️ Could not initialize InfluxDB:', error);
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async writeResult(result) {
|
|
26
|
+
try {
|
|
27
|
+
const point = {
|
|
28
|
+
measurement: 'performance_test',
|
|
29
|
+
tags: {
|
|
30
|
+
...this.tags,
|
|
31
|
+
scenario: result.scenario,
|
|
32
|
+
action: result.action,
|
|
33
|
+
success: result.success.toString(),
|
|
34
|
+
vu_id: result.vu_id.toString()
|
|
35
|
+
},
|
|
36
|
+
fields: {
|
|
37
|
+
duration: result.duration,
|
|
38
|
+
response_size: result.response_size || 0,
|
|
39
|
+
status: result.status || 0
|
|
40
|
+
},
|
|
41
|
+
timestamp: new Date(result.timestamp)
|
|
42
|
+
};
|
|
43
|
+
if (result.error) {
|
|
44
|
+
point.tags.error = result.error;
|
|
45
|
+
}
|
|
46
|
+
await this.client.writePoints([point], { database: this.database });
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
logger_1.logger.warn('⚠️ Could not write to InfluxDB:', error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async writeSummary(summary) {
|
|
53
|
+
try {
|
|
54
|
+
const summaryPoints = [
|
|
55
|
+
{
|
|
56
|
+
measurement: 'test_summary',
|
|
57
|
+
tags: this.tags,
|
|
58
|
+
fields: {
|
|
59
|
+
total_requests: summary.total_requests,
|
|
60
|
+
successful_requests: summary.successful_requests,
|
|
61
|
+
failed_requests: summary.failed_requests,
|
|
62
|
+
success_rate: summary.success_rate,
|
|
63
|
+
avg_response_time: summary.avg_response_time,
|
|
64
|
+
min_response_time: summary.min_response_time,
|
|
65
|
+
max_response_time: summary.max_response_time,
|
|
66
|
+
requests_per_second: summary.requests_per_second,
|
|
67
|
+
total_duration: summary.total_duration
|
|
68
|
+
},
|
|
69
|
+
timestamp: new Date()
|
|
70
|
+
}
|
|
71
|
+
];
|
|
72
|
+
await this.client.writePoints(summaryPoints, { database: this.database });
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
logger_1.logger.warn('⚠️ Could not write summary to InfluxDB:', error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async finalize() {
|
|
79
|
+
// InfluxDB doesn't need explicit finalization
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.InfluxDBOutput = InfluxDBOutput;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { OutputHandler } from './base';
|
|
2
|
+
import { TestResult, MetricsSummary } from '../metrics/types';
|
|
3
|
+
export declare class JSONOutput implements OutputHandler {
|
|
4
|
+
private filePath;
|
|
5
|
+
private results;
|
|
6
|
+
private summary?;
|
|
7
|
+
private fileStream?;
|
|
8
|
+
private firstWrite;
|
|
9
|
+
constructor(filePath: string);
|
|
10
|
+
initialize(): Promise<void>;
|
|
11
|
+
writeResult(result: TestResult): Promise<void>;
|
|
12
|
+
writeSummary(summary: MetricsSummary): Promise<void>;
|
|
13
|
+
finalize(): Promise<void>;
|
|
14
|
+
}
|