viberadar 0.3.208 → 0.3.210
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/server/index.d.ts.map +1 -1
- package/dist/server/index.js +307 -52
- package/dist/server/index.js.map +1 -1
- package/dist/ui/dashboard.html +350 -172
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAO7B,OAAO,EAAE,UAAU,EAA4H,MAAM,YAAY,CAAC;AAOlK,UAAU,aAAa;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;CACrB;AA0vED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAO7B,OAAO,EAAE,UAAU,EAA4H,MAAM,YAAY,CAAC;AAOlK,UAAU,aAAa;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;CACrB;AA0vED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAuzG1G"}
|
package/dist/server/index.js
CHANGED
|
@@ -2095,7 +2095,7 @@ function startServer({ data: initialData, port, projectRoot }) {
|
|
|
2095
2095
|
let loadRunning = false;
|
|
2096
2096
|
let loadProc = null;
|
|
2097
2097
|
let loadState = {
|
|
2098
|
-
status: 'idle', startTime: 0, buckets: [], totalRequests: 0,
|
|
2098
|
+
runId: null, status: 'idle', startTime: 0, buckets: [], totalRequests: 0,
|
|
2099
2099
|
totalErrors: 0, logs: [], script: '', config: null, summary: null,
|
|
2100
2100
|
};
|
|
2101
2101
|
let probeRunning = false;
|
|
@@ -2184,36 +2184,222 @@ function startServer({ data: initialData, port, projectRoot }) {
|
|
|
2184
2184
|
probeRunning = false;
|
|
2185
2185
|
}
|
|
2186
2186
|
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
return parseFloat(m[1]);
|
|
2193
|
-
if ((m = s.match(/^([\d.]+)s$/)))
|
|
2194
|
-
return parseFloat(m[1]) * 1000;
|
|
2195
|
-
if ((m = s.match(/^(\d+)m([\d.]+)s$/)))
|
|
2196
|
-
return parseInt(m[1]) * 60000 + parseFloat(m[2]) * 1000;
|
|
2197
|
-
return 0;
|
|
2187
|
+
const loadRunsDir = path.join(projectRoot, '.viberadar', 'load-runs');
|
|
2188
|
+
const MAX_LOAD_RUN_HISTORY = 50;
|
|
2189
|
+
function sanitizeLoadScriptName(name) {
|
|
2190
|
+
const raw = typeof name === 'string' && name.trim() ? name.trim() : 'Без названия';
|
|
2191
|
+
return raw.replace(/[^a-zA-Zа-яА-ЯёЁ0-9_\- .]/g, '_').slice(0, 80);
|
|
2198
2192
|
}
|
|
2199
|
-
function
|
|
2200
|
-
const
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
s.p90Duration = parseK6Dur(dur[2]);
|
|
2205
|
-
s.p95Duration = parseK6Dur(dur[3]);
|
|
2206
|
-
}
|
|
2207
|
-
const reqs = text.match(/\bhttp_reqs[^:]*:\s+(\d+)\s+([\d.]+)\/s/);
|
|
2208
|
-
if (reqs) {
|
|
2209
|
-
s.totalRequests = parseInt(reqs[1]);
|
|
2210
|
-
s.rps = parseFloat(reqs[2]);
|
|
2211
|
-
}
|
|
2212
|
-
const fail = text.match(/http_req_failed[^:]*:\s+([\d.]+)%/);
|
|
2213
|
-
if (fail)
|
|
2214
|
-
s.errorPct = parseFloat(fail[1]);
|
|
2215
|
-
return s;
|
|
2193
|
+
function normalizeLoadDuration(value) {
|
|
2194
|
+
const raw = typeof value === 'string' && value.trim() ? value.trim() : '30s';
|
|
2195
|
+
if (!/^(\d+(ms|s|m|h))+$/.test(raw))
|
|
2196
|
+
return '30s';
|
|
2197
|
+
return raw;
|
|
2216
2198
|
}
|
|
2199
|
+
function normalizeLoadVus(value) {
|
|
2200
|
+
const n = typeof value === 'number' ? value : parseInt(String(value || '10'), 10);
|
|
2201
|
+
if (!Number.isFinite(n) || n < 1)
|
|
2202
|
+
return 10;
|
|
2203
|
+
return Math.min(Math.floor(n), 10000);
|
|
2204
|
+
}
|
|
2205
|
+
function sanitizeLoadEnvVars(value) {
|
|
2206
|
+
if (!value || typeof value !== 'object')
|
|
2207
|
+
return {};
|
|
2208
|
+
const out = {};
|
|
2209
|
+
for (const [key, val] of Object.entries(value)) {
|
|
2210
|
+
const envKey = key.trim();
|
|
2211
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(envKey))
|
|
2212
|
+
continue;
|
|
2213
|
+
if (val == null)
|
|
2214
|
+
continue;
|
|
2215
|
+
out[envKey] = String(val);
|
|
2216
|
+
}
|
|
2217
|
+
return out;
|
|
2218
|
+
}
|
|
2219
|
+
function redactLoadEnvVars(envVars) {
|
|
2220
|
+
const out = {};
|
|
2221
|
+
for (const key of Object.keys(envVars)) {
|
|
2222
|
+
out[key] = /token|secret|password|key/i.test(key) ? '***' : envVars[key];
|
|
2223
|
+
}
|
|
2224
|
+
return out;
|
|
2225
|
+
}
|
|
2226
|
+
function resolveLoadLocalPath(value) {
|
|
2227
|
+
if (typeof value !== 'string' || !value.trim())
|
|
2228
|
+
return undefined;
|
|
2229
|
+
const raw = value.trim();
|
|
2230
|
+
return path.resolve(path.isAbsolute(raw) ? raw : path.join(projectRoot, raw));
|
|
2231
|
+
}
|
|
2232
|
+
function copyLoadDataFiles(sourcePath, workDir) {
|
|
2233
|
+
if (!sourcePath || !fs.existsSync(sourcePath))
|
|
2234
|
+
return 0;
|
|
2235
|
+
const stat = fs.statSync(sourcePath);
|
|
2236
|
+
let copied = 0;
|
|
2237
|
+
const copyOne = (src, dst) => {
|
|
2238
|
+
const st = fs.statSync(src);
|
|
2239
|
+
if (st.isDirectory()) {
|
|
2240
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
2241
|
+
for (const name of fs.readdirSync(src)) {
|
|
2242
|
+
if (name === '.git' || name === 'node_modules' || name === 'dist')
|
|
2243
|
+
continue;
|
|
2244
|
+
copyOne(path.join(src, name), path.join(dst, name));
|
|
2245
|
+
}
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
if (!st.isFile())
|
|
2249
|
+
return;
|
|
2250
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
2251
|
+
fs.copyFileSync(src, dst);
|
|
2252
|
+
copied++;
|
|
2253
|
+
};
|
|
2254
|
+
if (stat.isDirectory()) {
|
|
2255
|
+
for (const name of fs.readdirSync(sourcePath)) {
|
|
2256
|
+
if (name === '.git' || name === 'node_modules' || name === 'dist')
|
|
2257
|
+
continue;
|
|
2258
|
+
copyOne(path.join(sourcePath, name), path.join(workDir, name));
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
else if (stat.isFile()) {
|
|
2262
|
+
copyOne(sourcePath, path.join(workDir, path.basename(sourcePath)));
|
|
2263
|
+
}
|
|
2264
|
+
return copied;
|
|
2265
|
+
}
|
|
2266
|
+
function buildLoadConfig(cfg, paths) {
|
|
2267
|
+
const envVars = sanitizeLoadEnvVars(cfg.envVars);
|
|
2268
|
+
const baseUrl = typeof cfg.baseUrl === 'string' && cfg.baseUrl.trim() ? cfg.baseUrl.trim() : 'http://localhost:5000';
|
|
2269
|
+
const dataDir = resolveLoadLocalPath(cfg.dataDir);
|
|
2270
|
+
const resultDir = resolveLoadLocalPath(cfg.resultDir);
|
|
2271
|
+
envVars.BASE_URL = baseUrl;
|
|
2272
|
+
const config = {
|
|
2273
|
+
vus: normalizeLoadVus(cfg.vus),
|
|
2274
|
+
duration: normalizeLoadDuration(cfg.duration),
|
|
2275
|
+
baseUrl,
|
|
2276
|
+
scriptName: sanitizeLoadScriptName(cfg.scriptName),
|
|
2277
|
+
dataDir,
|
|
2278
|
+
resultDir,
|
|
2279
|
+
runDir: paths?.runDir,
|
|
2280
|
+
workDir: paths?.workDir,
|
|
2281
|
+
resultPath: paths?.resultPath,
|
|
2282
|
+
dataFilesCopied: paths?.dataFilesCopied,
|
|
2283
|
+
envVars: redactLoadEnvVars(envVars),
|
|
2284
|
+
};
|
|
2285
|
+
return { config, envVars, dataDir, resultDir };
|
|
2286
|
+
}
|
|
2287
|
+
function flattenK6Checks(group) {
|
|
2288
|
+
let passes = 0;
|
|
2289
|
+
let fails = 0;
|
|
2290
|
+
for (const check of group?.checks || []) {
|
|
2291
|
+
passes += Number(check.passes || 0);
|
|
2292
|
+
fails += Number(check.fails || 0);
|
|
2293
|
+
}
|
|
2294
|
+
for (const child of group?.groups || []) {
|
|
2295
|
+
const nested = flattenK6Checks(child);
|
|
2296
|
+
passes += nested.passes;
|
|
2297
|
+
fails += nested.fails;
|
|
2298
|
+
}
|
|
2299
|
+
return { passes, fails };
|
|
2300
|
+
}
|
|
2301
|
+
function normalizeK6Summary(raw, exitCode) {
|
|
2302
|
+
const metrics = raw?.metrics || {};
|
|
2303
|
+
const duration = metrics.http_req_duration?.values || {};
|
|
2304
|
+
const reqs = metrics.http_reqs?.values || {};
|
|
2305
|
+
const failed = metrics.http_req_failed?.values || {};
|
|
2306
|
+
const checks = flattenK6Checks(raw?.root_group);
|
|
2307
|
+
let thresholdsPassed = 0;
|
|
2308
|
+
let thresholdsFailed = 0;
|
|
2309
|
+
for (const metric of Object.values(metrics)) {
|
|
2310
|
+
for (const threshold of Object.values(metric?.thresholds || {})) {
|
|
2311
|
+
if (threshold?.ok === false)
|
|
2312
|
+
thresholdsFailed++;
|
|
2313
|
+
else if (threshold?.ok === true)
|
|
2314
|
+
thresholdsPassed++;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
return {
|
|
2318
|
+
totalRequests: typeof reqs.count === 'number' ? reqs.count : undefined,
|
|
2319
|
+
rps: typeof reqs.rate === 'number' ? reqs.rate : undefined,
|
|
2320
|
+
avgDuration: typeof duration.avg === 'number' ? duration.avg : undefined,
|
|
2321
|
+
p90Duration: typeof duration['p(90)'] === 'number' ? duration['p(90)'] : undefined,
|
|
2322
|
+
p95Duration: typeof duration['p(95)'] === 'number' ? duration['p(95)'] : undefined,
|
|
2323
|
+
p99Duration: typeof duration['p(99)'] === 'number' ? duration['p(99)'] : undefined,
|
|
2324
|
+
errorPct: typeof failed.rate === 'number' ? failed.rate * 100 : undefined,
|
|
2325
|
+
testRunDurationMs: typeof raw?.state?.testRunDurationMs === 'number' ? raw.state.testRunDurationMs : undefined,
|
|
2326
|
+
checksPassed: checks.passes,
|
|
2327
|
+
checksFailed: checks.fails,
|
|
2328
|
+
thresholdsPassed,
|
|
2329
|
+
thresholdsFailed,
|
|
2330
|
+
exitCode,
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
function readK6Summary(summaryPath, exitCode) {
|
|
2334
|
+
try {
|
|
2335
|
+
if (!fs.existsSync(summaryPath))
|
|
2336
|
+
return { exitCode };
|
|
2337
|
+
return normalizeK6Summary(JSON.parse(fs.readFileSync(summaryPath, 'utf-8')), exitCode);
|
|
2338
|
+
}
|
|
2339
|
+
catch {
|
|
2340
|
+
return { exitCode };
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
function readLoadRunIndex() {
|
|
2344
|
+
try {
|
|
2345
|
+
const p = path.join(loadRunsDir, 'index.json');
|
|
2346
|
+
const parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
2347
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
2348
|
+
}
|
|
2349
|
+
catch {
|
|
2350
|
+
return [];
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
function writeLoadRunIndex(items) {
|
|
2354
|
+
fs.mkdirSync(loadRunsDir, { recursive: true });
|
|
2355
|
+
fs.writeFileSync(path.join(loadRunsDir, 'index.json'), JSON.stringify(items, null, 2), 'utf-8');
|
|
2356
|
+
}
|
|
2357
|
+
function saveLoadRun() {
|
|
2358
|
+
if (!loadState.runId)
|
|
2359
|
+
return;
|
|
2360
|
+
fs.mkdirSync(loadRunsDir, { recursive: true });
|
|
2361
|
+
const record = {
|
|
2362
|
+
...loadState,
|
|
2363
|
+
scriptName: loadState.config?.scriptName,
|
|
2364
|
+
createdAt: new Date(loadState.startTime || Date.now()).toISOString(),
|
|
2365
|
+
};
|
|
2366
|
+
fs.writeFileSync(path.join(loadRunsDir, `${loadState.runId}.json`), JSON.stringify(record, null, 2), 'utf-8');
|
|
2367
|
+
const index = readLoadRunIndex().filter(i => i.runId !== loadState.runId);
|
|
2368
|
+
index.unshift({
|
|
2369
|
+
runId: loadState.runId,
|
|
2370
|
+
scriptName: loadState.config?.scriptName || 'Без названия',
|
|
2371
|
+
createdAt: record.createdAt,
|
|
2372
|
+
status: loadState.status,
|
|
2373
|
+
startTime: loadState.startTime,
|
|
2374
|
+
endTime: loadState.endTime,
|
|
2375
|
+
config: loadState.config,
|
|
2376
|
+
summary: loadState.summary,
|
|
2377
|
+
});
|
|
2378
|
+
const compact = index.slice(0, MAX_LOAD_RUN_HISTORY);
|
|
2379
|
+
writeLoadRunIndex(compact);
|
|
2380
|
+
for (const old of index.slice(MAX_LOAD_RUN_HISTORY)) {
|
|
2381
|
+
try {
|
|
2382
|
+
fs.unlinkSync(path.join(loadRunsDir, `${old.runId}.json`));
|
|
2383
|
+
}
|
|
2384
|
+
catch { }
|
|
2385
|
+
try {
|
|
2386
|
+
fs.rmSync(path.join(loadRunsDir, old.runId), { recursive: true, force: true });
|
|
2387
|
+
}
|
|
2388
|
+
catch { }
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
function loadLastRunIntoState() {
|
|
2392
|
+
const latest = readLoadRunIndex()[0];
|
|
2393
|
+
if (!latest)
|
|
2394
|
+
return;
|
|
2395
|
+
try {
|
|
2396
|
+
const record = JSON.parse(fs.readFileSync(path.join(loadRunsDir, `${latest.runId}.json`), 'utf-8'));
|
|
2397
|
+
if (record && typeof record === 'object')
|
|
2398
|
+
loadState = record;
|
|
2399
|
+
}
|
|
2400
|
+
catch { }
|
|
2401
|
+
}
|
|
2402
|
+
loadLastRunIntoState();
|
|
2217
2403
|
// ── SSE clients ────────────────────────────────────────────────────────────
|
|
2218
2404
|
const sseClients = new Set();
|
|
2219
2405
|
function broadcast(event, payload = {}) {
|
|
@@ -4911,7 +5097,7 @@ a{color:var(--blue)}
|
|
|
4911
5097
|
req.on('data', (d) => { body += d; });
|
|
4912
5098
|
req.on('end', () => {
|
|
4913
5099
|
try {
|
|
4914
|
-
const { name, script } = JSON.parse(body);
|
|
5100
|
+
const { name, script, vus, duration, baseUrl, dataDir, resultDir } = JSON.parse(body);
|
|
4915
5101
|
if (!name || !script) {
|
|
4916
5102
|
res.writeHead(400, jsonH);
|
|
4917
5103
|
res.end(JSON.stringify({ error: 'name and script required' }));
|
|
@@ -4921,7 +5107,17 @@ a{color:var(--blue)}
|
|
|
4921
5107
|
const safeName = name.replace(/[^a-zA-Zа-яА-ЯёЁ0-9_\- ]/g, '_').slice(0, 80);
|
|
4922
5108
|
const date = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
|
4923
5109
|
const fileName = `${Date.now()}-${safeName.replace(/\s+/g, '_')}.json`;
|
|
4924
|
-
const entry = {
|
|
5110
|
+
const entry = {
|
|
5111
|
+
name: safeName,
|
|
5112
|
+
date,
|
|
5113
|
+
script,
|
|
5114
|
+
fileName,
|
|
5115
|
+
vus: normalizeLoadVus(vus),
|
|
5116
|
+
duration: normalizeLoadDuration(duration),
|
|
5117
|
+
baseUrl: typeof baseUrl === 'string' && baseUrl.trim() ? baseUrl.trim() : 'http://localhost:5000',
|
|
5118
|
+
dataDir: resolveLoadLocalPath(dataDir),
|
|
5119
|
+
resultDir: resolveLoadLocalPath(resultDir),
|
|
5120
|
+
};
|
|
4925
5121
|
// overwrite if same name exists
|
|
4926
5122
|
const existing = fs.readdirSync(scriptsDir).find(f => {
|
|
4927
5123
|
try {
|
|
@@ -5011,6 +5207,31 @@ a{color:var(--blue)}
|
|
|
5011
5207
|
res.end(JSON.stringify(loadState));
|
|
5012
5208
|
return;
|
|
5013
5209
|
}
|
|
5210
|
+
if (url === '/api/load/runs' && req.method === 'GET') {
|
|
5211
|
+
res.writeHead(200, jsonH);
|
|
5212
|
+
res.end(JSON.stringify(readLoadRunIndex()));
|
|
5213
|
+
return;
|
|
5214
|
+
}
|
|
5215
|
+
const loadRunMatch = url.match(/^\/api\/load\/runs\/([^/]+)$/);
|
|
5216
|
+
if (loadRunMatch && req.method === 'GET') {
|
|
5217
|
+
try {
|
|
5218
|
+
const runId = decodeURIComponent(loadRunMatch[1]);
|
|
5219
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(runId)) {
|
|
5220
|
+
res.writeHead(400, jsonH);
|
|
5221
|
+
res.end(JSON.stringify({ error: 'Bad run id' }));
|
|
5222
|
+
return;
|
|
5223
|
+
}
|
|
5224
|
+
const runPath = path.join(loadRunsDir, `${runId}.json`);
|
|
5225
|
+
const record = JSON.parse(fs.readFileSync(runPath, 'utf-8'));
|
|
5226
|
+
res.writeHead(200, jsonH);
|
|
5227
|
+
res.end(JSON.stringify(record));
|
|
5228
|
+
}
|
|
5229
|
+
catch {
|
|
5230
|
+
res.writeHead(404, jsonH);
|
|
5231
|
+
res.end(JSON.stringify({ error: 'Run not found' }));
|
|
5232
|
+
}
|
|
5233
|
+
return;
|
|
5234
|
+
}
|
|
5014
5235
|
if (url === '/api/load/stop' && req.method === 'POST') {
|
|
5015
5236
|
if (loadProc) {
|
|
5016
5237
|
try {
|
|
@@ -5024,7 +5245,7 @@ a{color:var(--blue)}
|
|
|
5024
5245
|
loadState.status = 'stopped';
|
|
5025
5246
|
loadState.endTime = Date.now();
|
|
5026
5247
|
}
|
|
5027
|
-
broadcast('load-done', { status: loadState.status, summary: loadState.summary });
|
|
5248
|
+
broadcast('load-done', { runId: loadState.runId, status: loadState.status, summary: loadState.summary });
|
|
5028
5249
|
res.writeHead(200, jsonH);
|
|
5029
5250
|
res.end(JSON.stringify({ ok: true }));
|
|
5030
5251
|
return;
|
|
@@ -5053,9 +5274,20 @@ a{color:var(--blue)}
|
|
|
5053
5274
|
res.end(JSON.stringify({ error: 'No script provided' }));
|
|
5054
5275
|
return;
|
|
5055
5276
|
}
|
|
5056
|
-
const
|
|
5057
|
-
const
|
|
5277
|
+
const runId = `load-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5278
|
+
const runDir = path.join(loadRunsDir, runId);
|
|
5279
|
+
const workDir = path.join(runDir, 'work');
|
|
5280
|
+
const defaultResultDir = path.join(runDir, 'results');
|
|
5281
|
+
const initial = buildLoadConfig(cfg);
|
|
5282
|
+
const resultPath = initial.resultDir ? path.join(initial.resultDir, runId) : defaultResultDir;
|
|
5283
|
+
const scriptPath = path.join(workDir, 'script.js');
|
|
5284
|
+
const jsonOutPath = path.join(resultPath, 'metrics.ndjson');
|
|
5285
|
+
const summaryPath = path.join(resultPath, 'summary.json');
|
|
5286
|
+
let dataFilesCopied = 0;
|
|
5058
5287
|
try {
|
|
5288
|
+
fs.mkdirSync(workDir, { recursive: true });
|
|
5289
|
+
fs.mkdirSync(resultPath, { recursive: true });
|
|
5290
|
+
dataFilesCopied = copyLoadDataFiles(initial.dataDir, workDir);
|
|
5059
5291
|
fs.writeFileSync(scriptPath, script, 'utf-8');
|
|
5060
5292
|
}
|
|
5061
5293
|
catch (e) {
|
|
@@ -5063,25 +5295,36 @@ a{color:var(--blue)}
|
|
|
5063
5295
|
res.end(JSON.stringify({ error: e.message }));
|
|
5064
5296
|
return;
|
|
5065
5297
|
}
|
|
5298
|
+
const { config: loadConfig, envVars } = buildLoadConfig(cfg, {
|
|
5299
|
+
runDir,
|
|
5300
|
+
workDir,
|
|
5301
|
+
resultPath,
|
|
5302
|
+
dataFilesCopied,
|
|
5303
|
+
});
|
|
5066
5304
|
loadRunning = true;
|
|
5067
5305
|
loadState = {
|
|
5068
|
-
status: 'running', startTime: Date.now(), buckets: [], totalRequests: 0,
|
|
5069
|
-
totalErrors: 0, logs: [], script, config:
|
|
5306
|
+
runId, status: 'running', startTime: Date.now(), buckets: [], totalRequests: 0,
|
|
5307
|
+
totalErrors: 0, logs: [], script, config: loadConfig, summary: null,
|
|
5070
5308
|
};
|
|
5071
|
-
broadcast('load-started', { config:
|
|
5309
|
+
broadcast('load-started', { runId, config: loadConfig });
|
|
5072
5310
|
res.writeHead(200, jsonH);
|
|
5073
|
-
res.end(JSON.stringify({ ok: true }));
|
|
5074
|
-
// Build --env flags from cfg.envVars (e.g. { TOKEN: 'abc', BASE_URL: '...' })
|
|
5075
|
-
const envVars = (typeof cfg.envVars === 'object' && cfg.envVars !== null)
|
|
5076
|
-
? cfg.envVars
|
|
5077
|
-
: {};
|
|
5311
|
+
res.end(JSON.stringify({ ok: true, runId }));
|
|
5078
5312
|
const envFlags = [];
|
|
5079
5313
|
for (const [k, v] of Object.entries(envVars)) {
|
|
5080
5314
|
if (k && v !== undefined && v !== '')
|
|
5081
5315
|
envFlags.push('--env', `${k}=${v}`);
|
|
5082
5316
|
}
|
|
5083
|
-
|
|
5084
|
-
|
|
5317
|
+
const args = [
|
|
5318
|
+
'run',
|
|
5319
|
+
'--vus', String(loadConfig.vus),
|
|
5320
|
+
'--duration', loadConfig.duration,
|
|
5321
|
+
...envFlags,
|
|
5322
|
+
'--summary-export', summaryPath,
|
|
5323
|
+
'--out', `json=${jsonOutPath}`,
|
|
5324
|
+
scriptPath,
|
|
5325
|
+
];
|
|
5326
|
+
loadProc = (0, child_process_1.spawn)('k6', args, {
|
|
5327
|
+
cwd: workDir, env: { ...process.env }, shell: WIN, stdio: 'pipe',
|
|
5085
5328
|
});
|
|
5086
5329
|
const addLog = (line) => {
|
|
5087
5330
|
loadState.logs.push(line);
|
|
@@ -5156,7 +5399,7 @@ a{color:var(--blue)}
|
|
|
5156
5399
|
}
|
|
5157
5400
|
if (changed) {
|
|
5158
5401
|
const slice = loadState.buckets.slice(-30);
|
|
5159
|
-
broadcast('load-progress', { buckets: slice, total: loadState.totalRequests, errors: loadState.totalErrors });
|
|
5402
|
+
broadcast('load-progress', { runId: loadState.runId, buckets: slice, total: loadState.totalRequests, errors: loadState.totalErrors });
|
|
5160
5403
|
}
|
|
5161
5404
|
}
|
|
5162
5405
|
catch { }
|
|
@@ -5166,17 +5409,23 @@ a{color:var(--blue)}
|
|
|
5166
5409
|
loadRunning = false;
|
|
5167
5410
|
loadProc = null;
|
|
5168
5411
|
if (loadState.status === 'running') {
|
|
5169
|
-
loadState.status =
|
|
5412
|
+
loadState.status = code === 0 ? 'done' : 'error';
|
|
5170
5413
|
}
|
|
5171
5414
|
loadState.endTime = Date.now();
|
|
5172
|
-
loadState.summary =
|
|
5173
|
-
|
|
5415
|
+
loadState.summary = readK6Summary(summaryPath, code);
|
|
5416
|
+
if (loadState.summary?.totalRequests != null)
|
|
5417
|
+
loadState.totalRequests = loadState.summary.totalRequests;
|
|
5418
|
+
if (loadState.summary?.errorPct != null && loadState.summary.totalRequests != null) {
|
|
5419
|
+
loadState.totalErrors = Math.round(loadState.summary.totalRequests * (loadState.summary.errorPct / 100));
|
|
5420
|
+
}
|
|
5421
|
+
saveLoadRun();
|
|
5422
|
+
broadcast('load-done', { runId: loadState.runId, status: loadState.status, summary: loadState.summary });
|
|
5174
5423
|
try {
|
|
5175
|
-
fs.
|
|
5424
|
+
fs.writeFileSync(path.join(resultPath, 'k6.log'), loadState.logs.join('\n'), 'utf-8');
|
|
5176
5425
|
}
|
|
5177
5426
|
catch { }
|
|
5178
5427
|
try {
|
|
5179
|
-
fs.
|
|
5428
|
+
fs.writeFileSync(path.join(resultPath, 'config.json'), JSON.stringify(loadState.config, null, 2), 'utf-8');
|
|
5180
5429
|
}
|
|
5181
5430
|
catch { }
|
|
5182
5431
|
});
|
|
@@ -5187,9 +5436,15 @@ a{color:var(--blue)}
|
|
|
5187
5436
|
loadState.status = 'error';
|
|
5188
5437
|
loadState.endTime = Date.now();
|
|
5189
5438
|
addLog(`❌ k6 не запустился: ${err.message}`);
|
|
5190
|
-
|
|
5439
|
+
loadState.summary = { exitCode: null };
|
|
5440
|
+
saveLoadRun();
|
|
5441
|
+
broadcast('load-done', { runId: loadState.runId, status: 'error', summary: loadState.summary });
|
|
5442
|
+
try {
|
|
5443
|
+
fs.writeFileSync(path.join(resultPath, 'k6.log'), loadState.logs.join('\n'), 'utf-8');
|
|
5444
|
+
}
|
|
5445
|
+
catch { }
|
|
5191
5446
|
try {
|
|
5192
|
-
fs.
|
|
5447
|
+
fs.writeFileSync(path.join(resultPath, 'config.json'), JSON.stringify(loadState.config, null, 2), 'utf-8');
|
|
5193
5448
|
}
|
|
5194
5449
|
catch { }
|
|
5195
5450
|
});
|