@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,114 @@
|
|
|
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.FileScanner = void 0;
|
|
37
|
+
const fs = __importStar(require("fs/promises"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
class FileScanner {
|
|
40
|
+
constructor(testsDir) {
|
|
41
|
+
this.testsDir = testsDir;
|
|
42
|
+
}
|
|
43
|
+
async scanTestFiles() {
|
|
44
|
+
const tests = [];
|
|
45
|
+
// Only scan dedicated test directories
|
|
46
|
+
const searchDirs = [
|
|
47
|
+
path.join(this.testsDir, 'tests'),
|
|
48
|
+
path.join(this.testsDir, 'tmp/tests')
|
|
49
|
+
];
|
|
50
|
+
for (const dir of searchDirs) {
|
|
51
|
+
try {
|
|
52
|
+
await this.scanDirForTests(dir, tests, this.testsDir);
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
// Directory might not exist
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return tests.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
59
|
+
}
|
|
60
|
+
async scanDirForTests(dir, tests, baseDir) {
|
|
61
|
+
try {
|
|
62
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const fullPath = path.join(dir, entry.name);
|
|
65
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules' &&
|
|
66
|
+
entry.name !== 'data' && entry.name !== 'config' && entry.name !== 'environments') {
|
|
67
|
+
await this.scanDirForTests(fullPath, tests, baseDir);
|
|
68
|
+
}
|
|
69
|
+
else if (entry.isFile() && (entry.name.endsWith('.yml') || entry.name.endsWith('.yaml') || entry.name.endsWith('.json'))) {
|
|
70
|
+
// Skip obvious non-test files
|
|
71
|
+
if (entry.name.includes('package') || entry.name.includes('tsconfig') ||
|
|
72
|
+
entry.name.includes('env') || entry.name === 'CNAME' ||
|
|
73
|
+
entry.name.includes('credentials') || entry.name.includes('config'))
|
|
74
|
+
continue;
|
|
75
|
+
try {
|
|
76
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
77
|
+
// Only include files that look like actual test configs (have scenarios or steps)
|
|
78
|
+
if (!content.includes('scenarios:') && !content.includes('steps:') &&
|
|
79
|
+
!content.includes('"scenarios"') && !content.includes('"steps"')) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const stat = await fs.stat(fullPath);
|
|
83
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
84
|
+
// Detect test type from content or path (use forward slashes for cross-platform matching)
|
|
85
|
+
const normalizedPath = fullPath.replace(/\\/g, '/');
|
|
86
|
+
let testType = 'api';
|
|
87
|
+
if (content.includes('protocol: web') || content.includes('playwright') || normalizedPath.includes('/web/')) {
|
|
88
|
+
testType = 'web';
|
|
89
|
+
}
|
|
90
|
+
else if (content.includes('protocol: http') || content.includes('protocol: https') || normalizedPath.includes('/api/')) {
|
|
91
|
+
testType = 'api';
|
|
92
|
+
}
|
|
93
|
+
// Normalize paths to forward slashes for cross-platform compatibility
|
|
94
|
+
const normalizedRelativePath = relativePath.replace(/\\/g, '/');
|
|
95
|
+
tests.push({
|
|
96
|
+
name: entry.name.replace(/\.(yml|yaml|json)$/, ''),
|
|
97
|
+
path: fullPath, // Keep native path for filesystem operations
|
|
98
|
+
relativePath: normalizedRelativePath, // Use forward slashes for display/URLs
|
|
99
|
+
type: testType,
|
|
100
|
+
lastModified: stat.mtime.toISOString()
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
// Skip unreadable files
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
// Skip inaccessible directories
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.FileScanner = FileScanner;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { FileScanner } from './file-scanner';
|
|
2
|
+
export { ResultsManager } from './results-manager';
|
|
3
|
+
export { MetricsParser, MetricsParserOptions } from './metrics-parser';
|
|
4
|
+
export { TestExecutor, TestRunOptions, TestExecutorCallbacks, TestExecutorOptions } from './test-executor';
|
|
5
|
+
export { WorkersManager, WorkersInfo } from './workers-manager';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WorkersManager = exports.TestExecutor = exports.MetricsParser = exports.ResultsManager = exports.FileScanner = void 0;
|
|
4
|
+
var file_scanner_1 = require("./file-scanner");
|
|
5
|
+
Object.defineProperty(exports, "FileScanner", { enumerable: true, get: function () { return file_scanner_1.FileScanner; } });
|
|
6
|
+
var results_manager_1 = require("./results-manager");
|
|
7
|
+
Object.defineProperty(exports, "ResultsManager", { enumerable: true, get: function () { return results_manager_1.ResultsManager; } });
|
|
8
|
+
var metrics_parser_1 = require("./metrics-parser");
|
|
9
|
+
Object.defineProperty(exports, "MetricsParser", { enumerable: true, get: function () { return metrics_parser_1.MetricsParser; } });
|
|
10
|
+
var test_executor_1 = require("./test-executor");
|
|
11
|
+
Object.defineProperty(exports, "TestExecutor", { enumerable: true, get: function () { return test_executor_1.TestExecutor; } });
|
|
12
|
+
var workers_manager_1 = require("./workers-manager");
|
|
13
|
+
Object.defineProperty(exports, "WorkersManager", { enumerable: true, get: function () { return workers_manager_1.WorkersManager; } });
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { InfrastructureMetrics } from '../types';
|
|
2
|
+
export interface InfluxDBConfig {
|
|
3
|
+
url: string;
|
|
4
|
+
token: string;
|
|
5
|
+
org: string;
|
|
6
|
+
bucket: string;
|
|
7
|
+
}
|
|
8
|
+
export interface InfraQueryOptions {
|
|
9
|
+
host?: string;
|
|
10
|
+
startTime?: Date;
|
|
11
|
+
endTime?: Date;
|
|
12
|
+
testId?: string;
|
|
13
|
+
limit?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class InfluxDBService {
|
|
16
|
+
private client;
|
|
17
|
+
private writeApi;
|
|
18
|
+
private queryApi;
|
|
19
|
+
private config;
|
|
20
|
+
private enabled;
|
|
21
|
+
private fallbackBuffer;
|
|
22
|
+
private readonly MAX_BUFFER_SIZE;
|
|
23
|
+
constructor(config?: Partial<InfluxDBConfig>);
|
|
24
|
+
connect(): Promise<boolean>;
|
|
25
|
+
isEnabled(): boolean;
|
|
26
|
+
writeMetrics(data: InfrastructureMetrics): Promise<void>;
|
|
27
|
+
queryMetrics(options?: InfraQueryOptions): Promise<InfrastructureMetrics[]>;
|
|
28
|
+
queryMetricsByTestRun(testId: string, startTime: Date, endTime: Date): Promise<Record<string, InfrastructureMetrics[]>>;
|
|
29
|
+
getHosts(): Promise<string[]>;
|
|
30
|
+
getLatestMetrics(): Promise<Record<string, InfrastructureMetrics | null>>;
|
|
31
|
+
exportMetrics(options: InfraQueryOptions, format?: 'json' | 'csv'): Promise<string>;
|
|
32
|
+
importMetrics(data: string, format?: 'json' | 'csv'): Promise<number>;
|
|
33
|
+
deleteMetrics(options: InfraQueryOptions): Promise<void>;
|
|
34
|
+
getSnapshot(): Record<string, InfrastructureMetrics[]>;
|
|
35
|
+
private addToFallbackBuffer;
|
|
36
|
+
private queryFallbackBuffer;
|
|
37
|
+
private rowsToMetrics;
|
|
38
|
+
private metricsToCSV;
|
|
39
|
+
private csvToMetrics;
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InfluxDBService = void 0;
|
|
4
|
+
const influxdb_client_1 = require("@influxdata/influxdb-client");
|
|
5
|
+
const logger_1 = require("../../utils/logger");
|
|
6
|
+
class InfluxDBService {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.client = null;
|
|
9
|
+
this.writeApi = null;
|
|
10
|
+
this.queryApi = null;
|
|
11
|
+
this.enabled = false;
|
|
12
|
+
this.fallbackBuffer = new Map();
|
|
13
|
+
this.MAX_BUFFER_SIZE = 120;
|
|
14
|
+
this.config = {
|
|
15
|
+
url: config?.url || process.env.INFLUXDB_URL || 'http://localhost:8086',
|
|
16
|
+
token: config?.token || process.env.INFLUXDB_TOKEN || '',
|
|
17
|
+
org: config?.org || process.env.INFLUXDB_ORG || 'perfornium',
|
|
18
|
+
bucket: config?.bucket || process.env.INFLUXDB_BUCKET || 'metrics'
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async connect() {
|
|
22
|
+
if (!this.config.token) {
|
|
23
|
+
logger_1.logger.info('InfluxDB token not configured, using in-memory storage');
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
this.client = new influxdb_client_1.InfluxDB({
|
|
28
|
+
url: this.config.url,
|
|
29
|
+
token: this.config.token
|
|
30
|
+
});
|
|
31
|
+
this.writeApi = this.client.getWriteApi(this.config.org, this.config.bucket, 'ms');
|
|
32
|
+
this.queryApi = this.client.getQueryApi(this.config.org);
|
|
33
|
+
// Test connection with a simple query
|
|
34
|
+
const query = `from(bucket: "${this.config.bucket}") |> range(start: -1s) |> limit(n: 1)`;
|
|
35
|
+
await this.queryApi.collectRows(query);
|
|
36
|
+
this.enabled = true;
|
|
37
|
+
logger_1.logger.info(`Connected to InfluxDB at ${this.config.url}`);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
logger_1.logger.warn(`Failed to connect to InfluxDB: ${error.message}. Using in-memory storage.`);
|
|
42
|
+
this.enabled = false;
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
isEnabled() {
|
|
47
|
+
return this.enabled;
|
|
48
|
+
}
|
|
49
|
+
async writeMetrics(data) {
|
|
50
|
+
// Always store in fallback buffer for real-time display
|
|
51
|
+
this.addToFallbackBuffer(data);
|
|
52
|
+
if (!this.enabled || !this.writeApi) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const point = new influxdb_client_1.Point('infrastructure_metrics')
|
|
57
|
+
.tag('host', data.host)
|
|
58
|
+
.floatField('cpu_usage', data.metrics?.cpu?.usage_percent || 0)
|
|
59
|
+
.floatField('memory_usage', data.metrics?.memory?.usage_percent || 0)
|
|
60
|
+
.floatField('memory_used_mb', data.metrics?.memory?.used_mb || 0)
|
|
61
|
+
.floatField('memory_total_mb', data.metrics?.memory?.total_mb || 0)
|
|
62
|
+
.floatField('disk_usage', data.metrics?.disk?.usage_percent || 0)
|
|
63
|
+
.floatField('disk_used_gb', data.metrics?.disk?.used_gb || 0)
|
|
64
|
+
.floatField('disk_total_gb', data.metrics?.disk?.total_gb || 0)
|
|
65
|
+
.stringField('disk_path', data.metrics?.disk?.path || '/')
|
|
66
|
+
.floatField('network_bytes_in', data.metrics?.network?.bytes_in || 0)
|
|
67
|
+
.floatField('network_bytes_out', data.metrics?.network?.bytes_out || 0)
|
|
68
|
+
.stringField('network_interface', data.metrics?.network?.interface || 'eth0')
|
|
69
|
+
.intField('interval_seconds', data.interval_seconds || 5)
|
|
70
|
+
.timestamp(new Date(data.timestamp));
|
|
71
|
+
this.writeApi.writePoint(point);
|
|
72
|
+
await this.writeApi.flush();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
logger_1.logger.error(`Failed to write to InfluxDB: ${error.message}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async queryMetrics(options = {}) {
|
|
79
|
+
if (!this.enabled || !this.queryApi) {
|
|
80
|
+
return this.queryFallbackBuffer(options);
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const startTime = options.startTime || new Date(Date.now() - 10 * 60 * 1000); // Default 10 minutes
|
|
84
|
+
const endTime = options.endTime || new Date();
|
|
85
|
+
// Validate time range - start must be before end
|
|
86
|
+
if (startTime.getTime() >= endTime.getTime()) {
|
|
87
|
+
logger_1.logger.debug('Invalid time range for InfluxDB query, using fallback buffer');
|
|
88
|
+
return this.queryFallbackBuffer(options);
|
|
89
|
+
}
|
|
90
|
+
let query = `from(bucket: "${this.config.bucket}")
|
|
91
|
+
|> range(start: ${startTime.toISOString()}, stop: ${endTime.toISOString()})
|
|
92
|
+
|> filter(fn: (r) => r._measurement == "infrastructure_metrics")`;
|
|
93
|
+
if (options.host) {
|
|
94
|
+
query += `\n |> filter(fn: (r) => r.host == "${options.host}")`;
|
|
95
|
+
}
|
|
96
|
+
query += `\n |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")`;
|
|
97
|
+
if (options.limit) {
|
|
98
|
+
query += `\n |> limit(n: ${options.limit})`;
|
|
99
|
+
}
|
|
100
|
+
const rows = await this.queryApi.collectRows(query);
|
|
101
|
+
return this.rowsToMetrics(rows);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
logger_1.logger.error(`Failed to query InfluxDB: ${error.message}`);
|
|
105
|
+
return this.queryFallbackBuffer(options);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async queryMetricsByTestRun(testId, startTime, endTime) {
|
|
109
|
+
const metrics = await this.queryMetrics({ startTime, endTime });
|
|
110
|
+
// Group by host
|
|
111
|
+
const grouped = {};
|
|
112
|
+
for (const metric of metrics) {
|
|
113
|
+
if (!grouped[metric.host]) {
|
|
114
|
+
grouped[metric.host] = [];
|
|
115
|
+
}
|
|
116
|
+
grouped[metric.host].push(metric);
|
|
117
|
+
}
|
|
118
|
+
return grouped;
|
|
119
|
+
}
|
|
120
|
+
async getHosts() {
|
|
121
|
+
if (!this.enabled || !this.queryApi) {
|
|
122
|
+
return Array.from(this.fallbackBuffer.keys());
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const query = `from(bucket: "${this.config.bucket}")
|
|
126
|
+
|> range(start: -24h)
|
|
127
|
+
|> filter(fn: (r) => r._measurement == "infrastructure_metrics")
|
|
128
|
+
|> keep(columns: ["host"])
|
|
129
|
+
|> distinct(column: "host")`;
|
|
130
|
+
const rows = await this.queryApi.collectRows(query);
|
|
131
|
+
return rows.map((row) => row.host).filter(Boolean);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
logger_1.logger.error(`Failed to get hosts from InfluxDB: ${error.message}`);
|
|
135
|
+
return Array.from(this.fallbackBuffer.keys());
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async getLatestMetrics() {
|
|
139
|
+
const hosts = await this.getHosts();
|
|
140
|
+
const result = {};
|
|
141
|
+
for (const host of hosts) {
|
|
142
|
+
const metrics = await this.queryMetrics({ host, limit: 1 });
|
|
143
|
+
result[host] = metrics.length > 0 ? metrics[metrics.length - 1] : null;
|
|
144
|
+
}
|
|
145
|
+
// Include fallback buffer hosts
|
|
146
|
+
for (const [host, buffer] of this.fallbackBuffer) {
|
|
147
|
+
if (!result[host] && buffer.length > 0) {
|
|
148
|
+
result[host] = buffer[buffer.length - 1];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
async exportMetrics(options, format = 'json') {
|
|
154
|
+
const metrics = await this.queryMetrics(options);
|
|
155
|
+
if (format === 'csv') {
|
|
156
|
+
return this.metricsToCSV(metrics);
|
|
157
|
+
}
|
|
158
|
+
return JSON.stringify(metrics, null, 2);
|
|
159
|
+
}
|
|
160
|
+
async importMetrics(data, format = 'json') {
|
|
161
|
+
let metrics;
|
|
162
|
+
if (format === 'csv') {
|
|
163
|
+
metrics = this.csvToMetrics(data);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
metrics = JSON.parse(data);
|
|
167
|
+
}
|
|
168
|
+
let count = 0;
|
|
169
|
+
for (const metric of metrics) {
|
|
170
|
+
await this.writeMetrics(metric);
|
|
171
|
+
count++;
|
|
172
|
+
}
|
|
173
|
+
return count;
|
|
174
|
+
}
|
|
175
|
+
async deleteMetrics(options) {
|
|
176
|
+
if (!this.enabled || !this.client) {
|
|
177
|
+
// Clear fallback buffer for host if specified
|
|
178
|
+
if (options.host) {
|
|
179
|
+
this.fallbackBuffer.delete(options.host);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
this.fallbackBuffer.clear();
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Note: InfluxDB 2.x delete requires Delete API
|
|
187
|
+
// For simplicity, we'll just let data expire via retention policy
|
|
188
|
+
logger_1.logger.warn('Delete operation not fully implemented for InfluxDB. Consider using retention policies.');
|
|
189
|
+
}
|
|
190
|
+
getSnapshot() {
|
|
191
|
+
const snapshot = {};
|
|
192
|
+
for (const [host, metrics] of this.fallbackBuffer) {
|
|
193
|
+
snapshot[host] = [...metrics];
|
|
194
|
+
}
|
|
195
|
+
return snapshot;
|
|
196
|
+
}
|
|
197
|
+
addToFallbackBuffer(data) {
|
|
198
|
+
if (!this.fallbackBuffer.has(data.host)) {
|
|
199
|
+
this.fallbackBuffer.set(data.host, []);
|
|
200
|
+
}
|
|
201
|
+
const buffer = this.fallbackBuffer.get(data.host);
|
|
202
|
+
buffer.push(data);
|
|
203
|
+
if (buffer.length > this.MAX_BUFFER_SIZE) {
|
|
204
|
+
buffer.shift();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
queryFallbackBuffer(options) {
|
|
208
|
+
let result = [];
|
|
209
|
+
if (options.host) {
|
|
210
|
+
result = this.fallbackBuffer.get(options.host) || [];
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
for (const buffer of this.fallbackBuffer.values()) {
|
|
214
|
+
result = result.concat(buffer);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Filter by time range
|
|
218
|
+
if (options.startTime || options.endTime) {
|
|
219
|
+
result = result.filter(m => {
|
|
220
|
+
const ts = new Date(m.timestamp).getTime();
|
|
221
|
+
if (options.startTime && ts < options.startTime.getTime())
|
|
222
|
+
return false;
|
|
223
|
+
if (options.endTime && ts > options.endTime.getTime())
|
|
224
|
+
return false;
|
|
225
|
+
return true;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
// Sort by timestamp
|
|
229
|
+
result.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
230
|
+
if (options.limit) {
|
|
231
|
+
result = result.slice(-options.limit);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
rowsToMetrics(rows) {
|
|
236
|
+
return rows.map(row => ({
|
|
237
|
+
host: row.host,
|
|
238
|
+
timestamp: row._time,
|
|
239
|
+
interval_seconds: row.interval_seconds || 5,
|
|
240
|
+
metrics: {
|
|
241
|
+
cpu: { usage_percent: row.cpu_usage || 0 },
|
|
242
|
+
memory: {
|
|
243
|
+
usage_percent: row.memory_usage || 0,
|
|
244
|
+
used_mb: row.memory_used_mb || 0,
|
|
245
|
+
total_mb: row.memory_total_mb || 0
|
|
246
|
+
},
|
|
247
|
+
disk: {
|
|
248
|
+
usage_percent: row.disk_usage || 0,
|
|
249
|
+
used_gb: row.disk_used_gb || 0,
|
|
250
|
+
total_gb: row.disk_total_gb || 0,
|
|
251
|
+
path: row.disk_path || '/'
|
|
252
|
+
},
|
|
253
|
+
network: {
|
|
254
|
+
bytes_in: row.network_bytes_in || 0,
|
|
255
|
+
bytes_out: row.network_bytes_out || 0,
|
|
256
|
+
interface: row.network_interface || 'eth0'
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
metricsToCSV(metrics) {
|
|
262
|
+
const headers = [
|
|
263
|
+
'timestamp', 'host', 'interval_seconds',
|
|
264
|
+
'cpu_usage', 'memory_usage', 'memory_used_mb', 'memory_total_mb',
|
|
265
|
+
'disk_usage', 'disk_used_gb', 'disk_total_gb', 'disk_path',
|
|
266
|
+
'network_bytes_in', 'network_bytes_out', 'network_interface'
|
|
267
|
+
];
|
|
268
|
+
const rows = metrics.map(m => [
|
|
269
|
+
m.timestamp,
|
|
270
|
+
m.host,
|
|
271
|
+
m.interval_seconds || 5,
|
|
272
|
+
m.metrics?.cpu?.usage_percent || 0,
|
|
273
|
+
m.metrics?.memory?.usage_percent || 0,
|
|
274
|
+
m.metrics?.memory?.used_mb || 0,
|
|
275
|
+
m.metrics?.memory?.total_mb || 0,
|
|
276
|
+
m.metrics?.disk?.usage_percent || 0,
|
|
277
|
+
m.metrics?.disk?.used_gb || 0,
|
|
278
|
+
m.metrics?.disk?.total_gb || 0,
|
|
279
|
+
m.metrics?.disk?.path || '/',
|
|
280
|
+
m.metrics?.network?.bytes_in || 0,
|
|
281
|
+
m.metrics?.network?.bytes_out || 0,
|
|
282
|
+
m.metrics?.network?.interface || 'eth0'
|
|
283
|
+
].join(','));
|
|
284
|
+
return [headers.join(','), ...rows].join('\n');
|
|
285
|
+
}
|
|
286
|
+
csvToMetrics(csv) {
|
|
287
|
+
const lines = csv.trim().split('\n');
|
|
288
|
+
if (lines.length < 2)
|
|
289
|
+
return [];
|
|
290
|
+
const headers = lines[0].split(',');
|
|
291
|
+
const metrics = [];
|
|
292
|
+
for (let i = 1; i < lines.length; i++) {
|
|
293
|
+
const values = lines[i].split(',');
|
|
294
|
+
const row = {};
|
|
295
|
+
headers.forEach((h, idx) => row[h] = values[idx]);
|
|
296
|
+
metrics.push({
|
|
297
|
+
timestamp: row.timestamp,
|
|
298
|
+
host: row.host,
|
|
299
|
+
interval_seconds: parseInt(row.interval_seconds) || 5,
|
|
300
|
+
metrics: {
|
|
301
|
+
cpu: { usage_percent: parseFloat(row.cpu_usage) || 0 },
|
|
302
|
+
memory: {
|
|
303
|
+
usage_percent: parseFloat(row.memory_usage) || 0,
|
|
304
|
+
used_mb: parseFloat(row.memory_used_mb) || 0,
|
|
305
|
+
total_mb: parseFloat(row.memory_total_mb) || 0
|
|
306
|
+
},
|
|
307
|
+
disk: {
|
|
308
|
+
usage_percent: parseFloat(row.disk_usage) || 0,
|
|
309
|
+
used_gb: parseFloat(row.disk_used_gb) || 0,
|
|
310
|
+
total_gb: parseFloat(row.disk_total_gb) || 0,
|
|
311
|
+
path: row.disk_path || '/'
|
|
312
|
+
},
|
|
313
|
+
network: {
|
|
314
|
+
bytes_in: parseFloat(row.network_bytes_in) || 0,
|
|
315
|
+
bytes_out: parseFloat(row.network_bytes_out) || 0,
|
|
316
|
+
interface: row.network_interface || 'eth0'
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return metrics;
|
|
322
|
+
}
|
|
323
|
+
async close() {
|
|
324
|
+
if (this.writeApi) {
|
|
325
|
+
await this.writeApi.close();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
exports.InfluxDBService = InfluxDBService;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { LiveTest } from '../types';
|
|
2
|
+
export interface MetricsParserOptions {
|
|
3
|
+
/** When InfluxDB is enabled, don't limit response times (data is persisted) */
|
|
4
|
+
influxEnabled?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class MetricsParser {
|
|
7
|
+
private influxEnabled;
|
|
8
|
+
constructor(options?: MetricsParserOptions);
|
|
9
|
+
parseOutputForMetrics(line: string, test: LiveTest): boolean;
|
|
10
|
+
private addHistoryEntry;
|
|
11
|
+
parseNetworkData(line: string): any | null;
|
|
12
|
+
}
|