fix(traffic): объединять чтения истории общего сервера
This commit is contained in:
1 parent
7b88416415
commit
a68bd61e58
18 files changed
+1373
-30
No files matched your search
@@ -0,0 +1,33 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import vm from 'node:vm'
|
||||
import ts from 'typescript'
|
||||
import {buildTrafficFromSamples,mergeBuiltTraffic} from './traffic-rate.js'
|
||||
import {applyTrafficFreshness,trafficBindingStatus} from './traffic-freshness.js'
|
||||
|
||||
const source=fs.readFileSync(new URL('./traffic-users.ts',import.meta.url),'utf8')
|
||||
const ast=ts.createSourceFile('traffic-users.ts',source,ts.ScriptTarget.Latest,true)
|
||||
const names=['seriesFromBuilt','buildUserTrafficList']
|
||||
const functions=ast.statements.filter(n=>ts.isFunctionDeclaration(n)&&n.name&&names.includes(n.name.text)).map(n=>n.getText(ast)).join('\n')
|
||||
assert.equal(ast.statements.filter(n=>ts.isFunctionDeclaration(n)&&n.name&&names.includes(n.name.text)).length,2)
|
||||
let calls=0,fail=false
|
||||
const at=Date.parse('2026-10-13T00:00:00Z')
|
||||
const users=Array.from({length:100},(_,i)=>({id:String(i),login:'user'+i,name:'User '+i,role:'user',active:true,
|
||||
bindings:[{serverId:1,interfaceName:'ether1',peerPublicKey:'',userId:String(i),id:'binding'+i}]}))
|
||||
const context=vm.createContext({Date,Map,Promise,buildTrafficFromSamples,mergeBuiltTraffic,applyTrafficFreshness,trafficBindingStatus,
|
||||
listUsers:async()=>users,servers:{},db:{select:()=>({from:async()=>[{id:1,enabled:true,updatedAt:new Date(at).toISOString()}]})},
|
||||
readTrafficCollectionContext:async()=>({settings:{enabled:true,intervalSec:30},states:new Map()}),
|
||||
readServerSamplesInRange:async()=>{calls++;await new Promise(resolve=>setTimeout(resolve,5));if(fail)throw Error('fixture read failed');
|
||||
return [0,30_000].map((offset,i)=>({interfaceName:'ether1',sampledAt:new Date(at+offset).toISOString(),rxBytes:i*1024,txBytes:0,rxBps:0,txBps:0,running:true,disabled:false}))},
|
||||
})
|
||||
vm.runInContext(ts.transpileModule(functions,{compilerOptions:{target:ts.ScriptTarget.ES2022,module:ts.ModuleKind.CommonJS}}).outputText.replace(/^.*exports.*$/gm,''),context)
|
||||
const build=context.buildUserTrafficList as (start:number,end:number)=>Promise<Array<{rxTotal:number;interfaces:unknown[]}>>
|
||||
const rows=await build(at,at+30_000)
|
||||
assert.equal(calls,1,'100 concurrent users share one server history read')
|
||||
assert.equal(rows.length,100);assert(rows.every(r=>r.rxTotal===1024/1024**3&&r.interfaces.length===1))
|
||||
await build(at,at+60_000);assert.equal(calls,2,'cache is scoped to a request and never reuses an older period')
|
||||
fail=true;await assert.rejects(build(at,at+60_000),/fixture read failed/);assert.equal(calls,3)
|
||||
fail=false;await build(at,at+60_000);assert.equal(calls,4,'a failed request cannot poison later reads')
|
||||
users[99].bindings[0].serverId=2
|
||||
await build(at,at+60_000);assert.equal(calls,6,'different server IDs never share a cached history')
|
||||
console.log('user traffic: 100 concurrent consumers/one server read/request scope/failure/recovery passed')
|
||||
@@ -80,18 +80,19 @@ export async function buildUserTrafficList(rangeStartMs: number, rangeEndMs: num
|
||||
const users = await listUsers()
|
||||
const [context, allServers] = await Promise.all([readTrafficCollectionContext(), db.select().from(servers)])
|
||||
const catalog = new Map(allServers.map(s => [s.id, s]))
|
||||
const sampleCache = new Map<number, Awaited<ReturnType<typeof readServerSamplesInRange>>>()
|
||||
const sampleCache = new Map<number, ReturnType<typeof readServerSamplesInRange>>()
|
||||
|
||||
return Promise.all(users.map(async (user) => {
|
||||
const parts: BuiltTrafficSeries[] = []
|
||||
const interfaces: BoundIfaceTrafficDto[] = []
|
||||
|
||||
for (const b of user.bindings) {
|
||||
let rows = sampleCache.get(b.serverId)
|
||||
if (!rows) {
|
||||
rows = await readServerSamplesInRange(b.serverId, sinceIso)
|
||||
sampleCache.set(b.serverId, rows)
|
||||
let pending = sampleCache.get(b.serverId)
|
||||
if (!pending) {
|
||||
pending = readServerSamplesInRange(b.serverId, sinceIso)
|
||||
sampleCache.set(b.serverId, pending)
|
||||
}
|
||||
const rows = await pending
|
||||
const peerKey = b.peerPublicKey ?? ""
|
||||
const built = buildTrafficFromSamples(rows, rangeStartMs, rangeEndMs, b.interfaceName, peerKey, true)
|
||||
const server = catalog.get(b.serverId)
|
||||
|
||||
Reference in new issue
Block a user