diff --git a/docs/ux-ui-performance.md b/docs/ux-ui-performance.md index 9f925bf3..fb799d7e 100644 --- a/docs/ux-ui-performance.md +++ b/docs/ux-ui-performance.md @@ -163,3 +163,10 @@ WAN1000×JH1:1000→50rows,28019→1419tableDOM,28712→2149pageDOM,2264ms dev-r Final1000WAN:134chars/86.5px высоты summary; WAN10:130chars/86.5px. В тест добавлены реальные размеры/длина preview, точные counts и мобильный snapshot.12 profile browser +1 screenshot repeat, full ESLint страницы/test и production build прошли. Полный search WAN1000, последняя страница, refresh/clamp/pinnedWAN/320px/axe сохранены. Снимок docs/ux-ui/screens/optimizer-wan-summary-320.png просмотрен;9 historical optimizer PNG восстановлены по явному списку. Общие main/context доказательства относятся к прежним HEAD; этот небольшой текстовый delta проверен профильными сценариями. Исторический context-delta(a558412/36) сохранён отдельно от нового context-final-delta(b1f8326/221), чтобы ссылки12.4.3 не изменили смысл. Следующий12.3.65 — pure CPU7WAN×1000pool с hard timeout; не запускать в UI. KPI captions/native selector и худшая DFS/full-route комбинация не приняты этим блоком. Внешняя12.5 остаётся. + + +## 12.3.65 — CPU-профили pool1000 + +2026-10-04, UI/source расчёта не менялись. Child script принимает integer count20–1000, hard timeout10с, без API/DB/браузера/сети.7WAN×1000: equal1217.5ms (IDs p0..6), varied7967.5ms (p13..19), freshness не завершился за10с, процесс остановлен. Startup отдельно ~0.4s. Freshness создаёт1000 разных возрастов, будущие timestamps не используются. ESLint скрипта прошёл; корректность ID проверяется после успешного завершения. + +Это оставшийся CPU риск, а не performance приёмка. В current DFS повторяются Date.parse/trim/score и оценка upper для ветвей, уже достигших максимум. Следующий12.3.66: score cache только для bounded WAN≤7 DFS с одним snapshot time и early stop при достижении node upper, preserving first tie/order/forced picks/greedy fallback. Проверить1200 legacy oracle fixtures, raw observations, profiles20/1000. Худшая комбинаторика и весь full-route product по-прежнему не ограничены. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 2aa248a4..842dd8af 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,6 +1,6 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-04: 12.3.64 завершён: сводка1000WAN13695→86.5px/134chars, полный каталог сохранён;12+1browser/lint/build passed. Следующий12.3.65: CPU7WAN×1000 probes в child с timeout, без запуска тяжёлого расчёта в UI; затем KPI captions/внешняя12.5. Main1161/1162+focus fix20+15,context221/26 записаны. +Актуальное продолжение 2026-10-04: 12.3.65 baseline7WAN×1000pool сохранён: equal1218ms,varied7968ms,freshness timeout10с. Следующий12.3.66: кешировать score пары WAN/probe на одном времени снимка только в DFS≤7, безопасно завершать ветвь при достижении её upper bound; доказать exact IDs/ties и повторить20/1000 child profiles. Не запускать тяжёлый pool в UI. Внешняя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 9aa79522..c57c89f3 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-04: 12.3.64 завершён: сводка1000WAN13695→86.5px/134chars, полный каталог сохранён;12+1browser/lint/build passed. Следующий12.3.65: CPU7WAN×1000 probes в child с timeout, без запуска тяжёлого расчёта в UI; затем KPI captions/внешняя12.5. Main1161/1162+focus fix20+15,context221/26 записаны. +Актуальное продолжение 2026-10-04: 12.3.65 baseline7WAN×1000pool сохранён: equal1218ms,varied7968ms,freshness timeout10с. Следующий12.3.66: кешировать score пары WAN/probe на одном времени снимка только в DFS≤7, безопасно завершать ветвь при достижении её upper bound; доказать exact IDs/ties и повторить20/1000 child profiles. Не запускать тяжёлый pool в UI. Внешняя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 63c13f61..6451d06e 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -740,3 +740,10 @@ Main12.4.6 относится к991f2b3:1161/1162; единственный focu Final1000WAN:134chars/86.5px высоты summary; WAN10:130chars/86.5px. В тест добавлены реальные размеры/длина preview, точные counts и мобильный snapshot.12 profile browser +1 screenshot repeat, full ESLint страницы/test и production build прошли. Полный search WAN1000, последняя страница, refresh/clamp/pinnedWAN/320px/axe сохранены. Снимок docs/ux-ui/screens/optimizer-wan-summary-320.png просмотрен;9 historical optimizer PNG восстановлены по явному списку. Общие main/context доказательства относятся к прежним HEAD; этот небольшой текстовый delta проверен профильными сценариями. Исторический context-delta(a558412/36) сохранён отдельно от нового context-final-delta(b1f8326/221), чтобы ссылки12.4.3 не изменили смысл. Следующий12.3.65 — pure CPU7WAN×1000pool с hard timeout; не запускать в UI. KPI captions/native selector и худшая DFS/full-route комбинация не приняты этим блоком. Внешняя12.5 остаётся. + + +## 12.3.65 — CPU-профили pool1000 + +2026-10-04, UI/source расчёта не менялись. Child script принимает integer count20–1000, hard timeout10с, без API/DB/браузера/сети.7WAN×1000: equal1217.5ms (IDs p0..6), varied7967.5ms (p13..19), freshness не завершился за10с, процесс остановлен. Startup отдельно ~0.4s. Freshness создаёт1000 разных возрастов, будущие timestamps не используются. ESLint скрипта прошёл; корректность ID проверяется после успешного завершения. + +Это оставшийся CPU риск, а не performance приёмка. В current DFS повторяются Date.parse/trim/score и оценка upper для ветвей, уже достигших максимум. Следующий12.3.66: score cache только для bounded WAN≤7 DFS с одним snapshot time и early stop при достижении node upper, preserving first tie/order/forced picks/greedy fallback. Проверить1200 legacy oracle fixtures, raw observations, profiles20/1000. Худшая комбинаторика и весь full-route product по-прежнему не ограничены. diff --git a/scripts/performance/optimizer-probes-profile.mjs b/scripts/performance/optimizer-probes-profile.mjs index dfe66e78..4e0c4f74 100644 --- a/scripts/performance/optimizer-probes-profile.mjs +++ b/scripts/performance/optimizer-probes-profile.mjs @@ -2,6 +2,8 @@ import assert from 'node:assert/strict' import { spawnSync } from 'node:child_process' const variant=process.argv[2] ?? 'equal'; +const probeCount=Number(process.argv[3] ?? 20); +if(!Number.isInteger(probeCount)||probeCount<20||probeCount>1000)throw new Error('Probe count must be an integer from 20 to 1000'); if(!['equal','varied','freshness'].includes(variant)) throw new Error('Use equal, varied or freshness'); const code = ` const fs=require('node:fs'),ts=require('typescript'); @@ -10,17 +12,17 @@ const code = ` 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:7},(_,i)=>({id:'wan'+i,name:'WAN'+i,isp:'ISP',iface:'ether'+i,ip:'198.51.100.1',maxDl:100,maxUl:100}))},{...common,id:2,name:'Jump',type:'jump-host'}]; - const probes=Array.from({length:20},(_,i)=>({id:'p'+i,srcServerId:'1',dstServerId:'2',srcInterface:'',dstInterface:'',enabled:true,lastPingRttMs:10,lastPingLossPct:0,lastTxAvgMbps:100,lastRxAvgMbps:100,lastPingAt:null})); + const probes=Array.from({length:${probeCount}},(_,i)=>({id:'p'+i,srcServerId:'1',dstServerId:'2',srcInterface:'',dstInterface:'',enabled:true,lastPingRttMs:10,lastPingLossPct:0,lastTxAvgMbps:100,lastRxAvgMbps:100,lastPingAt:null})); if(${JSON.stringify(variant)}==='varied') probes.forEach((p,i)=>{if(i<13)p.lastRxAvgMbps=null;if(i<6)p.lastTxAvgMbps=null}); - if(${JSON.stringify(variant)}==='freshness') probes.forEach((p,i)=>{p.lastPingAt=new Date(Date.now()-(20-i)*3600000).toISOString()}); + if(${JSON.stringify(variant)}==='freshness') probes.forEach((p,i)=>{p.lastPingAt=new Date(Date.now()-(${probeCount}-i)*3600000).toISOString()}); const start=performance.now();const result=buildLiveOptimizerData(rows,[],DEFAULT_ROUTE_AI_OPTIMIZER_SETTINGS,probes); console.log(JSON.stringify({elapsedMs:performance.now()-start,legs:result.homes[0].wanJhLegs.length,probeIds:result.homes[0].wanJhLegs.map(leg=>leg.observed.probeId)})); `; const start=performance.now(); const result=spawnSync(process.execPath,['-e',code],{cwd:process.cwd(),timeout:10000,encoding:'utf8',windowsHide:true}); -const profile={variant,wanCount:7,probeCount:20,timeoutMs:10000,elapsedMs:Math.round(performance.now()-start),status:result.status,timedOut:result.error?.code==='ETIMEDOUT',result:result.stdout.trim(),error:result.stderr.trim()}; +const profile={variant,wanCount:7,probeCount,timeoutMs:10000,elapsedMs:Math.round(performance.now()-start),status:result.status,timedOut:result.error?.code==='ETIMEDOUT',result:result.stdout.trim(),error:result.stderr.trim()}; console.log(JSON.stringify(profile,null,2)); if(result.error && !profile.timedOut) throw result.error; if(result.status !== 0 && !profile.timedOut) process.exitCode=1; -if(result.status===0){const measured=JSON.parse(result.stdout);assert.deepEqual(measured.probeIds,Array.from({length:7},(_,i)=>'p'+(variant==='equal'?i:i+13)))} +if(result.status===0){const measured=JSON.parse(result.stdout);assert.deepEqual(measured.probeIds,Array.from({length:7},(_,i)=>'p'+(variant==='equal'?i:variant==='freshness'?i+probeCount-7:i+13)))}