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.
@@ -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,CA+zE1G"}
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"}
@@ -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
  });