@testsmith/perfornium 0.6.4 → 0.6.5
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/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/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 +11 -0
- package/dist/protocols/web/commands/verification.js +98 -0
- package/dist/protocols/web/handler.d.ts +19 -30
- package/dist/protocols/web/handler.js +160 -650
- 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/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,19 @@
|
|
|
1
|
+
import { Page } from 'playwright';
|
|
2
|
+
import { NetworkCaptureConfig } from '../../../config';
|
|
3
|
+
import { CapturedNetworkCall } from '../../../metrics/types';
|
|
4
|
+
import { CurrentContext } from './types';
|
|
5
|
+
export type NetworkCallCallback = (call: CapturedNetworkCall) => void;
|
|
6
|
+
export declare class NetworkCaptureManager {
|
|
7
|
+
private networkCalls;
|
|
8
|
+
private pendingRequests;
|
|
9
|
+
private currentContext;
|
|
10
|
+
private onNetworkCall?;
|
|
11
|
+
constructor(onNetworkCall?: NetworkCallCallback);
|
|
12
|
+
setNetworkCallCallback(callback: NetworkCallCallback): void;
|
|
13
|
+
updateContext(vuId: number, context: CurrentContext): void;
|
|
14
|
+
setupNetworkCapture(page: Page, vuId: number, config: NetworkCaptureConfig): void;
|
|
15
|
+
getAndClearNetworkCalls(vuId: number): CapturedNetworkCall[];
|
|
16
|
+
getNetworkCalls(vuId: number): CapturedNetworkCall[];
|
|
17
|
+
clearVU(vuId: number): void;
|
|
18
|
+
clearAll(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NetworkCaptureManager = void 0;
|
|
4
|
+
const logger_1 = require("../../../utils/logger");
|
|
5
|
+
const filters_1 = require("./filters");
|
|
6
|
+
const utils_1 = require("./utils");
|
|
7
|
+
class NetworkCaptureManager {
|
|
8
|
+
constructor(onNetworkCall) {
|
|
9
|
+
this.networkCalls = new Map();
|
|
10
|
+
this.pendingRequests = new Map();
|
|
11
|
+
this.currentContext = new Map();
|
|
12
|
+
this.onNetworkCall = onNetworkCall;
|
|
13
|
+
}
|
|
14
|
+
setNetworkCallCallback(callback) {
|
|
15
|
+
this.onNetworkCall = callback;
|
|
16
|
+
}
|
|
17
|
+
updateContext(vuId, context) {
|
|
18
|
+
this.currentContext.set(vuId, context);
|
|
19
|
+
}
|
|
20
|
+
setupNetworkCapture(page, vuId, config) {
|
|
21
|
+
if (!this.networkCalls.has(vuId)) {
|
|
22
|
+
this.networkCalls.set(vuId, []);
|
|
23
|
+
}
|
|
24
|
+
const capturedBodies = new Map();
|
|
25
|
+
logger_1.logger.info(`VU ${vuId}: Setting up network capture with config: ${JSON.stringify(config)}`);
|
|
26
|
+
// Use route interception to capture response bodies
|
|
27
|
+
page.route('**/*', async (route) => {
|
|
28
|
+
const request = route.request();
|
|
29
|
+
const url = request.url();
|
|
30
|
+
const shouldCapture = filters_1.NetworkFilters.shouldCaptureUrl(url, config) && config.capture_response_body;
|
|
31
|
+
if (!shouldCapture) {
|
|
32
|
+
await route.continue();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const response = await route.fetch();
|
|
37
|
+
const contentType = response.headers()['content-type'] || '';
|
|
38
|
+
let bodyText;
|
|
39
|
+
if (filters_1.NetworkFilters.shouldCaptureBodyByContentType(contentType, config)) {
|
|
40
|
+
try {
|
|
41
|
+
bodyText = await response.text();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Body not available as text
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (bodyText !== undefined) {
|
|
48
|
+
capturedBodies.set(url, { body: bodyText, headers: response.headers() });
|
|
49
|
+
}
|
|
50
|
+
await route.fulfill({ response });
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
await route.continue();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// Capture request start
|
|
57
|
+
page.on('request', (request) => {
|
|
58
|
+
const url = request.url();
|
|
59
|
+
if (!filters_1.NetworkFilters.shouldCaptureUrl(url, config))
|
|
60
|
+
return;
|
|
61
|
+
const requestId = utils_1.NetworkUtils.generateRequestId();
|
|
62
|
+
const startTime = Date.now();
|
|
63
|
+
const currentCtx = this.currentContext.get(vuId);
|
|
64
|
+
const call = {
|
|
65
|
+
id: requestId,
|
|
66
|
+
vu_id: vuId,
|
|
67
|
+
timestamp: startTime,
|
|
68
|
+
request_url: url,
|
|
69
|
+
request_method: request.method(),
|
|
70
|
+
request_headers: utils_1.NetworkUtils.captureHeaders(request.headers(), config),
|
|
71
|
+
request_body: utils_1.NetworkUtils.captureRequestBody(request.postData(), config),
|
|
72
|
+
request_body_truncated: utils_1.NetworkUtils.isBodyTruncated(request.postData(), config),
|
|
73
|
+
start_time: startTime,
|
|
74
|
+
resource_type: request.resourceType(),
|
|
75
|
+
scenario: currentCtx?.scenario,
|
|
76
|
+
step_name: currentCtx?.step_name,
|
|
77
|
+
success: false
|
|
78
|
+
};
|
|
79
|
+
this.pendingRequests.set(request, { call, vuId });
|
|
80
|
+
});
|
|
81
|
+
// Capture response
|
|
82
|
+
page.on('response', async (response) => {
|
|
83
|
+
const request = response.request();
|
|
84
|
+
const url = request.url();
|
|
85
|
+
if (!filters_1.NetworkFilters.shouldCaptureUrl(url, config))
|
|
86
|
+
return;
|
|
87
|
+
const pending = this.pendingRequests.get(request);
|
|
88
|
+
if (!pending) {
|
|
89
|
+
logger_1.logger.debug(`VU ${vuId}: Response without matching request for ${url}`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.pendingRequests.delete(request);
|
|
93
|
+
const endTime = Date.now();
|
|
94
|
+
try {
|
|
95
|
+
const status = response.status();
|
|
96
|
+
const headers = response.headers();
|
|
97
|
+
let body;
|
|
98
|
+
let bodyTruncated = false;
|
|
99
|
+
const captured = capturedBodies.get(url);
|
|
100
|
+
if (captured && config.capture_response_body) {
|
|
101
|
+
body = utils_1.NetworkUtils.truncateBody(captured.body, config);
|
|
102
|
+
bodyTruncated = utils_1.NetworkUtils.isBodyTruncated(captured.body, config);
|
|
103
|
+
capturedBodies.delete(url);
|
|
104
|
+
}
|
|
105
|
+
else if (config.capture_response_body && filters_1.NetworkFilters.shouldCaptureBodyByContentType(headers['content-type'], config)) {
|
|
106
|
+
try {
|
|
107
|
+
const bodyText = await response.text();
|
|
108
|
+
body = utils_1.NetworkUtils.truncateBody(bodyText, config);
|
|
109
|
+
bodyTruncated = utils_1.NetworkUtils.isBodyTruncated(bodyText, config);
|
|
110
|
+
}
|
|
111
|
+
catch (bodyError) {
|
|
112
|
+
logger_1.logger.debug(`Failed to capture response body for ${url}: ${bodyError.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const completedCall = {
|
|
116
|
+
...pending.call,
|
|
117
|
+
response_status: status,
|
|
118
|
+
response_status_text: response.statusText(),
|
|
119
|
+
response_headers: utils_1.NetworkUtils.captureHeaders(headers, config),
|
|
120
|
+
response_body: body,
|
|
121
|
+
response_body_truncated: bodyTruncated,
|
|
122
|
+
response_size: body?.length,
|
|
123
|
+
end_time: endTime,
|
|
124
|
+
duration: endTime - pending.call.start_time,
|
|
125
|
+
success: status >= 200 && status < 400
|
|
126
|
+
};
|
|
127
|
+
const vuCalls = this.networkCalls.get(vuId);
|
|
128
|
+
vuCalls.push(completedCall);
|
|
129
|
+
// Invoke callback for real-time processing (e.g., InfluxDB storage)
|
|
130
|
+
if (this.onNetworkCall) {
|
|
131
|
+
this.onNetworkCall(completedCall);
|
|
132
|
+
}
|
|
133
|
+
if (config.store_separate !== false) {
|
|
134
|
+
console.log(`[NETWORK] ${JSON.stringify({
|
|
135
|
+
id: completedCall.id,
|
|
136
|
+
vu: vuId,
|
|
137
|
+
url: url,
|
|
138
|
+
method: request.method(),
|
|
139
|
+
status: status,
|
|
140
|
+
statusText: completedCall.response_status_text,
|
|
141
|
+
duration: completedCall.duration,
|
|
142
|
+
size: completedCall.response_size || 0,
|
|
143
|
+
type: request.resourceType(),
|
|
144
|
+
success: completedCall.success,
|
|
145
|
+
requestHeaders: completedCall.request_headers,
|
|
146
|
+
requestBody: completedCall.request_body,
|
|
147
|
+
responseHeaders: completedCall.response_headers,
|
|
148
|
+
responseBody: completedCall.response_body
|
|
149
|
+
})}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
logger_1.logger.debug(`VU ${vuId}: Failed to capture response for ${url}: ${error.message}`);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
// Capture request failures
|
|
157
|
+
page.on('requestfailed', (request) => {
|
|
158
|
+
const url = request.url();
|
|
159
|
+
if (!filters_1.NetworkFilters.shouldCaptureUrl(url, config))
|
|
160
|
+
return;
|
|
161
|
+
const pending = this.pendingRequests.get(request);
|
|
162
|
+
this.pendingRequests.delete(request);
|
|
163
|
+
const endTime = Date.now();
|
|
164
|
+
const currentCtx = this.currentContext.get(vuId);
|
|
165
|
+
const failedCall = {
|
|
166
|
+
id: pending?.call.id || utils_1.NetworkUtils.generateRequestId(),
|
|
167
|
+
vu_id: vuId,
|
|
168
|
+
timestamp: pending?.call.start_time || endTime,
|
|
169
|
+
request_url: url,
|
|
170
|
+
request_method: request.method(),
|
|
171
|
+
request_headers: pending?.call.request_headers,
|
|
172
|
+
request_body: pending?.call.request_body,
|
|
173
|
+
start_time: pending?.call.start_time || endTime,
|
|
174
|
+
end_time: endTime,
|
|
175
|
+
duration: endTime - (pending?.call.start_time || endTime),
|
|
176
|
+
resource_type: request.resourceType(),
|
|
177
|
+
scenario: pending?.call.scenario || currentCtx?.scenario,
|
|
178
|
+
step_name: pending?.call.step_name || currentCtx?.step_name,
|
|
179
|
+
success: false,
|
|
180
|
+
error: request.failure()?.errorText || 'Request failed'
|
|
181
|
+
};
|
|
182
|
+
const vuCalls = this.networkCalls.get(vuId);
|
|
183
|
+
vuCalls.push(failedCall);
|
|
184
|
+
// Invoke callback for real-time processing (e.g., InfluxDB storage)
|
|
185
|
+
if (this.onNetworkCall) {
|
|
186
|
+
this.onNetworkCall(failedCall);
|
|
187
|
+
}
|
|
188
|
+
if (config.store_separate !== false) {
|
|
189
|
+
console.log(`[NETWORK] ${JSON.stringify({
|
|
190
|
+
id: failedCall.id,
|
|
191
|
+
vu: vuId,
|
|
192
|
+
url: url,
|
|
193
|
+
method: request.method(),
|
|
194
|
+
status: 0,
|
|
195
|
+
statusText: 'Failed',
|
|
196
|
+
duration: failedCall.duration,
|
|
197
|
+
size: 0,
|
|
198
|
+
type: request.resourceType(),
|
|
199
|
+
success: false,
|
|
200
|
+
error: failedCall.error,
|
|
201
|
+
requestHeaders: failedCall.request_headers,
|
|
202
|
+
requestBody: failedCall.request_body
|
|
203
|
+
})}`);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
getAndClearNetworkCalls(vuId) {
|
|
208
|
+
const calls = this.networkCalls.get(vuId) || [];
|
|
209
|
+
this.networkCalls.set(vuId, []);
|
|
210
|
+
return calls;
|
|
211
|
+
}
|
|
212
|
+
getNetworkCalls(vuId) {
|
|
213
|
+
return this.networkCalls.get(vuId) || [];
|
|
214
|
+
}
|
|
215
|
+
clearVU(vuId) {
|
|
216
|
+
this.networkCalls.delete(vuId);
|
|
217
|
+
this.currentContext.delete(vuId);
|
|
218
|
+
}
|
|
219
|
+
clearAll() {
|
|
220
|
+
this.networkCalls.clear();
|
|
221
|
+
this.pendingRequests.clear();
|
|
222
|
+
this.currentContext.clear();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.NetworkCaptureManager = NetworkCaptureManager;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { NetworkCaptureConfig } from '../../../config';
|
|
2
|
+
export declare class NetworkFilters {
|
|
3
|
+
static shouldCaptureUrl(url: string, config: NetworkCaptureConfig): boolean;
|
|
4
|
+
static shouldCaptureBodyByContentType(contentType: string | undefined, config: NetworkCaptureConfig): boolean;
|
|
5
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NetworkFilters = void 0;
|
|
4
|
+
const minimatch_1 = require("minimatch");
|
|
5
|
+
const logger_1 = require("../../../utils/logger");
|
|
6
|
+
class NetworkFilters {
|
|
7
|
+
static shouldCaptureUrl(url, config) {
|
|
8
|
+
let pathname = url;
|
|
9
|
+
try {
|
|
10
|
+
pathname = new URL(url).pathname;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
// Keep full URL if parsing fails
|
|
14
|
+
}
|
|
15
|
+
// Check exclude patterns first
|
|
16
|
+
if (config.exclude_patterns?.length) {
|
|
17
|
+
for (const pattern of config.exclude_patterns) {
|
|
18
|
+
const corePattern = pattern.replace(/\*\*/g, '').replace(/\*/g, '').replace(/^\/+|\/+$/g, '');
|
|
19
|
+
const simpleMatch = corePattern && (url.includes(corePattern) || pathname.includes(corePattern));
|
|
20
|
+
if ((0, minimatch_1.minimatch)(url, pattern) || (0, minimatch_1.minimatch)(pathname, pattern) || simpleMatch) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Check include patterns (if specified, URL must match at least one)
|
|
26
|
+
if (config.include_patterns?.length) {
|
|
27
|
+
for (const pattern of config.include_patterns) {
|
|
28
|
+
const corePattern = pattern.replace(/\*\*/g, '').replace(/\*/g, '').replace(/^\/+|\/+$/g, '');
|
|
29
|
+
const simpleMatch = corePattern && (url.includes(corePattern) || pathname.includes(corePattern));
|
|
30
|
+
const globMatch = (0, minimatch_1.minimatch)(url, pattern) || (0, minimatch_1.minimatch)(pathname, pattern);
|
|
31
|
+
if (globMatch || simpleMatch) {
|
|
32
|
+
logger_1.logger.debug(`URL captured: ${url} matches pattern ${pattern}`);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
static shouldCaptureBodyByContentType(contentType, config) {
|
|
41
|
+
if (!contentType)
|
|
42
|
+
return false;
|
|
43
|
+
if (!config.content_type_filters?.length)
|
|
44
|
+
return true;
|
|
45
|
+
const lowerContentType = contentType.toLowerCase();
|
|
46
|
+
return config.content_type_filters.some(filter => lowerContentType.includes(filter.toLowerCase()));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.NetworkFilters = NetworkFilters;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NetworkUtils = exports.NetworkFilters = exports.NetworkCaptureManager = void 0;
|
|
4
|
+
var capture_1 = require("./capture");
|
|
5
|
+
Object.defineProperty(exports, "NetworkCaptureManager", { enumerable: true, get: function () { return capture_1.NetworkCaptureManager; } });
|
|
6
|
+
var filters_1 = require("./filters");
|
|
7
|
+
Object.defineProperty(exports, "NetworkFilters", { enumerable: true, get: function () { return filters_1.NetworkFilters; } });
|
|
8
|
+
var utils_1 = require("./utils");
|
|
9
|
+
Object.defineProperty(exports, "NetworkUtils", { enumerable: true, get: function () { return utils_1.NetworkUtils; } });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CapturedNetworkCall } from '../../../metrics/types';
|
|
2
|
+
export interface PendingRequest {
|
|
3
|
+
call: Partial<CapturedNetworkCall>;
|
|
4
|
+
vuId: number;
|
|
5
|
+
}
|
|
6
|
+
export interface CurrentContext {
|
|
7
|
+
scenario?: string;
|
|
8
|
+
step_name?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface CapturedBody {
|
|
11
|
+
body: string;
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NetworkCaptureConfig } from '../../../config';
|
|
2
|
+
export declare class NetworkUtils {
|
|
3
|
+
static captureHeaders(headers: Record<string, string>, config: NetworkCaptureConfig): Record<string, string> | undefined;
|
|
4
|
+
static captureRequestBody(body: string | null | undefined, config: NetworkCaptureConfig): string | undefined;
|
|
5
|
+
static truncateBody(body: string | null | undefined, config: NetworkCaptureConfig): string | undefined;
|
|
6
|
+
static isBodyTruncated(body: string | null | undefined, config: NetworkCaptureConfig): boolean;
|
|
7
|
+
static generateRequestId(): string;
|
|
8
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NetworkUtils = void 0;
|
|
4
|
+
class NetworkUtils {
|
|
5
|
+
static captureHeaders(headers, config) {
|
|
6
|
+
return headers;
|
|
7
|
+
}
|
|
8
|
+
static captureRequestBody(body, config) {
|
|
9
|
+
if (!config.capture_request_body || !body)
|
|
10
|
+
return undefined;
|
|
11
|
+
return NetworkUtils.truncateBody(body, config);
|
|
12
|
+
}
|
|
13
|
+
static truncateBody(body, config) {
|
|
14
|
+
if (!body)
|
|
15
|
+
return undefined;
|
|
16
|
+
const maxSize = config.max_body_size || 10240;
|
|
17
|
+
return body.length > maxSize ? body.substring(0, maxSize) : body;
|
|
18
|
+
}
|
|
19
|
+
static isBodyTruncated(body, config) {
|
|
20
|
+
if (!body)
|
|
21
|
+
return false;
|
|
22
|
+
const maxSize = config.max_body_size || 10240;
|
|
23
|
+
return body.length > maxSize;
|
|
24
|
+
}
|
|
25
|
+
static generateRequestId() {
|
|
26
|
+
return `req_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.NetworkUtils = NetworkUtils;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { StepStatisticsCalculator, StepStatistics, StepResponseTime } from './step-statistics';
|
|
2
|
+
export { ThroughputCalculator, RequestsPerSecond, ResponsesPerSecond } from './throughput';
|
|
3
|
+
export { TimelineCalculator, VURampupDataPoint, TimelineDataPoint } from './timeline';
|
|
4
|
+
export { ScenarioCalculator, ScenarioData, ScenarioStatistics, ChartData } from './scenario';
|
|
5
|
+
export { NetworkStatisticsCalculator, NetworkStatistics, EndpointStats } from './network';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NetworkStatisticsCalculator = exports.ScenarioCalculator = exports.TimelineCalculator = exports.ThroughputCalculator = exports.StepStatisticsCalculator = void 0;
|
|
4
|
+
var step_statistics_1 = require("./step-statistics");
|
|
5
|
+
Object.defineProperty(exports, "StepStatisticsCalculator", { enumerable: true, get: function () { return step_statistics_1.StepStatisticsCalculator; } });
|
|
6
|
+
var throughput_1 = require("./throughput");
|
|
7
|
+
Object.defineProperty(exports, "ThroughputCalculator", { enumerable: true, get: function () { return throughput_1.ThroughputCalculator; } });
|
|
8
|
+
var timeline_1 = require("./timeline");
|
|
9
|
+
Object.defineProperty(exports, "TimelineCalculator", { enumerable: true, get: function () { return timeline_1.TimelineCalculator; } });
|
|
10
|
+
var scenario_1 = require("./scenario");
|
|
11
|
+
Object.defineProperty(exports, "ScenarioCalculator", { enumerable: true, get: function () { return scenario_1.ScenarioCalculator; } });
|
|
12
|
+
var network_1 = require("./network");
|
|
13
|
+
Object.defineProperty(exports, "NetworkStatisticsCalculator", { enumerable: true, get: function () { return network_1.NetworkStatisticsCalculator; } });
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { TestResult } from '../../metrics/types';
|
|
2
|
+
export interface EndpointStats {
|
|
3
|
+
endpoint: string;
|
|
4
|
+
count: number;
|
|
5
|
+
avg_duration: number;
|
|
6
|
+
success_rate: number;
|
|
7
|
+
total_size: number;
|
|
8
|
+
status_distribution: Record<number, number>;
|
|
9
|
+
}
|
|
10
|
+
export interface NetworkStatistics {
|
|
11
|
+
total_calls: number;
|
|
12
|
+
successful_calls: number;
|
|
13
|
+
failed_calls: number;
|
|
14
|
+
success_rate: number;
|
|
15
|
+
avg_duration: number;
|
|
16
|
+
total_size: number;
|
|
17
|
+
by_endpoint: EndpointStats[];
|
|
18
|
+
by_type: Array<{
|
|
19
|
+
type: string;
|
|
20
|
+
count: number;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
export declare class NetworkStatisticsCalculator {
|
|
24
|
+
static calculateNetworkStatistics(results: TestResult[]): NetworkStatistics | null;
|
|
25
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NetworkStatisticsCalculator = void 0;
|
|
4
|
+
class NetworkStatisticsCalculator {
|
|
5
|
+
static calculateNetworkStatistics(results) {
|
|
6
|
+
const allCalls = [];
|
|
7
|
+
results.forEach(result => {
|
|
8
|
+
if (result.custom_metrics?.network_calls) {
|
|
9
|
+
allCalls.push(...result.custom_metrics.network_calls);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
if (allCalls.length === 0)
|
|
13
|
+
return null;
|
|
14
|
+
const successfulCalls = allCalls.filter(c => c.success);
|
|
15
|
+
const failedCalls = allCalls.filter(c => !c.success);
|
|
16
|
+
const durations = allCalls.filter(c => c.duration).map(c => c.duration);
|
|
17
|
+
const avgDuration = durations.length > 0
|
|
18
|
+
? durations.reduce((a, b) => a + b, 0) / durations.length
|
|
19
|
+
: 0;
|
|
20
|
+
const totalSize = allCalls.reduce((sum, c) => sum + (c.response_size || 0), 0);
|
|
21
|
+
const byEndpoint = new Map();
|
|
22
|
+
allCalls.forEach(call => {
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(call.request_url);
|
|
25
|
+
const endpoint = url.pathname;
|
|
26
|
+
if (!byEndpoint.has(endpoint)) {
|
|
27
|
+
byEndpoint.set(endpoint, []);
|
|
28
|
+
}
|
|
29
|
+
byEndpoint.get(endpoint).push(call);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Invalid URL, skip
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const endpointStats = Array.from(byEndpoint.entries())
|
|
36
|
+
.map(([endpoint, calls]) => {
|
|
37
|
+
const successful = calls.filter(c => c.success);
|
|
38
|
+
const callDurations = calls.filter(c => c.duration).map(c => c.duration);
|
|
39
|
+
const avgDur = callDurations.length > 0
|
|
40
|
+
? callDurations.reduce((a, b) => a + b, 0) / callDurations.length
|
|
41
|
+
: 0;
|
|
42
|
+
const statusDist = {};
|
|
43
|
+
calls.forEach(c => {
|
|
44
|
+
const status = c.response_status || 0;
|
|
45
|
+
statusDist[status] = (statusDist[status] || 0) + 1;
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
endpoint,
|
|
49
|
+
count: calls.length,
|
|
50
|
+
avg_duration: Math.round(avgDur),
|
|
51
|
+
success_rate: calls.length > 0 ? (successful.length / calls.length) * 100 : 0,
|
|
52
|
+
total_size: calls.reduce((sum, c) => sum + (c.response_size || 0), 0),
|
|
53
|
+
status_distribution: statusDist
|
|
54
|
+
};
|
|
55
|
+
})
|
|
56
|
+
.sort((a, b) => b.count - a.count)
|
|
57
|
+
.slice(0, 20);
|
|
58
|
+
const byType = new Map();
|
|
59
|
+
allCalls.forEach(call => {
|
|
60
|
+
const type = call.resource_type || 'unknown';
|
|
61
|
+
byType.set(type, (byType.get(type) || 0) + 1);
|
|
62
|
+
});
|
|
63
|
+
const typeStats = Array.from(byType.entries())
|
|
64
|
+
.map(([type, count]) => ({ type, count }))
|
|
65
|
+
.sort((a, b) => b.count - a.count);
|
|
66
|
+
return {
|
|
67
|
+
total_calls: allCalls.length,
|
|
68
|
+
successful_calls: successfulCalls.length,
|
|
69
|
+
failed_calls: failedCalls.length,
|
|
70
|
+
success_rate: allCalls.length > 0 ? (successfulCalls.length / allCalls.length) * 100 : 0,
|
|
71
|
+
avg_duration: Math.round(avgDuration),
|
|
72
|
+
total_size: totalSize,
|
|
73
|
+
by_endpoint: endpointStats,
|
|
74
|
+
by_type: typeStats
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.NetworkStatisticsCalculator = NetworkStatisticsCalculator;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { TestResult } from '../../metrics/types';
|
|
2
|
+
export interface ScenarioData {
|
|
3
|
+
name: string;
|
|
4
|
+
total: number;
|
|
5
|
+
success: number;
|
|
6
|
+
errors: number;
|
|
7
|
+
avgResponseTime: number;
|
|
8
|
+
responseTimes: number[];
|
|
9
|
+
successRate: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ScenarioStatistics extends ScenarioData {
|
|
12
|
+
percentiles: Record<number, number>;
|
|
13
|
+
minResponseTime: number;
|
|
14
|
+
maxResponseTime: number;
|
|
15
|
+
p50: number;
|
|
16
|
+
p90: number;
|
|
17
|
+
p95: number;
|
|
18
|
+
p99: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ChartData {
|
|
21
|
+
responseTimes: Array<{
|
|
22
|
+
timestamp: number;
|
|
23
|
+
duration: number;
|
|
24
|
+
scenario: string;
|
|
25
|
+
}>;
|
|
26
|
+
errors: Array<{
|
|
27
|
+
timestamp: number;
|
|
28
|
+
error: string;
|
|
29
|
+
scenario: string;
|
|
30
|
+
}>;
|
|
31
|
+
scenarios: ScenarioData[];
|
|
32
|
+
}
|
|
33
|
+
export declare class ScenarioCalculator {
|
|
34
|
+
static groupByScenario(results: TestResult[]): ScenarioData[];
|
|
35
|
+
static prepareChartData(results: TestResult[]): ChartData;
|
|
36
|
+
static calculateScenarioStatistics(results: TestResult[]): ScenarioStatistics[];
|
|
37
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScenarioCalculator = void 0;
|
|
4
|
+
const statistics_1 = require("../statistics");
|
|
5
|
+
class ScenarioCalculator {
|
|
6
|
+
static groupByScenario(results) {
|
|
7
|
+
const scenarios = {};
|
|
8
|
+
results.forEach(result => {
|
|
9
|
+
if (!scenarios[result.scenario]) {
|
|
10
|
+
scenarios[result.scenario] = {
|
|
11
|
+
name: result.scenario,
|
|
12
|
+
total: 0,
|
|
13
|
+
success: 0,
|
|
14
|
+
errors: 0,
|
|
15
|
+
avgResponseTime: 0,
|
|
16
|
+
responseTimes: [],
|
|
17
|
+
successRate: 0
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const scenario = scenarios[result.scenario];
|
|
21
|
+
scenario.total++;
|
|
22
|
+
if (result.success) {
|
|
23
|
+
scenario.success++;
|
|
24
|
+
scenario.responseTimes.push(result.duration);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
scenario.errors++;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
Object.values(scenarios).forEach((scenario) => {
|
|
31
|
+
if (scenario.responseTimes.length > 0) {
|
|
32
|
+
scenario.avgResponseTime = scenario.responseTimes.reduce((a, b) => a + b, 0) / scenario.responseTimes.length;
|
|
33
|
+
}
|
|
34
|
+
scenario.successRate = scenario.total > 0 ? (scenario.success / scenario.total) * 100 : 0;
|
|
35
|
+
});
|
|
36
|
+
return Object.values(scenarios);
|
|
37
|
+
}
|
|
38
|
+
static prepareChartData(results) {
|
|
39
|
+
const responseTimes = results
|
|
40
|
+
.filter(r => r.success)
|
|
41
|
+
.map(r => ({
|
|
42
|
+
timestamp: r.timestamp,
|
|
43
|
+
duration: r.duration,
|
|
44
|
+
scenario: r.scenario
|
|
45
|
+
}));
|
|
46
|
+
const errors = results
|
|
47
|
+
.filter(r => !r.success)
|
|
48
|
+
.map(r => ({
|
|
49
|
+
timestamp: r.timestamp,
|
|
50
|
+
error: r.error || 'Unknown error',
|
|
51
|
+
scenario: r.scenario
|
|
52
|
+
}));
|
|
53
|
+
return {
|
|
54
|
+
responseTimes,
|
|
55
|
+
errors,
|
|
56
|
+
scenarios: ScenarioCalculator.groupByScenario(results)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
static calculateScenarioStatistics(results) {
|
|
60
|
+
const scenarioGroups = ScenarioCalculator.groupByScenario(results);
|
|
61
|
+
return scenarioGroups.map((scenario) => {
|
|
62
|
+
const percentiles = statistics_1.StatisticsCalculator.calculatePercentiles(scenario.responseTimes, [50, 90, 95, 99]);
|
|
63
|
+
return {
|
|
64
|
+
...scenario,
|
|
65
|
+
percentiles,
|
|
66
|
+
minResponseTime: scenario.responseTimes.length > 0 ? Math.min(...scenario.responseTimes) : 0,
|
|
67
|
+
maxResponseTime: scenario.responseTimes.length > 0 ? Math.max(...scenario.responseTimes) : 0,
|
|
68
|
+
p50: percentiles[50] || 0,
|
|
69
|
+
p90: percentiles[90] || 0,
|
|
70
|
+
p95: percentiles[95] || 0,
|
|
71
|
+
p99: percentiles[99] || 0
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.ScenarioCalculator = ScenarioCalculator;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { TestResult, StepStatistics } from '../../metrics/types';
|
|
2
|
+
export type { StepStatistics } from '../../metrics/types';
|
|
3
|
+
export interface StepResponseTime {
|
|
4
|
+
step_name: string;
|
|
5
|
+
count: number;
|
|
6
|
+
avg: number;
|
|
7
|
+
min: number;
|
|
8
|
+
max: number;
|
|
9
|
+
p50: number;
|
|
10
|
+
p90: number;
|
|
11
|
+
p95: number;
|
|
12
|
+
p99: number;
|
|
13
|
+
response_times: number[];
|
|
14
|
+
timeline_data: Array<{
|
|
15
|
+
duration: number;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
vu_id: number;
|
|
18
|
+
iteration: number;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
export declare class StepStatisticsCalculator {
|
|
22
|
+
static calculateStepStatistics(results: TestResult[]): StepStatistics[];
|
|
23
|
+
static calculateStepResponseTimes(results: TestResult[]): StepResponseTime[];
|
|
24
|
+
}
|