viberadar 0.3.207 → 0.3.209
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 +260 -46
- package/dist/server/index.js.map +1 -1
- package/dist/ui/dashboard.html +317 -173
- 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,CAgvG1G"}
|
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,170 @@ 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 buildLoadConfig(cfg) {
|
|
2227
|
+
const envVars = sanitizeLoadEnvVars(cfg.envVars);
|
|
2228
|
+
const baseUrl = typeof cfg.baseUrl === 'string' && cfg.baseUrl.trim() ? cfg.baseUrl.trim() : 'http://localhost:5000';
|
|
2229
|
+
envVars.BASE_URL = baseUrl;
|
|
2230
|
+
const config = {
|
|
2231
|
+
vus: normalizeLoadVus(cfg.vus),
|
|
2232
|
+
duration: normalizeLoadDuration(cfg.duration),
|
|
2233
|
+
baseUrl,
|
|
2234
|
+
scriptName: sanitizeLoadScriptName(cfg.scriptName),
|
|
2235
|
+
envVars: redactLoadEnvVars(envVars),
|
|
2236
|
+
};
|
|
2237
|
+
return { config, envVars };
|
|
2238
|
+
}
|
|
2239
|
+
function flattenK6Checks(group) {
|
|
2240
|
+
let passes = 0;
|
|
2241
|
+
let fails = 0;
|
|
2242
|
+
for (const check of group?.checks || []) {
|
|
2243
|
+
passes += Number(check.passes || 0);
|
|
2244
|
+
fails += Number(check.fails || 0);
|
|
2245
|
+
}
|
|
2246
|
+
for (const child of group?.groups || []) {
|
|
2247
|
+
const nested = flattenK6Checks(child);
|
|
2248
|
+
passes += nested.passes;
|
|
2249
|
+
fails += nested.fails;
|
|
2250
|
+
}
|
|
2251
|
+
return { passes, fails };
|
|
2252
|
+
}
|
|
2253
|
+
function normalizeK6Summary(raw, exitCode) {
|
|
2254
|
+
const metrics = raw?.metrics || {};
|
|
2255
|
+
const duration = metrics.http_req_duration?.values || {};
|
|
2256
|
+
const reqs = metrics.http_reqs?.values || {};
|
|
2257
|
+
const failed = metrics.http_req_failed?.values || {};
|
|
2258
|
+
const checks = flattenK6Checks(raw?.root_group);
|
|
2259
|
+
let thresholdsPassed = 0;
|
|
2260
|
+
let thresholdsFailed = 0;
|
|
2261
|
+
for (const metric of Object.values(metrics)) {
|
|
2262
|
+
for (const threshold of Object.values(metric?.thresholds || {})) {
|
|
2263
|
+
if (threshold?.ok === false)
|
|
2264
|
+
thresholdsFailed++;
|
|
2265
|
+
else if (threshold?.ok === true)
|
|
2266
|
+
thresholdsPassed++;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
return {
|
|
2270
|
+
totalRequests: typeof reqs.count === 'number' ? reqs.count : undefined,
|
|
2271
|
+
rps: typeof reqs.rate === 'number' ? reqs.rate : undefined,
|
|
2272
|
+
avgDuration: typeof duration.avg === 'number' ? duration.avg : undefined,
|
|
2273
|
+
p90Duration: typeof duration['p(90)'] === 'number' ? duration['p(90)'] : undefined,
|
|
2274
|
+
p95Duration: typeof duration['p(95)'] === 'number' ? duration['p(95)'] : undefined,
|
|
2275
|
+
p99Duration: typeof duration['p(99)'] === 'number' ? duration['p(99)'] : undefined,
|
|
2276
|
+
errorPct: typeof failed.rate === 'number' ? failed.rate * 100 : undefined,
|
|
2277
|
+
testRunDurationMs: typeof raw?.state?.testRunDurationMs === 'number' ? raw.state.testRunDurationMs : undefined,
|
|
2278
|
+
checksPassed: checks.passes,
|
|
2279
|
+
checksFailed: checks.fails,
|
|
2280
|
+
thresholdsPassed,
|
|
2281
|
+
thresholdsFailed,
|
|
2282
|
+
exitCode,
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
function readK6Summary(summaryPath, exitCode) {
|
|
2286
|
+
try {
|
|
2287
|
+
if (!fs.existsSync(summaryPath))
|
|
2288
|
+
return { exitCode };
|
|
2289
|
+
return normalizeK6Summary(JSON.parse(fs.readFileSync(summaryPath, 'utf-8')), exitCode);
|
|
2290
|
+
}
|
|
2291
|
+
catch {
|
|
2292
|
+
return { exitCode };
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
function readLoadRunIndex() {
|
|
2296
|
+
try {
|
|
2297
|
+
const p = path.join(loadRunsDir, 'index.json');
|
|
2298
|
+
const parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
2299
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
2300
|
+
}
|
|
2301
|
+
catch {
|
|
2302
|
+
return [];
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
function writeLoadRunIndex(items) {
|
|
2306
|
+
fs.mkdirSync(loadRunsDir, { recursive: true });
|
|
2307
|
+
fs.writeFileSync(path.join(loadRunsDir, 'index.json'), JSON.stringify(items, null, 2), 'utf-8');
|
|
2308
|
+
}
|
|
2309
|
+
function saveLoadRun() {
|
|
2310
|
+
if (!loadState.runId)
|
|
2311
|
+
return;
|
|
2312
|
+
fs.mkdirSync(loadRunsDir, { recursive: true });
|
|
2313
|
+
const record = {
|
|
2314
|
+
...loadState,
|
|
2315
|
+
scriptName: loadState.config?.scriptName,
|
|
2316
|
+
createdAt: new Date(loadState.startTime || Date.now()).toISOString(),
|
|
2317
|
+
};
|
|
2318
|
+
fs.writeFileSync(path.join(loadRunsDir, `${loadState.runId}.json`), JSON.stringify(record, null, 2), 'utf-8');
|
|
2319
|
+
const index = readLoadRunIndex().filter(i => i.runId !== loadState.runId);
|
|
2320
|
+
index.unshift({
|
|
2321
|
+
runId: loadState.runId,
|
|
2322
|
+
scriptName: loadState.config?.scriptName || 'Без названия',
|
|
2323
|
+
createdAt: record.createdAt,
|
|
2324
|
+
status: loadState.status,
|
|
2325
|
+
startTime: loadState.startTime,
|
|
2326
|
+
endTime: loadState.endTime,
|
|
2327
|
+
config: loadState.config,
|
|
2328
|
+
summary: loadState.summary,
|
|
2329
|
+
});
|
|
2330
|
+
const compact = index.slice(0, MAX_LOAD_RUN_HISTORY);
|
|
2331
|
+
writeLoadRunIndex(compact);
|
|
2332
|
+
for (const old of index.slice(MAX_LOAD_RUN_HISTORY)) {
|
|
2333
|
+
try {
|
|
2334
|
+
fs.unlinkSync(path.join(loadRunsDir, `${old.runId}.json`));
|
|
2335
|
+
}
|
|
2336
|
+
catch { }
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
function loadLastRunIntoState() {
|
|
2340
|
+
const latest = readLoadRunIndex()[0];
|
|
2341
|
+
if (!latest)
|
|
2342
|
+
return;
|
|
2343
|
+
try {
|
|
2344
|
+
const record = JSON.parse(fs.readFileSync(path.join(loadRunsDir, `${latest.runId}.json`), 'utf-8'));
|
|
2345
|
+
if (record && typeof record === 'object')
|
|
2346
|
+
loadState = record;
|
|
2347
|
+
}
|
|
2348
|
+
catch { }
|
|
2349
|
+
}
|
|
2350
|
+
loadLastRunIntoState();
|
|
2217
2351
|
// ── SSE clients ────────────────────────────────────────────────────────────
|
|
2218
2352
|
const sseClients = new Set();
|
|
2219
2353
|
function broadcast(event, payload = {}) {
|
|
@@ -2672,6 +2806,19 @@ function startServer({ data: initialData, port, projectRoot }) {
|
|
|
2672
2806
|
return;
|
|
2673
2807
|
}
|
|
2674
2808
|
}
|
|
2809
|
+
else if (task === 'obs-feature-review') {
|
|
2810
|
+
const requestedFeatureKey = item.meta?.featureKey || item.featureKey;
|
|
2811
|
+
if (!requestedFeatureKey) {
|
|
2812
|
+
failBeforeStart('Фича не указана');
|
|
2813
|
+
return;
|
|
2814
|
+
}
|
|
2815
|
+
const builtPrompt = buildObsFeatureReviewPrompt(requestedFeatureKey, currentData);
|
|
2816
|
+
if (builtPrompt === null) {
|
|
2817
|
+
failBeforeStart(`Не удалось собрать prompt по наблюдаемости фичи ${requestedFeatureKey}`);
|
|
2818
|
+
return;
|
|
2819
|
+
}
|
|
2820
|
+
prompt = builtPrompt;
|
|
2821
|
+
}
|
|
2675
2822
|
else if (task === 'actualize-docs') {
|
|
2676
2823
|
if (!featureKey || !currentData.features) {
|
|
2677
2824
|
failBeforeStart('Фича не найдена');
|
|
@@ -3371,6 +3518,16 @@ function startServer({ data: initialData, port, projectRoot }) {
|
|
|
3371
3518
|
const label = meta?.fieldName ? `поле ${meta.fieldName}` : meta?.recommendationType || 'логи';
|
|
3372
3519
|
title = `${agentLabel} — ${label} (${count} модулей)`;
|
|
3373
3520
|
}
|
|
3521
|
+
else if (task === 'obs-feature-review') {
|
|
3522
|
+
const requestedFeatureKey = meta?.featureKey || featureKey;
|
|
3523
|
+
const isUnmapped = requestedFeatureKey === '__unmapped__';
|
|
3524
|
+
const feat = !isUnmapped ? currentData.features?.find(f => f.key === requestedFeatureKey) : null;
|
|
3525
|
+
if (!requestedFeatureKey || (!isUnmapped && !feat)) {
|
|
3526
|
+
broadcast('agent-error', { message: `Фича не найдена: ${requestedFeatureKey || 'не указана'}` });
|
|
3527
|
+
return null;
|
|
3528
|
+
}
|
|
3529
|
+
title = `${agentLabel} — наблюдаемость фичи "${isUnmapped ? 'Unmapped' : feat.label}"`;
|
|
3530
|
+
}
|
|
3374
3531
|
else if (task === 'actualize-docs') {
|
|
3375
3532
|
const feat = currentData.features?.find(f => f.key === featureKey);
|
|
3376
3533
|
title = feat ? `${agentLabel} — документация "${feat.label}"` : `${agentLabel} — документация`;
|
|
@@ -4888,7 +5045,7 @@ a{color:var(--blue)}
|
|
|
4888
5045
|
req.on('data', (d) => { body += d; });
|
|
4889
5046
|
req.on('end', () => {
|
|
4890
5047
|
try {
|
|
4891
|
-
const { name, script } = JSON.parse(body);
|
|
5048
|
+
const { name, script, vus, duration, baseUrl } = JSON.parse(body);
|
|
4892
5049
|
if (!name || !script) {
|
|
4893
5050
|
res.writeHead(400, jsonH);
|
|
4894
5051
|
res.end(JSON.stringify({ error: 'name and script required' }));
|
|
@@ -4898,7 +5055,15 @@ a{color:var(--blue)}
|
|
|
4898
5055
|
const safeName = name.replace(/[^a-zA-Zа-яА-ЯёЁ0-9_\- ]/g, '_').slice(0, 80);
|
|
4899
5056
|
const date = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
|
4900
5057
|
const fileName = `${Date.now()}-${safeName.replace(/\s+/g, '_')}.json`;
|
|
4901
|
-
const entry = {
|
|
5058
|
+
const entry = {
|
|
5059
|
+
name: safeName,
|
|
5060
|
+
date,
|
|
5061
|
+
script,
|
|
5062
|
+
fileName,
|
|
5063
|
+
vus: normalizeLoadVus(vus),
|
|
5064
|
+
duration: normalizeLoadDuration(duration),
|
|
5065
|
+
baseUrl: typeof baseUrl === 'string' && baseUrl.trim() ? baseUrl.trim() : 'http://localhost:5000',
|
|
5066
|
+
};
|
|
4902
5067
|
// overwrite if same name exists
|
|
4903
5068
|
const existing = fs.readdirSync(scriptsDir).find(f => {
|
|
4904
5069
|
try {
|
|
@@ -4988,6 +5153,31 @@ a{color:var(--blue)}
|
|
|
4988
5153
|
res.end(JSON.stringify(loadState));
|
|
4989
5154
|
return;
|
|
4990
5155
|
}
|
|
5156
|
+
if (url === '/api/load/runs' && req.method === 'GET') {
|
|
5157
|
+
res.writeHead(200, jsonH);
|
|
5158
|
+
res.end(JSON.stringify(readLoadRunIndex()));
|
|
5159
|
+
return;
|
|
5160
|
+
}
|
|
5161
|
+
const loadRunMatch = url.match(/^\/api\/load\/runs\/([^/]+)$/);
|
|
5162
|
+
if (loadRunMatch && req.method === 'GET') {
|
|
5163
|
+
try {
|
|
5164
|
+
const runId = decodeURIComponent(loadRunMatch[1]);
|
|
5165
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(runId)) {
|
|
5166
|
+
res.writeHead(400, jsonH);
|
|
5167
|
+
res.end(JSON.stringify({ error: 'Bad run id' }));
|
|
5168
|
+
return;
|
|
5169
|
+
}
|
|
5170
|
+
const runPath = path.join(loadRunsDir, `${runId}.json`);
|
|
5171
|
+
const record = JSON.parse(fs.readFileSync(runPath, 'utf-8'));
|
|
5172
|
+
res.writeHead(200, jsonH);
|
|
5173
|
+
res.end(JSON.stringify(record));
|
|
5174
|
+
}
|
|
5175
|
+
catch {
|
|
5176
|
+
res.writeHead(404, jsonH);
|
|
5177
|
+
res.end(JSON.stringify({ error: 'Run not found' }));
|
|
5178
|
+
}
|
|
5179
|
+
return;
|
|
5180
|
+
}
|
|
4991
5181
|
if (url === '/api/load/stop' && req.method === 'POST') {
|
|
4992
5182
|
if (loadProc) {
|
|
4993
5183
|
try {
|
|
@@ -5001,7 +5191,7 @@ a{color:var(--blue)}
|
|
|
5001
5191
|
loadState.status = 'stopped';
|
|
5002
5192
|
loadState.endTime = Date.now();
|
|
5003
5193
|
}
|
|
5004
|
-
broadcast('load-done', { status: loadState.status, summary: loadState.summary });
|
|
5194
|
+
broadcast('load-done', { runId: loadState.runId, status: loadState.status, summary: loadState.summary });
|
|
5005
5195
|
res.writeHead(200, jsonH);
|
|
5006
5196
|
res.end(JSON.stringify({ ok: true }));
|
|
5007
5197
|
return;
|
|
@@ -5030,8 +5220,11 @@ a{color:var(--blue)}
|
|
|
5030
5220
|
res.end(JSON.stringify({ error: 'No script provided' }));
|
|
5031
5221
|
return;
|
|
5032
5222
|
}
|
|
5223
|
+
const { config: loadConfig, envVars } = buildLoadConfig(cfg);
|
|
5224
|
+
const runId = `load-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5033
5225
|
const scriptPath = path.join(os.tmpdir(), `viberadar-k6-${Date.now()}.js`);
|
|
5034
5226
|
const jsonOutPath = path.join(os.tmpdir(), `viberadar-k6-out-${Date.now()}.ndjson`);
|
|
5227
|
+
const summaryPath = path.join(os.tmpdir(), `viberadar-k6-summary-${Date.now()}.json`);
|
|
5035
5228
|
try {
|
|
5036
5229
|
fs.writeFileSync(scriptPath, script, 'utf-8');
|
|
5037
5230
|
}
|
|
@@ -5042,22 +5235,27 @@ a{color:var(--blue)}
|
|
|
5042
5235
|
}
|
|
5043
5236
|
loadRunning = true;
|
|
5044
5237
|
loadState = {
|
|
5045
|
-
status: 'running', startTime: Date.now(), buckets: [], totalRequests: 0,
|
|
5046
|
-
totalErrors: 0, logs: [], script, config:
|
|
5238
|
+
runId, status: 'running', startTime: Date.now(), buckets: [], totalRequests: 0,
|
|
5239
|
+
totalErrors: 0, logs: [], script, config: loadConfig, summary: null,
|
|
5047
5240
|
};
|
|
5048
|
-
broadcast('load-started', { config:
|
|
5241
|
+
broadcast('load-started', { runId, config: loadConfig });
|
|
5049
5242
|
res.writeHead(200, jsonH);
|
|
5050
|
-
res.end(JSON.stringify({ ok: true }));
|
|
5051
|
-
// Build --env flags from cfg.envVars (e.g. { TOKEN: 'abc', BASE_URL: '...' })
|
|
5052
|
-
const envVars = (typeof cfg.envVars === 'object' && cfg.envVars !== null)
|
|
5053
|
-
? cfg.envVars
|
|
5054
|
-
: {};
|
|
5243
|
+
res.end(JSON.stringify({ ok: true, runId }));
|
|
5055
5244
|
const envFlags = [];
|
|
5056
5245
|
for (const [k, v] of Object.entries(envVars)) {
|
|
5057
5246
|
if (k && v !== undefined && v !== '')
|
|
5058
5247
|
envFlags.push('--env', `${k}=${v}`);
|
|
5059
5248
|
}
|
|
5060
|
-
|
|
5249
|
+
const args = [
|
|
5250
|
+
'run',
|
|
5251
|
+
'--vus', String(loadConfig.vus),
|
|
5252
|
+
'--duration', loadConfig.duration,
|
|
5253
|
+
...envFlags,
|
|
5254
|
+
'--summary-export', summaryPath,
|
|
5255
|
+
'--out', `json=${jsonOutPath}`,
|
|
5256
|
+
scriptPath,
|
|
5257
|
+
];
|
|
5258
|
+
loadProc = (0, child_process_1.spawn)('k6', args, {
|
|
5061
5259
|
cwd: projectRoot, env: { ...process.env }, shell: WIN, stdio: 'pipe',
|
|
5062
5260
|
});
|
|
5063
5261
|
const addLog = (line) => {
|
|
@@ -5133,7 +5331,7 @@ a{color:var(--blue)}
|
|
|
5133
5331
|
}
|
|
5134
5332
|
if (changed) {
|
|
5135
5333
|
const slice = loadState.buckets.slice(-30);
|
|
5136
|
-
broadcast('load-progress', { buckets: slice, total: loadState.totalRequests, errors: loadState.totalErrors });
|
|
5334
|
+
broadcast('load-progress', { runId: loadState.runId, buckets: slice, total: loadState.totalRequests, errors: loadState.totalErrors });
|
|
5137
5335
|
}
|
|
5138
5336
|
}
|
|
5139
5337
|
catch { }
|
|
@@ -5143,11 +5341,17 @@ a{color:var(--blue)}
|
|
|
5143
5341
|
loadRunning = false;
|
|
5144
5342
|
loadProc = null;
|
|
5145
5343
|
if (loadState.status === 'running') {
|
|
5146
|
-
loadState.status =
|
|
5344
|
+
loadState.status = code === 0 ? 'done' : 'error';
|
|
5147
5345
|
}
|
|
5148
5346
|
loadState.endTime = Date.now();
|
|
5149
|
-
loadState.summary =
|
|
5150
|
-
|
|
5347
|
+
loadState.summary = readK6Summary(summaryPath, code);
|
|
5348
|
+
if (loadState.summary?.totalRequests != null)
|
|
5349
|
+
loadState.totalRequests = loadState.summary.totalRequests;
|
|
5350
|
+
if (loadState.summary?.errorPct != null && loadState.summary.totalRequests != null) {
|
|
5351
|
+
loadState.totalErrors = Math.round(loadState.summary.totalRequests * (loadState.summary.errorPct / 100));
|
|
5352
|
+
}
|
|
5353
|
+
saveLoadRun();
|
|
5354
|
+
broadcast('load-done', { runId: loadState.runId, status: loadState.status, summary: loadState.summary });
|
|
5151
5355
|
try {
|
|
5152
5356
|
fs.unlinkSync(scriptPath);
|
|
5153
5357
|
}
|
|
@@ -5156,6 +5360,10 @@ a{color:var(--blue)}
|
|
|
5156
5360
|
fs.unlinkSync(jsonOutPath);
|
|
5157
5361
|
}
|
|
5158
5362
|
catch { }
|
|
5363
|
+
try {
|
|
5364
|
+
fs.unlinkSync(summaryPath);
|
|
5365
|
+
}
|
|
5366
|
+
catch { }
|
|
5159
5367
|
});
|
|
5160
5368
|
loadProc.on('error', (err) => {
|
|
5161
5369
|
clearInterval(watchInterval);
|
|
@@ -5164,11 +5372,17 @@ a{color:var(--blue)}
|
|
|
5164
5372
|
loadState.status = 'error';
|
|
5165
5373
|
loadState.endTime = Date.now();
|
|
5166
5374
|
addLog(`❌ k6 не запустился: ${err.message}`);
|
|
5167
|
-
|
|
5375
|
+
loadState.summary = { exitCode: null };
|
|
5376
|
+
saveLoadRun();
|
|
5377
|
+
broadcast('load-done', { runId: loadState.runId, status: 'error', summary: loadState.summary });
|
|
5168
5378
|
try {
|
|
5169
5379
|
fs.unlinkSync(scriptPath);
|
|
5170
5380
|
}
|
|
5171
5381
|
catch { }
|
|
5382
|
+
try {
|
|
5383
|
+
fs.unlinkSync(summaryPath);
|
|
5384
|
+
}
|
|
5385
|
+
catch { }
|
|
5172
5386
|
});
|
|
5173
5387
|
});
|
|
5174
5388
|
return;
|