@testsmith/perfornium 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/cli.js +16 -1
- package/dist/cli/commands/distributed.js +2 -2
- package/dist/cli/commands/report.js +2 -2
- package/dist/cli/commands/run.js +2 -0
- package/dist/config/parser.js +2 -2
- package/dist/config/types/global-config.d.ts +82 -2
- package/dist/config/types/scenario-config.d.ts +2 -2
- package/dist/config/types/step-types.d.ts +1 -1
- package/dist/core/data/data-manager.d.ts +70 -0
- package/dist/core/data/data-manager.js +186 -0
- package/dist/core/data/data-provider.d.ts +85 -0
- package/dist/core/data/data-provider.js +468 -0
- package/dist/core/data/index.d.ts +8 -0
- package/dist/core/data/index.js +13 -0
- package/dist/core/execution/check-evaluator.d.ts +10 -0
- package/dist/core/execution/check-evaluator.js +79 -0
- package/dist/core/execution/data-extractor.d.ts +6 -0
- package/dist/core/execution/data-extractor.js +70 -0
- package/dist/core/execution/index.d.ts +3 -0
- package/dist/core/execution/index.js +9 -0
- package/dist/core/execution/json-payload-processor.d.ts +7 -0
- package/dist/core/execution/json-payload-processor.js +140 -0
- package/dist/core/factories/index.d.ts +2 -0
- package/dist/core/factories/index.js +7 -0
- package/dist/core/factories/output-handler-factory.d.ts +10 -0
- package/dist/core/factories/output-handler-factory.js +91 -0
- package/dist/core/factories/protocol-handler-factory.d.ts +12 -0
- package/dist/core/factories/protocol-handler-factory.js +96 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +8 -3
- package/dist/core/reporting/dashboard-reporter.d.ts +17 -0
- package/dist/core/reporting/dashboard-reporter.js +127 -0
- package/dist/core/reporting/index.d.ts +1 -0
- package/dist/core/reporting/index.js +5 -0
- package/dist/core/step-executor.d.ts +6 -20
- package/dist/core/step-executor.js +72 -366
- package/dist/core/strategies/index.d.ts +2 -0
- package/dist/core/strategies/index.js +7 -0
- package/dist/core/strategies/scenario-selector.d.ts +13 -0
- package/dist/core/strategies/scenario-selector.js +37 -0
- package/dist/core/strategies/think-time-strategy.d.ts +15 -0
- package/dist/core/strategies/think-time-strategy.js +71 -0
- package/dist/core/test-runner.d.ts +4 -11
- package/dist/core/test-runner.js +105 -312
- package/dist/core/virtual-user.d.ts +7 -37
- package/dist/core/virtual-user.js +29 -269
- package/dist/dashboard/routes/api.d.ts +64 -0
- package/dist/dashboard/routes/api.js +569 -0
- package/dist/dashboard/routes/index.d.ts +2 -0
- package/dist/dashboard/routes/index.js +7 -0
- package/dist/dashboard/routes/static.d.ts +6 -0
- package/dist/dashboard/routes/static.js +76 -0
- package/dist/dashboard/server.d.ts +8 -84
- package/dist/dashboard/server.js +76 -2007
- package/dist/dashboard/services/file-scanner.d.ts +7 -0
- package/dist/dashboard/services/file-scanner.js +114 -0
- package/dist/dashboard/services/index.d.ts +5 -0
- package/dist/dashboard/services/index.js +13 -0
- package/dist/dashboard/services/influxdb-service.d.ts +41 -0
- package/dist/dashboard/services/influxdb-service.js +329 -0
- package/dist/dashboard/services/metrics-parser.d.ts +12 -0
- package/dist/dashboard/services/metrics-parser.js +209 -0
- package/dist/dashboard/services/results-manager.d.ts +17 -0
- package/dist/dashboard/services/results-manager.js +311 -0
- package/dist/dashboard/services/test-executor.d.ts +41 -0
- package/dist/dashboard/services/test-executor.js +250 -0
- package/dist/dashboard/services/workers-manager.d.ts +13 -0
- package/dist/dashboard/services/workers-manager.js +81 -0
- package/dist/dashboard/templates/index.html +122 -0
- package/dist/dashboard/templates/scripts/main.js +3280 -0
- package/dist/dashboard/templates/styles.css +402 -0
- package/dist/dashboard/types.d.ts +168 -0
- package/dist/dashboard/types.js +2 -0
- package/dist/distributed/result-aggregator.js +1 -3
- package/dist/metrics/batch/batch-processor.d.ts +27 -0
- package/dist/metrics/batch/batch-processor.js +85 -0
- package/dist/metrics/batch/index.d.ts +1 -0
- package/dist/metrics/batch/index.js +5 -0
- package/dist/metrics/collector.d.ts +46 -45
- package/dist/metrics/collector.js +179 -640
- package/dist/metrics/core/error-tracker.d.ts +9 -0
- package/dist/metrics/core/error-tracker.js +52 -0
- package/dist/metrics/core/index.d.ts +3 -0
- package/dist/metrics/core/index.js +9 -0
- package/dist/metrics/core/result-storage.d.ts +19 -0
- package/dist/metrics/core/result-storage.js +56 -0
- package/dist/metrics/core/statistics-engine.d.ts +27 -0
- package/dist/metrics/core/statistics-engine.js +91 -0
- package/dist/metrics/output/file-writer.d.ts +19 -0
- package/dist/metrics/output/file-writer.js +129 -0
- package/dist/metrics/output/index.d.ts +2 -0
- package/dist/metrics/output/index.js +10 -0
- package/dist/metrics/output/influxdb-writer.d.ts +89 -0
- package/dist/metrics/output/influxdb-writer.js +404 -0
- package/dist/metrics/realtime/dispatcher.d.ts +18 -0
- package/dist/metrics/realtime/dispatcher.js +45 -0
- package/dist/metrics/realtime/endpoints/graphite.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/graphite.js +61 -0
- package/dist/metrics/realtime/endpoints/influxdb.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/influxdb.js +35 -0
- package/dist/metrics/realtime/endpoints/webhook.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/webhook.js +22 -0
- package/dist/metrics/realtime/endpoints/websocket.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/websocket.js +25 -0
- package/dist/metrics/realtime/index.d.ts +5 -0
- package/dist/metrics/realtime/index.js +13 -0
- package/dist/metrics/reporting/index.d.ts +3 -0
- package/dist/metrics/reporting/index.js +9 -0
- package/dist/metrics/reporting/step-statistics.d.ts +6 -0
- package/dist/metrics/reporting/step-statistics.js +59 -0
- package/dist/metrics/reporting/summary-generator.d.ts +16 -0
- package/dist/metrics/reporting/summary-generator.js +46 -0
- package/dist/metrics/reporting/timeline-calculator.d.ts +7 -0
- package/dist/metrics/reporting/timeline-calculator.js +86 -0
- package/dist/metrics/types.d.ts +58 -0
- package/dist/outputs/csv.d.ts +2 -0
- package/dist/outputs/csv.js +21 -2
- package/dist/outputs/json.js +6 -2
- package/dist/protocols/rest/handler.d.ts +4 -53
- package/dist/protocols/rest/handler.js +73 -454
- package/dist/protocols/rest/request/auth-handler.d.ts +4 -0
- package/dist/protocols/rest/request/auth-handler.js +30 -0
- package/dist/protocols/rest/request/body-processor.d.ts +11 -0
- package/dist/protocols/rest/request/body-processor.js +62 -0
- package/dist/protocols/rest/request/index.d.ts +2 -0
- package/dist/protocols/rest/request/index.js +7 -0
- package/dist/protocols/rest/response/checks.d.ts +6 -0
- package/dist/protocols/rest/response/checks.js +71 -0
- package/dist/protocols/rest/response/index.d.ts +2 -0
- package/dist/protocols/rest/response/index.js +7 -0
- package/dist/protocols/rest/response/size-calculator.d.ts +12 -0
- package/dist/protocols/rest/response/size-calculator.js +64 -0
- package/dist/protocols/web/browser/highlight.d.ts +7 -0
- package/dist/protocols/web/browser/highlight.js +47 -0
- package/dist/protocols/web/browser/index.d.ts +4 -0
- package/dist/protocols/web/browser/index.js +11 -0
- package/dist/protocols/web/browser/manager.d.ts +20 -0
- package/dist/protocols/web/browser/manager.js +189 -0
- package/dist/protocols/web/browser/screenshot.d.ts +8 -0
- package/dist/protocols/web/browser/screenshot.js +69 -0
- package/dist/protocols/web/browser/storage.d.ts +5 -0
- package/dist/protocols/web/browser/storage.js +45 -0
- package/dist/protocols/web/commands/index.d.ts +5 -0
- package/dist/protocols/web/commands/index.js +11 -0
- package/dist/protocols/web/commands/interaction.d.ts +13 -0
- package/dist/protocols/web/commands/interaction.js +68 -0
- package/dist/protocols/web/commands/measurement.d.ts +16 -0
- package/dist/protocols/web/commands/measurement.js +33 -0
- package/dist/protocols/web/commands/navigation.d.ts +11 -0
- package/dist/protocols/web/commands/navigation.js +43 -0
- package/dist/protocols/web/commands/types.d.ts +12 -0
- package/dist/protocols/web/commands/types.js +2 -0
- package/dist/protocols/web/commands/verification.d.ts +12 -0
- package/dist/protocols/web/commands/verification.js +118 -0
- package/dist/protocols/web/handler.d.ts +19 -30
- package/dist/protocols/web/handler.js +164 -651
- package/dist/protocols/web/network/capture.d.ts +19 -0
- package/dist/protocols/web/network/capture.js +225 -0
- package/dist/protocols/web/network/filters.d.ts +5 -0
- package/dist/protocols/web/network/filters.js +49 -0
- package/dist/protocols/web/network/index.d.ts +4 -0
- package/dist/protocols/web/network/index.js +9 -0
- package/dist/protocols/web/network/types.d.ts +13 -0
- package/dist/protocols/web/network/types.js +2 -0
- package/dist/protocols/web/network/utils.d.ts +8 -0
- package/dist/protocols/web/network/utils.js +29 -0
- package/dist/recorder/continue-recorder.d.ts +11 -0
- package/dist/recorder/continue-recorder.js +872 -0
- package/dist/reporting/chart-data/index.d.ts +5 -0
- package/dist/reporting/chart-data/index.js +13 -0
- package/dist/reporting/chart-data/network.d.ts +25 -0
- package/dist/reporting/chart-data/network.js +78 -0
- package/dist/reporting/chart-data/scenario.d.ts +37 -0
- package/dist/reporting/chart-data/scenario.js +76 -0
- package/dist/reporting/chart-data/step-statistics.d.ts +24 -0
- package/dist/reporting/chart-data/step-statistics.js +94 -0
- package/dist/reporting/chart-data/throughput.d.ts +16 -0
- package/dist/reporting/chart-data/throughput.js +24 -0
- package/dist/reporting/chart-data/timeline.d.ts +17 -0
- package/dist/reporting/chart-data/timeline.js +46 -0
- package/dist/reporting/handlebars-helpers.d.ts +1 -0
- package/dist/reporting/handlebars-helpers.js +63 -0
- package/dist/reporting/{enhanced-html-generator.d.ts → html-generator.d.ts} +1 -1
- package/dist/reporting/{enhanced-html-generator.js → html-generator.js} +10 -7
- package/dist/reporting/templates/{enhanced-report.hbs → report.hbs} +9 -9
- package/dist/utils/data-utils.d.ts +17 -0
- package/dist/utils/data-utils.js +129 -0
- package/dist/utils/template.js +2 -2
- package/package.json +5 -2
- package/dist/core/csv-data-provider.d.ts +0 -47
- package/dist/core/csv-data-provider.js +0 -265
- package/dist/reporting/generator.d.ts +0 -42
- package/dist/reporting/generator.js +0 -1217
- package/dist/reporting/templates/html.hbs +0 -2453
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ErrorDetail, TestResult } from '../types';
|
|
2
|
+
export declare class ErrorTracker {
|
|
3
|
+
private errorDetails;
|
|
4
|
+
clear(): void;
|
|
5
|
+
trackError(result: TestResult): void;
|
|
6
|
+
getErrorDetails(): ErrorDetail[];
|
|
7
|
+
getErrorDistribution(results: TestResult[]): Record<string, number>;
|
|
8
|
+
getStatusDistribution(results: TestResult[]): Record<number, number>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ErrorTracker = void 0;
|
|
4
|
+
class ErrorTracker {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.errorDetails = new Map();
|
|
7
|
+
}
|
|
8
|
+
clear() {
|
|
9
|
+
this.errorDetails.clear();
|
|
10
|
+
}
|
|
11
|
+
trackError(result) {
|
|
12
|
+
const errorKey = `${result.scenario}:${result.action}:${result.status || 'NO_STATUS'}:${result.error}`;
|
|
13
|
+
const existing = this.errorDetails.get(errorKey);
|
|
14
|
+
if (existing) {
|
|
15
|
+
existing.count++;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
this.errorDetails.set(errorKey, {
|
|
19
|
+
timestamp: result.timestamp,
|
|
20
|
+
vu_id: result.vu_id,
|
|
21
|
+
scenario: result.scenario,
|
|
22
|
+
action: result.action,
|
|
23
|
+
status: result.status,
|
|
24
|
+
error: result.error || 'Unknown error',
|
|
25
|
+
request_url: result.request_url,
|
|
26
|
+
response_body: result.response_body,
|
|
27
|
+
count: 1
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
getErrorDetails() {
|
|
32
|
+
return Array.from(this.errorDetails.values()).sort((a, b) => b.count - a.count);
|
|
33
|
+
}
|
|
34
|
+
getErrorDistribution(results) {
|
|
35
|
+
const distribution = {};
|
|
36
|
+
results.filter(r => !r.success).forEach(r => {
|
|
37
|
+
const error = r.error || 'Unknown error';
|
|
38
|
+
distribution[error] = (distribution[error] || 0) + 1;
|
|
39
|
+
});
|
|
40
|
+
return distribution;
|
|
41
|
+
}
|
|
42
|
+
getStatusDistribution(results) {
|
|
43
|
+
const distribution = {};
|
|
44
|
+
results.forEach(r => {
|
|
45
|
+
if (r.status) {
|
|
46
|
+
distribution[r.status] = (distribution[r.status] || 0) + 1;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return distribution;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.ErrorTracker = ErrorTracker;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResultStorage = exports.ErrorTracker = exports.StatisticsEngine = void 0;
|
|
4
|
+
var statistics_engine_1 = require("./statistics-engine");
|
|
5
|
+
Object.defineProperty(exports, "StatisticsEngine", { enumerable: true, get: function () { return statistics_engine_1.StatisticsEngine; } });
|
|
6
|
+
var error_tracker_1 = require("./error-tracker");
|
|
7
|
+
Object.defineProperty(exports, "ErrorTracker", { enumerable: true, get: function () { return error_tracker_1.ErrorTracker; } });
|
|
8
|
+
var result_storage_1 = require("./result-storage");
|
|
9
|
+
Object.defineProperty(exports, "ResultStorage", { enumerable: true, get: function () { return result_storage_1.ResultStorage; } });
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { TestResult, VUStartEvent } from '../types';
|
|
2
|
+
export declare class ResultStorage {
|
|
3
|
+
private results;
|
|
4
|
+
private vuStartEvents;
|
|
5
|
+
private loadPatternType;
|
|
6
|
+
private readonly maxStoredResults;
|
|
7
|
+
constructor(maxStoredResults?: number);
|
|
8
|
+
clear(): void;
|
|
9
|
+
setLoadPatternType(pattern: string): void;
|
|
10
|
+
recordVUStart(vuId: number): void;
|
|
11
|
+
addResult(result: TestResult): boolean;
|
|
12
|
+
getResults(): TestResult[];
|
|
13
|
+
getVUStartEvents(): VUStartEvent[];
|
|
14
|
+
getResultCount(): number;
|
|
15
|
+
isAtCapacity(): boolean;
|
|
16
|
+
getResponseSizes(): number[];
|
|
17
|
+
getActiveVUsAtTime(time: number): number;
|
|
18
|
+
getResultsInInterval(startTime: number, endTime: number): TestResult[];
|
|
19
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResultStorage = void 0;
|
|
4
|
+
class ResultStorage {
|
|
5
|
+
constructor(maxStoredResults = 50000) {
|
|
6
|
+
this.results = [];
|
|
7
|
+
this.vuStartEvents = [];
|
|
8
|
+
this.loadPatternType = 'basic';
|
|
9
|
+
this.maxStoredResults = maxStoredResults;
|
|
10
|
+
}
|
|
11
|
+
clear() {
|
|
12
|
+
this.results = [];
|
|
13
|
+
this.vuStartEvents = [];
|
|
14
|
+
}
|
|
15
|
+
setLoadPatternType(pattern) {
|
|
16
|
+
this.loadPatternType = pattern;
|
|
17
|
+
}
|
|
18
|
+
recordVUStart(vuId) {
|
|
19
|
+
this.vuStartEvents.push({
|
|
20
|
+
vu_id: vuId,
|
|
21
|
+
start_time: Date.now(),
|
|
22
|
+
load_pattern: this.loadPatternType
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
addResult(result) {
|
|
26
|
+
if (this.results.length < this.maxStoredResults) {
|
|
27
|
+
this.results.push(result);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
getResults() {
|
|
33
|
+
return [...this.results];
|
|
34
|
+
}
|
|
35
|
+
getVUStartEvents() {
|
|
36
|
+
return [...this.vuStartEvents];
|
|
37
|
+
}
|
|
38
|
+
getResultCount() {
|
|
39
|
+
return this.results.length;
|
|
40
|
+
}
|
|
41
|
+
isAtCapacity() {
|
|
42
|
+
return this.results.length >= this.maxStoredResults;
|
|
43
|
+
}
|
|
44
|
+
getResponseSizes() {
|
|
45
|
+
return this.results
|
|
46
|
+
.filter(r => r.response_size)
|
|
47
|
+
.map(r => r.response_size);
|
|
48
|
+
}
|
|
49
|
+
getActiveVUsAtTime(time) {
|
|
50
|
+
return this.vuStartEvents.filter(vu => vu.start_time <= time).length;
|
|
51
|
+
}
|
|
52
|
+
getResultsInInterval(startTime, endTime) {
|
|
53
|
+
return this.results.filter(r => r.timestamp >= startTime && r.timestamp < endTime);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.ResultStorage = ResultStorage;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface RunningStats {
|
|
2
|
+
totalRequests: number;
|
|
3
|
+
successfulRequests: number;
|
|
4
|
+
failedRequests: number;
|
|
5
|
+
totalDuration: number;
|
|
6
|
+
minDuration: number;
|
|
7
|
+
maxDuration: number;
|
|
8
|
+
durations: number[];
|
|
9
|
+
}
|
|
10
|
+
export declare class StatisticsEngine {
|
|
11
|
+
private readonly maxDurationsForPercentiles;
|
|
12
|
+
private stats;
|
|
13
|
+
constructor(maxDurationsForPercentiles?: number);
|
|
14
|
+
private createEmptyStats;
|
|
15
|
+
reset(): void;
|
|
16
|
+
recordResult(duration: number, success: boolean): void;
|
|
17
|
+
getStats(): RunningStats;
|
|
18
|
+
getAverageResponseTime(): number;
|
|
19
|
+
getMinDuration(): number;
|
|
20
|
+
getMaxDuration(): number;
|
|
21
|
+
getDurations(): number[];
|
|
22
|
+
calculatePercentiles(values?: number[]): Record<number, number>;
|
|
23
|
+
getSuccessRate(): number;
|
|
24
|
+
getTotalRequests(): number;
|
|
25
|
+
getSuccessfulRequests(): number;
|
|
26
|
+
getFailedRequests(): number;
|
|
27
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StatisticsEngine = void 0;
|
|
4
|
+
class StatisticsEngine {
|
|
5
|
+
constructor(maxDurationsForPercentiles = 10000) {
|
|
6
|
+
this.maxDurationsForPercentiles = maxDurationsForPercentiles;
|
|
7
|
+
this.stats = this.createEmptyStats();
|
|
8
|
+
}
|
|
9
|
+
createEmptyStats() {
|
|
10
|
+
return {
|
|
11
|
+
totalRequests: 0,
|
|
12
|
+
successfulRequests: 0,
|
|
13
|
+
failedRequests: 0,
|
|
14
|
+
totalDuration: 0,
|
|
15
|
+
minDuration: Infinity,
|
|
16
|
+
maxDuration: 0,
|
|
17
|
+
durations: []
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
reset() {
|
|
21
|
+
this.stats = this.createEmptyStats();
|
|
22
|
+
}
|
|
23
|
+
recordResult(duration, success) {
|
|
24
|
+
this.stats.totalRequests++;
|
|
25
|
+
if (success) {
|
|
26
|
+
this.stats.successfulRequests++;
|
|
27
|
+
this.stats.totalDuration += duration;
|
|
28
|
+
this.stats.minDuration = Math.min(this.stats.minDuration, duration);
|
|
29
|
+
this.stats.maxDuration = Math.max(this.stats.maxDuration, duration);
|
|
30
|
+
// Keep limited durations for percentile calculation (reservoir sampling)
|
|
31
|
+
if (this.stats.durations.length < this.maxDurationsForPercentiles) {
|
|
32
|
+
this.stats.durations.push(duration);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Randomly replace an existing duration (reservoir sampling)
|
|
36
|
+
const replaceIndex = Math.floor(Math.random() * this.stats.totalRequests);
|
|
37
|
+
if (replaceIndex < this.maxDurationsForPercentiles) {
|
|
38
|
+
this.stats.durations[replaceIndex] = duration;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
this.stats.failedRequests++;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
getStats() {
|
|
47
|
+
return { ...this.stats };
|
|
48
|
+
}
|
|
49
|
+
getAverageResponseTime() {
|
|
50
|
+
return this.stats.successfulRequests > 0
|
|
51
|
+
? this.stats.totalDuration / this.stats.successfulRequests
|
|
52
|
+
: 0;
|
|
53
|
+
}
|
|
54
|
+
getMinDuration() {
|
|
55
|
+
return this.stats.minDuration === Infinity ? 0 : this.stats.minDuration;
|
|
56
|
+
}
|
|
57
|
+
getMaxDuration() {
|
|
58
|
+
return this.stats.maxDuration;
|
|
59
|
+
}
|
|
60
|
+
getDurations() {
|
|
61
|
+
return [...this.stats.durations];
|
|
62
|
+
}
|
|
63
|
+
calculatePercentiles(values) {
|
|
64
|
+
const durations = values || this.stats.durations;
|
|
65
|
+
if (durations.length === 0)
|
|
66
|
+
return {};
|
|
67
|
+
const sorted = [...durations].sort((a, b) => a - b);
|
|
68
|
+
const percentileValues = [50, 90, 95, 99, 99.9, 99.99];
|
|
69
|
+
const result = {};
|
|
70
|
+
percentileValues.forEach(p => {
|
|
71
|
+
const index = Math.ceil((p / 100) * sorted.length) - 1;
|
|
72
|
+
result[p] = sorted[Math.max(0, index)];
|
|
73
|
+
});
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
getSuccessRate() {
|
|
77
|
+
return this.stats.totalRequests > 0
|
|
78
|
+
? (this.stats.successfulRequests / this.stats.totalRequests) * 100
|
|
79
|
+
: 0;
|
|
80
|
+
}
|
|
81
|
+
getTotalRequests() {
|
|
82
|
+
return this.stats.totalRequests;
|
|
83
|
+
}
|
|
84
|
+
getSuccessfulRequests() {
|
|
85
|
+
return this.stats.successfulRequests;
|
|
86
|
+
}
|
|
87
|
+
getFailedRequests() {
|
|
88
|
+
return this.stats.failedRequests;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.StatisticsEngine = StatisticsEngine;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { TestResult } from '../types';
|
|
2
|
+
export interface FileOutputConfig {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
path: string;
|
|
5
|
+
format: 'jsonl' | 'csv';
|
|
6
|
+
}
|
|
7
|
+
export interface IncrementalFilesConfig {
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
jsonPath?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class FileWriter {
|
|
12
|
+
private incrementalConfig;
|
|
13
|
+
initialize(config: IncrementalFilesConfig): Promise<void>;
|
|
14
|
+
reset(): void;
|
|
15
|
+
writeBatchToFile(batch: TestResult[], config: FileOutputConfig, batchNumber: number): Promise<void>;
|
|
16
|
+
private formatBatchAsCSV;
|
|
17
|
+
updateIncrementalFiles(batch: TestResult[]): Promise<void>;
|
|
18
|
+
private updateIncrementalJSON;
|
|
19
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
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.FileWriter = void 0;
|
|
37
|
+
const fs = __importStar(require("fs/promises"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const logger_1 = require("../../utils/logger");
|
|
40
|
+
class FileWriter {
|
|
41
|
+
constructor() {
|
|
42
|
+
this.incrementalConfig = null;
|
|
43
|
+
}
|
|
44
|
+
async initialize(config) {
|
|
45
|
+
this.incrementalConfig = config;
|
|
46
|
+
if (!config.enabled)
|
|
47
|
+
return;
|
|
48
|
+
try {
|
|
49
|
+
// Initialize JSON file
|
|
50
|
+
if (config.jsonPath) {
|
|
51
|
+
const dir = path.dirname(config.jsonPath);
|
|
52
|
+
await fs.mkdir(dir, { recursive: true });
|
|
53
|
+
await fs.writeFile(config.jsonPath, '[]');
|
|
54
|
+
logger_1.logger.debug(`Initialized incremental JSON file: ${config.jsonPath}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
logger_1.logger.error('Failed to initialize incremental files:', error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
reset() {
|
|
62
|
+
// No state to reset
|
|
63
|
+
}
|
|
64
|
+
async writeBatchToFile(batch, config, batchNumber) {
|
|
65
|
+
if (!config.enabled)
|
|
66
|
+
return;
|
|
67
|
+
try {
|
|
68
|
+
const dir = path.dirname(config.path);
|
|
69
|
+
await fs.mkdir(dir, { recursive: true });
|
|
70
|
+
let content;
|
|
71
|
+
if (config.format === 'csv') {
|
|
72
|
+
content = this.formatBatchAsCSV(batch, batchNumber);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// JSONL format (default)
|
|
76
|
+
content = batch.map(result => JSON.stringify({
|
|
77
|
+
...result,
|
|
78
|
+
timestamp: new Date(result.timestamp).toISOString(),
|
|
79
|
+
batch_number: batchNumber
|
|
80
|
+
})).join('\n') + '\n';
|
|
81
|
+
}
|
|
82
|
+
await fs.appendFile(config.path, content);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logger_1.logger.error('Failed to write batch to file:', error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
formatBatchAsCSV(batch, batchNumber) {
|
|
89
|
+
return batch.map(result => [
|
|
90
|
+
new Date(result.timestamp).toISOString(),
|
|
91
|
+
batchNumber,
|
|
92
|
+
result.vu_id,
|
|
93
|
+
result.scenario,
|
|
94
|
+
result.action,
|
|
95
|
+
result.step_name || '',
|
|
96
|
+
result.duration,
|
|
97
|
+
result.success,
|
|
98
|
+
result.status || '',
|
|
99
|
+
(result.error || '').replace(/"/g, '""')
|
|
100
|
+
].join(',')).join('\n') + '\n';
|
|
101
|
+
}
|
|
102
|
+
async updateIncrementalFiles(batch) {
|
|
103
|
+
if (!this.incrementalConfig?.enabled)
|
|
104
|
+
return;
|
|
105
|
+
try {
|
|
106
|
+
if (this.incrementalConfig.jsonPath) {
|
|
107
|
+
await this.updateIncrementalJSON(batch, this.incrementalConfig.jsonPath);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
logger_1.logger.error('Failed to update incremental files:', error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async updateIncrementalJSON(batch, filePath) {
|
|
115
|
+
try {
|
|
116
|
+
const existingContent = await fs.readFile(filePath, 'utf8');
|
|
117
|
+
let existingData = [];
|
|
118
|
+
if (existingContent.trim()) {
|
|
119
|
+
existingData = JSON.parse(existingContent);
|
|
120
|
+
}
|
|
121
|
+
const updatedData = [...existingData, ...batch];
|
|
122
|
+
await fs.writeFile(filePath, JSON.stringify(updatedData, null, 2));
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
await fs.writeFile(filePath, JSON.stringify(batch, null, 2));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.FileWriter = FileWriter;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initInfluxDBWriter = exports.setInfluxDBWriter = exports.getInfluxDBWriter = exports.InfluxDBWriter = exports.FileWriter = void 0;
|
|
4
|
+
var file_writer_1 = require("./file-writer");
|
|
5
|
+
Object.defineProperty(exports, "FileWriter", { enumerable: true, get: function () { return file_writer_1.FileWriter; } });
|
|
6
|
+
var influxdb_writer_1 = require("./influxdb-writer");
|
|
7
|
+
Object.defineProperty(exports, "InfluxDBWriter", { enumerable: true, get: function () { return influxdb_writer_1.InfluxDBWriter; } });
|
|
8
|
+
Object.defineProperty(exports, "getInfluxDBWriter", { enumerable: true, get: function () { return influxdb_writer_1.getInfluxDBWriter; } });
|
|
9
|
+
Object.defineProperty(exports, "setInfluxDBWriter", { enumerable: true, get: function () { return influxdb_writer_1.setInfluxDBWriter; } });
|
|
10
|
+
Object.defineProperty(exports, "initInfluxDBWriter", { enumerable: true, get: function () { return influxdb_writer_1.initInfluxDBWriter; } });
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { TestResult, MetricsSummary, CapturedNetworkCall } from '../types';
|
|
2
|
+
export interface InfluxDBWriterConfig {
|
|
3
|
+
url: string;
|
|
4
|
+
token: string;
|
|
5
|
+
org: string;
|
|
6
|
+
bucket: string;
|
|
7
|
+
/** Batch size for writing points (default: 100) */
|
|
8
|
+
batchSize?: number;
|
|
9
|
+
/** Flush interval in ms (default: 1000) */
|
|
10
|
+
flushInterval?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface TestMetricsQueryOptions {
|
|
13
|
+
testId?: string;
|
|
14
|
+
testName?: string;
|
|
15
|
+
startTime?: Date;
|
|
16
|
+
endTime?: Date;
|
|
17
|
+
scenario?: string;
|
|
18
|
+
limit?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* InfluxDB writer for test metrics (response times, network calls, etc.)
|
|
22
|
+
* This is optional - test results can still be stored as JSON files.
|
|
23
|
+
*/
|
|
24
|
+
export declare class InfluxDBWriter {
|
|
25
|
+
private client;
|
|
26
|
+
private writeApi;
|
|
27
|
+
private queryApi;
|
|
28
|
+
private config;
|
|
29
|
+
private enabled;
|
|
30
|
+
private currentTestId;
|
|
31
|
+
private currentTestName;
|
|
32
|
+
constructor(config?: Partial<InfluxDBWriterConfig>);
|
|
33
|
+
connect(): Promise<boolean>;
|
|
34
|
+
isEnabled(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Start a new test run - sets the test ID and name for all subsequent writes
|
|
37
|
+
*/
|
|
38
|
+
startTest(testId: string, testName: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* Write a single test result to InfluxDB
|
|
41
|
+
*/
|
|
42
|
+
writeResult(result: TestResult): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Write a batch of test results to InfluxDB
|
|
45
|
+
*/
|
|
46
|
+
writeBatch(results: TestResult[]): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Write a network call to InfluxDB
|
|
49
|
+
*/
|
|
50
|
+
writeNetworkCall(call: CapturedNetworkCall): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Write test summary to InfluxDB
|
|
53
|
+
*/
|
|
54
|
+
writeSummary(summary: MetricsSummary): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Query test results from InfluxDB
|
|
57
|
+
*/
|
|
58
|
+
queryResults(options?: TestMetricsQueryOptions): Promise<TestResult[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Query network calls from InfluxDB
|
|
61
|
+
*/
|
|
62
|
+
queryNetworkCalls(options?: TestMetricsQueryOptions): Promise<CapturedNetworkCall[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Get list of test runs
|
|
65
|
+
*/
|
|
66
|
+
getTestRuns(limit?: number): Promise<Array<{
|
|
67
|
+
testId: string;
|
|
68
|
+
testName: string;
|
|
69
|
+
timestamp: Date;
|
|
70
|
+
}>>;
|
|
71
|
+
/**
|
|
72
|
+
* Export test data for a specific test run
|
|
73
|
+
*/
|
|
74
|
+
exportTestData(testId: string, format?: 'json' | 'csv'): Promise<string>;
|
|
75
|
+
/**
|
|
76
|
+
* Finalize the current test - flush all pending writes
|
|
77
|
+
*/
|
|
78
|
+
finalize(): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Close the connection
|
|
81
|
+
*/
|
|
82
|
+
close(): Promise<void>;
|
|
83
|
+
private rowsToResults;
|
|
84
|
+
private rowsToNetworkCalls;
|
|
85
|
+
private resultsToCSV;
|
|
86
|
+
}
|
|
87
|
+
export declare function getInfluxDBWriter(): InfluxDBWriter | null;
|
|
88
|
+
export declare function setInfluxDBWriter(writer: InfluxDBWriter): void;
|
|
89
|
+
export declare function initInfluxDBWriter(config?: Partial<InfluxDBWriterConfig>): Promise<InfluxDBWriter>;
|