diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 02a8b997..d25f652f 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,6 +1,6 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-07: 12.3.67 завершён: компактные ReUI KPI captions, distinct площадки/unknown/пустой и исключённый pool,16+2browser/type/lint/build. Следующий12.3.68: child CPU/full-route product≤100000, hard timeout/memory cap, сохранить baseline перед map lookup оптимизацией. Внешняя12.5 отдельно. +Актуальное продолжение 2026-10-07: 12.3.68 baseline full routes сохранён: pure product1000/100000 под timeout10s/memory256MB, все route IDs/unknown сохранены. Следующий12.3.69: заменить повторный wanJhLegs.find на map с first-match семантикой и доказать route/source/tie эквивалентность. Затем аудит направлений TX/RX и привязки wildcard-проб; внешняя12.5 остаётся. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-04. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 1dcb0700..b7d1c8c4 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-07: 12.3.67 завершён: компактные ReUI KPI captions, distinct площадки/unknown/пустой и исключённый pool,16+2browser/type/lint/build. Следующий12.3.68: child CPU/full-route product≤100000, hard timeout/memory cap, сохранить baseline перед map lookup оптимизацией. Внешняя12.5 отдельно. +Актуальное продолжение 2026-10-07: 12.3.68 baseline full routes сохранён: pure product1000/100000 под timeout10s/memory256MB, все route IDs/unknown сохранены. Следующий12.3.69: заменить повторный wanJhLegs.find на map с first-match семантикой и доказать route/source/tie эквивалентность. Затем аудит направлений TX/RX и привязки wildcard-проб; внешняя12.5 остаётся. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 808a714b..a93e2bd8 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -767,3 +767,10 @@ Pure child CPU1000: equal1217.5→15.6ms,varied7967.5→14.5ms,freshness10с tim 16 main browser +2 повторных новых профиля после copy-уточнения прошли:1000JH и1000Exit отдельно (без опасного cross product), repetitions/two sites/1000sites/long80chars/unknown/empty/offline/disabled/503 cache/recovery.320px без overflow, caption≤80px, scoped WCAG axe passed. Non-incremental TypeScript до dev, full page/helper/test lint и окончательный production build прошли. ReUI API/validate_usage проверены; primitive/theme не изменены. Historical9 optimizer PNG восстановлены, новые docs/ux-ui/screens/optimizer-sites-{jump-host,exit-node}-320.png просмотрены. Далее12.3.68 pure child CPU profile full-route product≤100000/timeout10s/memory cap; не грузить тяжёлый baseline в UI. Остаточные DFS worst-case/full-route memory/native selector и внешняя12.5 не объявляются принятыми. Общая main/context регрессия относится к ранее записанным HEAD; текущий footer delta проверен профильными16+2. + + +## 12.3.68 — bounded CPU-профиль генерации полных маршрутов + +2026-10-07. Pure child process без API/DB/браузера/сети, product≤100000/dimensions1–1000/timeout10s/V8heap256MB.1000WAN×1JH×1Exit1000routes≈16.9ms/heapDelta1.8MB;10×100×100100000routes≈329.6ms/22.0MB;100×10×100≈266.7ms/27.1MB. Первый измеренный прогон; точные повторные значения и source SHA256 в docs/ux-ui/acceptance/optimizer-routes-baseline-2026-10-07.json. Heap delta не peak/retained memory, время не production p95/SLA. Проверены count/first-last IDs/bestFirst/сохранение отсутствующих наблюдений. ESLint скрипта прошёл. + +Baseline в UI не запускался. Повторный Array.find по1000wanJhLegs на каждую из100000комбинаций даёт лишние линейные сканирования. Следующий12.3.69: map lookup с сохранением первого matching leg и порядка routes/ties, профильный repeat. Полный product всё ещё хранится/сортируется в памяти; опасную произвольную размерность этот профиль не принимает. Далее аудит TX/RX и связи wildcard-пробы с конкретным WAN, прежде чем объявлять observed correctness закрытой. Внешняя12.5 отдельно. diff --git a/docs/ux-ui/acceptance/optimizer-routes-baseline-2026-10-07.json b/docs/ux-ui/acceptance/optimizer-routes-baseline-2026-10-07.json new file mode 100644 index 00000000..71caf5b0 --- /dev/null +++ b/docs/ux-ui/acceptance/optimizer-routes-baseline-2026-10-07.json @@ -0,0 +1,72 @@ +{ + "head": "53e36f2a036881962cf6ebce98c01cb8063b7c9d", + "sourceSha256": "53273273c46f78cde2c82cff52b282dd19dcfc5c363f0b64c0b50cac44e20510", + "runs": [ + { + "wanCount": 1000, + "jhCount": 1, + "exitCount": 1, + "combinations": 1000, + "memoryCapMb": 256, + "timeoutMs": 10000, + "totalMs": 308, + "status": 0, + "timedOut": false, + "result": { + "elapsedMs": 16.607, + "heapDeltaBytes": 1783440, + "wanJhLegs": 1000, + "routes": 1000, + "firstId": "1-w0-2-3", + "lastId": "1-w999-2-3", + "bestMatchesFirst": true, + "missingObservations": true + }, + "error": "" + }, + { + "wanCount": 10, + "jhCount": 100, + "exitCount": 100, + "combinations": 100000, + "memoryCapMb": 256, + "timeoutMs": 10000, + "totalMs": 526, + "status": 0, + "timedOut": false, + "result": { + "elapsedMs": 284.0216, + "heapDeltaBytes": 16283720, + "wanJhLegs": 1000, + "routes": 100000, + "firstId": "1-w0-2-102", + "lastId": "1-w9-101-201", + "bestMatchesFirst": true, + "missingObservations": true + }, + "error": "" + }, + { + "wanCount": 100, + "jhCount": 10, + "exitCount": 100, + "combinations": 100000, + "memoryCapMb": 256, + "timeoutMs": 10000, + "totalMs": 526, + "status": 0, + "timedOut": false, + "result": { + "elapsedMs": 287.04370000000006, + "heapDeltaBytes": 16787856, + "wanJhLegs": 1000, + "routes": 100000, + "firstId": "1-w0-2-12", + "lastId": "1-w99-11-111", + "bestMatchesFirst": true, + "missingObservations": true + }, + "error": "" + } + ] +} diff --git a/scripts/performance/optimizer-routes-profile.mjs b/scripts/performance/optimizer-routes-profile.mjs new file mode 100644 index 00000000..29580e1d --- /dev/null +++ b/scripts/performance/optimizer-routes-profile.mjs @@ -0,0 +1,23 @@ +// Bounded pure full-route calculation: no API, database, network or browser. +import {spawnSync} from 'node:child_process' +import assert from 'node:assert/strict' +const [wanCount,jhCount,exitCount]=process.argv.slice(2).length?process.argv.slice(2).map(Number):[10,100,100] +const combinations=wanCount*jhCount*exitCount +if([wanCount,jhCount,exitCount].some(n=>!Number.isInteger(n)||n<1||n>1000)||!Number.isFinite(combinations)||combinations>100000)throw new Error('Three dimensions 1..1000; product <=100000') +const code=` +const fs=require('node:fs'),ts=require('typescript'); +const source=ts.transpileModule(fs.readFileSync('lib/route-optimizer-data.ts','utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText; +const mod={exports:{}};new Function('exports','require','module',source)(mod.exports,require,mod); +const {buildLiveOptimizerData,DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS}=mod.exports; +const common={host:'192.0.2.1',enabled:true,status:'online',latency:10,country:'RU',site:'TEST',model:'CHR'}; +const rows=[{...common,id:1,name:'Home',type:'home-router',wanUplinks:Array.from({length:${wanCount}},(_,i)=>({id:'w'+i,name:'WAN'+(i+1),iface:'ether'+(i+1),isp:'ISP',ip:'198.51.100.1',maxDl:100,maxUl:100}))},...Array.from({length:${jhCount}},(_,i)=>({...common,id:i+2,name:'Jump'+i,type:'jump-host'})),...Array.from({length:${exitCount}},(_,i)=>({...common,id:i+2+${jhCount},name:'Exit'+i,type:'exit-node'}))]; +const before=process.memoryUsage().heapUsed,start=performance.now();const entry=buildLiveOptimizerData(rows,[],DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS).homes[0]; +console.log(JSON.stringify({elapsedMs:performance.now()-start,heapDeltaBytes:process.memoryUsage().heapUsed-before,wanJhLegs:entry.wanJhLegs.length,routes:entry.fullRoutes.length,firstId:entry.fullRoutes[0]?.id,lastId:entry.fullRoutes.at(-1)?.id,bestMatchesFirst:entry.bestRoute===entry.fullRoutes[0],missingObservations:entry.fullRoutes.every(route=>route.hw.observed.probeId===null&&route.je.observed.probeId===null)})); +`; +const start=performance.now(),r=spawnSync(process.execPath,['--max-old-space-size=256','-e',code],{cwd:process.cwd(),timeout:10000,encoding:'utf8',windowsHide:true,maxBuffer:16384}); +const result=r.status===0?JSON.parse(r.stdout):null; +const profile={wanCount,jhCount,exitCount,combinations,memoryCapMb:256,timeoutMs:10000,totalMs:Math.round(performance.now()-start),status:r.status,timedOut:r.error?.code==='ETIMEDOUT',result,error:r.stderr.slice(0,2000)}; +console.log(JSON.stringify(profile,null,2)); +if(result){assert.equal(result.routes,combinations);assert.equal(result.wanJhLegs,wanCount*jhCount);assert.equal(result.firstId,`1-w0-2-${jhCount+2}`);assert.equal(result.lastId,`1-w${wanCount-1}-${jhCount+1}-${jhCount+exitCount+1}`);assert.equal(result.bestMatchesFirst,true);assert.equal(result.missingObservations,true)} +if(r.error&&!profile.timedOut)throw r.error; +if(r.status!==0&&!profile.timedOut)process.exitCode=1;