viberadar 0.3.156 → 0.3.158
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 +233 -1
- package/dist/server/index.js.map +1 -1
- package/dist/ui/dashboard.html +493 -2
- 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;AAGlK,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;AAuhED,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;AAGlK,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;AAuhED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAo/E1G"}
|
package/dist/server/index.js
CHANGED
|
@@ -1865,6 +1865,42 @@ function startServer({ data: initialData, port, projectRoot }) {
|
|
|
1865
1865
|
let activeRunId = null;
|
|
1866
1866
|
// Keyed by absolute file path → per-file failure details from last test run
|
|
1867
1867
|
const lastTestResults = new Map();
|
|
1868
|
+
let loadRunning = false;
|
|
1869
|
+
let loadProc = null;
|
|
1870
|
+
let loadState = {
|
|
1871
|
+
status: 'idle', startTime: 0, buckets: [], totalRequests: 0,
|
|
1872
|
+
totalErrors: 0, logs: [], script: '', config: null, summary: null,
|
|
1873
|
+
};
|
|
1874
|
+
function parseK6Dur(s) {
|
|
1875
|
+
let m;
|
|
1876
|
+
if ((m = s.match(/^([\d.]+)µs$/)))
|
|
1877
|
+
return parseFloat(m[1]) / 1000;
|
|
1878
|
+
if ((m = s.match(/^([\d.]+)ms$/)))
|
|
1879
|
+
return parseFloat(m[1]);
|
|
1880
|
+
if ((m = s.match(/^([\d.]+)s$/)))
|
|
1881
|
+
return parseFloat(m[1]) * 1000;
|
|
1882
|
+
if ((m = s.match(/^(\d+)m([\d.]+)s$/)))
|
|
1883
|
+
return parseInt(m[1]) * 60000 + parseFloat(m[2]) * 1000;
|
|
1884
|
+
return 0;
|
|
1885
|
+
}
|
|
1886
|
+
function parseK6Summary(text) {
|
|
1887
|
+
const s = {};
|
|
1888
|
+
const dur = text.match(/http_req_duration[^:]*:\s+avg=([\w.µ]+)[^\n]*p\(90\)=([\w.µ]+)[^\n]*p\(95\)=([\w.µ]+)/);
|
|
1889
|
+
if (dur) {
|
|
1890
|
+
s.avgDuration = parseK6Dur(dur[1]);
|
|
1891
|
+
s.p90Duration = parseK6Dur(dur[2]);
|
|
1892
|
+
s.p95Duration = parseK6Dur(dur[3]);
|
|
1893
|
+
}
|
|
1894
|
+
const reqs = text.match(/\bhttp_reqs[^:]*:\s+(\d+)\s+([\d.]+)\/s/);
|
|
1895
|
+
if (reqs) {
|
|
1896
|
+
s.totalRequests = parseInt(reqs[1]);
|
|
1897
|
+
s.rps = parseFloat(reqs[2]);
|
|
1898
|
+
}
|
|
1899
|
+
const fail = text.match(/http_req_failed[^:]*:\s+([\d.]+)%/);
|
|
1900
|
+
if (fail)
|
|
1901
|
+
s.errorPct = parseFloat(fail[1]);
|
|
1902
|
+
return s;
|
|
1903
|
+
}
|
|
1868
1904
|
// ── SSE clients ────────────────────────────────────────────────────────────
|
|
1869
1905
|
const sseClients = new Set();
|
|
1870
1906
|
function broadcast(event, payload = {}) {
|
|
@@ -3010,7 +3046,7 @@ function startServer({ data: initialData, port, projectRoot }) {
|
|
|
3010
3046
|
title = `${agentLabel} — исправить логи в "${modName}"`;
|
|
3011
3047
|
}
|
|
3012
3048
|
else if (task === 'obs-fix-selected') {
|
|
3013
|
-
const count = Array.isArray(meta?.catalogIndices) ? meta.catalogIndices.length : 0;
|
|
3049
|
+
const count = Array.isArray(meta?.catalogPaths) ? meta.catalogPaths.length : Array.isArray(meta?.catalogIndices) ? meta.catalogIndices.length : 0;
|
|
3014
3050
|
const label = meta?.fieldName ? `поле ${meta.fieldName}` : meta?.recommendationType || 'логи';
|
|
3015
3051
|
title = `${agentLabel} — ${label} (${count} модулей)`;
|
|
3016
3052
|
}
|
|
@@ -4431,6 +4467,202 @@ a{color:var(--blue)}
|
|
|
4431
4467
|
});
|
|
4432
4468
|
return;
|
|
4433
4469
|
}
|
|
4470
|
+
// ── Load testing (k6) ─────────────────────────────────────────────────────
|
|
4471
|
+
if (url === '/api/load/check' && req.method === 'GET') {
|
|
4472
|
+
const k6 = (0, child_process_1.spawn)(WIN ? 'k6.cmd' : 'k6', ['version'], { shell: false });
|
|
4473
|
+
let ver = '';
|
|
4474
|
+
k6.stdout?.on('data', (d) => { ver += d.toString(); });
|
|
4475
|
+
k6.on('close', (code) => {
|
|
4476
|
+
res.writeHead(200, jsonH);
|
|
4477
|
+
res.end(JSON.stringify({ available: code === 0, version: ver.trim().split('\n')[0] || '' }));
|
|
4478
|
+
});
|
|
4479
|
+
k6.on('error', () => {
|
|
4480
|
+
res.writeHead(200, jsonH);
|
|
4481
|
+
res.end(JSON.stringify({ available: false, version: '' }));
|
|
4482
|
+
});
|
|
4483
|
+
return;
|
|
4484
|
+
}
|
|
4485
|
+
if (url === '/api/load/results' && req.method === 'GET') {
|
|
4486
|
+
res.writeHead(200, jsonH);
|
|
4487
|
+
res.end(JSON.stringify(loadState));
|
|
4488
|
+
return;
|
|
4489
|
+
}
|
|
4490
|
+
if (url === '/api/load/stop' && req.method === 'POST') {
|
|
4491
|
+
if (loadProc) {
|
|
4492
|
+
try {
|
|
4493
|
+
loadProc.kill('SIGTERM');
|
|
4494
|
+
}
|
|
4495
|
+
catch { }
|
|
4496
|
+
loadProc = null;
|
|
4497
|
+
}
|
|
4498
|
+
if (loadRunning) {
|
|
4499
|
+
loadRunning = false;
|
|
4500
|
+
loadState.status = 'stopped';
|
|
4501
|
+
loadState.endTime = Date.now();
|
|
4502
|
+
}
|
|
4503
|
+
broadcast('load-done', { status: loadState.status, summary: loadState.summary });
|
|
4504
|
+
res.writeHead(200, jsonH);
|
|
4505
|
+
res.end(JSON.stringify({ ok: true }));
|
|
4506
|
+
return;
|
|
4507
|
+
}
|
|
4508
|
+
if (url === '/api/load/run' && req.method === 'POST') {
|
|
4509
|
+
if (loadRunning) {
|
|
4510
|
+
res.writeHead(409, jsonH);
|
|
4511
|
+
res.end(JSON.stringify({ error: 'Already running' }));
|
|
4512
|
+
return;
|
|
4513
|
+
}
|
|
4514
|
+
let body = '';
|
|
4515
|
+
req.on('data', (d) => { body += d; });
|
|
4516
|
+
req.on('end', () => {
|
|
4517
|
+
let cfg;
|
|
4518
|
+
try {
|
|
4519
|
+
cfg = JSON.parse(body);
|
|
4520
|
+
}
|
|
4521
|
+
catch (e) {
|
|
4522
|
+
res.writeHead(400, jsonH);
|
|
4523
|
+
res.end(JSON.stringify({ error: 'Bad JSON' }));
|
|
4524
|
+
return;
|
|
4525
|
+
}
|
|
4526
|
+
const script = cfg.script || '';
|
|
4527
|
+
if (!script.trim()) {
|
|
4528
|
+
res.writeHead(400, jsonH);
|
|
4529
|
+
res.end(JSON.stringify({ error: 'No script provided' }));
|
|
4530
|
+
return;
|
|
4531
|
+
}
|
|
4532
|
+
const scriptPath = path.join(os.tmpdir(), `viberadar-k6-${Date.now()}.js`);
|
|
4533
|
+
const jsonOutPath = path.join(os.tmpdir(), `viberadar-k6-out-${Date.now()}.ndjson`);
|
|
4534
|
+
try {
|
|
4535
|
+
fs.writeFileSync(scriptPath, script, 'utf-8');
|
|
4536
|
+
}
|
|
4537
|
+
catch (e) {
|
|
4538
|
+
res.writeHead(500, jsonH);
|
|
4539
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
4540
|
+
return;
|
|
4541
|
+
}
|
|
4542
|
+
loadRunning = true;
|
|
4543
|
+
loadState = {
|
|
4544
|
+
status: 'running', startTime: Date.now(), buckets: [], totalRequests: 0,
|
|
4545
|
+
totalErrors: 0, logs: [], script, config: cfg, summary: null,
|
|
4546
|
+
};
|
|
4547
|
+
broadcast('load-started', { config: cfg });
|
|
4548
|
+
res.writeHead(200, jsonH);
|
|
4549
|
+
res.end(JSON.stringify({ ok: true }));
|
|
4550
|
+
loadProc = (0, child_process_1.spawn)(WIN ? 'k6.cmd' : 'k6', ['run', '--out', `json=${jsonOutPath}`, scriptPath], {
|
|
4551
|
+
cwd: projectRoot, env: { ...process.env },
|
|
4552
|
+
});
|
|
4553
|
+
const addLog = (line) => {
|
|
4554
|
+
loadState.logs.push(line);
|
|
4555
|
+
if (loadState.logs.length > 500)
|
|
4556
|
+
loadState.logs.shift();
|
|
4557
|
+
broadcast('load-log', { line });
|
|
4558
|
+
};
|
|
4559
|
+
loadProc.stdout?.on('data', (chunk) => {
|
|
4560
|
+
for (const ln of chunk.toString().split(/\r?\n/)) {
|
|
4561
|
+
if (ln.trim())
|
|
4562
|
+
addLog(ln);
|
|
4563
|
+
}
|
|
4564
|
+
});
|
|
4565
|
+
loadProc.stderr?.on('data', (chunk) => {
|
|
4566
|
+
for (const ln of chunk.toString().split(/\r?\n/)) {
|
|
4567
|
+
if (ln.trim())
|
|
4568
|
+
addLog(ln);
|
|
4569
|
+
}
|
|
4570
|
+
});
|
|
4571
|
+
let jsonPos = 0;
|
|
4572
|
+
const watchInterval = setInterval(() => {
|
|
4573
|
+
if (!loadRunning) {
|
|
4574
|
+
clearInterval(watchInterval);
|
|
4575
|
+
return;
|
|
4576
|
+
}
|
|
4577
|
+
try {
|
|
4578
|
+
if (!fs.existsSync(jsonOutPath))
|
|
4579
|
+
return;
|
|
4580
|
+
const stat = fs.statSync(jsonOutPath);
|
|
4581
|
+
if (stat.size <= jsonPos)
|
|
4582
|
+
return;
|
|
4583
|
+
const buf = Buffer.alloc(stat.size - jsonPos);
|
|
4584
|
+
const fd = fs.openSync(jsonOutPath, 'r');
|
|
4585
|
+
fs.readSync(fd, buf, 0, buf.length, jsonPos);
|
|
4586
|
+
fs.closeSync(fd);
|
|
4587
|
+
jsonPos = stat.size;
|
|
4588
|
+
let changed = false;
|
|
4589
|
+
for (const ln of buf.toString().split(/\r?\n/)) {
|
|
4590
|
+
if (!ln.trim())
|
|
4591
|
+
continue;
|
|
4592
|
+
try {
|
|
4593
|
+
const obj = JSON.parse(ln);
|
|
4594
|
+
if (obj.type !== 'Point')
|
|
4595
|
+
continue;
|
|
4596
|
+
const bucketTs = Math.floor((new Date(obj.data.time).getTime() - loadState.startTime) / 2000) * 2000;
|
|
4597
|
+
let bkt = loadState.buckets.find(b => b.ts === bucketTs);
|
|
4598
|
+
if (!bkt) {
|
|
4599
|
+
bkt = { ts: bucketTs, count: 0, errors: 0, durSum: 0, vus: 0 };
|
|
4600
|
+
loadState.buckets.push(bkt);
|
|
4601
|
+
loadState.buckets.sort((a, b) => a.ts - b.ts);
|
|
4602
|
+
}
|
|
4603
|
+
if (obj.metric === 'http_reqs') {
|
|
4604
|
+
bkt.count += obj.data.value;
|
|
4605
|
+
loadState.totalRequests++;
|
|
4606
|
+
changed = true;
|
|
4607
|
+
}
|
|
4608
|
+
if (obj.metric === 'http_req_failed' && obj.data.value > 0) {
|
|
4609
|
+
bkt.errors += obj.data.value;
|
|
4610
|
+
loadState.totalErrors++;
|
|
4611
|
+
changed = true;
|
|
4612
|
+
}
|
|
4613
|
+
if (obj.metric === 'http_req_duration') {
|
|
4614
|
+
bkt.durSum += obj.data.value;
|
|
4615
|
+
changed = true;
|
|
4616
|
+
}
|
|
4617
|
+
if (obj.metric === 'vus') {
|
|
4618
|
+
bkt.vus = obj.data.value;
|
|
4619
|
+
changed = true;
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
catch { }
|
|
4623
|
+
}
|
|
4624
|
+
if (changed) {
|
|
4625
|
+
const slice = loadState.buckets.slice(-30);
|
|
4626
|
+
broadcast('load-progress', { buckets: slice, total: loadState.totalRequests, errors: loadState.totalErrors });
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
catch { }
|
|
4630
|
+
}, 2000);
|
|
4631
|
+
loadProc.on('close', (code) => {
|
|
4632
|
+
clearInterval(watchInterval);
|
|
4633
|
+
loadRunning = false;
|
|
4634
|
+
loadProc = null;
|
|
4635
|
+
if (loadState.status === 'running') {
|
|
4636
|
+
loadState.status = (code === 0 || code === null) ? 'done' : 'done';
|
|
4637
|
+
}
|
|
4638
|
+
loadState.endTime = Date.now();
|
|
4639
|
+
loadState.summary = parseK6Summary(loadState.logs.join('\n'));
|
|
4640
|
+
broadcast('load-done', { status: loadState.status, summary: loadState.summary });
|
|
4641
|
+
try {
|
|
4642
|
+
fs.unlinkSync(scriptPath);
|
|
4643
|
+
}
|
|
4644
|
+
catch { }
|
|
4645
|
+
try {
|
|
4646
|
+
fs.unlinkSync(jsonOutPath);
|
|
4647
|
+
}
|
|
4648
|
+
catch { }
|
|
4649
|
+
});
|
|
4650
|
+
loadProc.on('error', (err) => {
|
|
4651
|
+
clearInterval(watchInterval);
|
|
4652
|
+
loadRunning = false;
|
|
4653
|
+
loadProc = null;
|
|
4654
|
+
loadState.status = 'error';
|
|
4655
|
+
loadState.endTime = Date.now();
|
|
4656
|
+
addLog(`❌ k6 не запустился: ${err.message}`);
|
|
4657
|
+
broadcast('load-done', { status: 'error', summary: null });
|
|
4658
|
+
try {
|
|
4659
|
+
fs.unlinkSync(scriptPath);
|
|
4660
|
+
}
|
|
4661
|
+
catch { }
|
|
4662
|
+
});
|
|
4663
|
+
});
|
|
4664
|
+
return;
|
|
4665
|
+
}
|
|
4434
4666
|
res.writeHead(404);
|
|
4435
4667
|
res.end('Not found');
|
|
4436
4668
|
});
|