fix(traffic): отличать измеренный ноль от отсутствия наблюдений

This commit is contained in:
Denozordec committed 2026-10-03 23:10:39 +07:00
1 parent a28f871fd4
commit 9e0f695684
12 files changed
+111 -30

No files matched your search

+23 -6
View File
@@ -20,7 +20,10 @@ export interface RatePoint {
txMbps: number
}
export interface TrafficObservations {rxNow:boolean;txNow:boolean;rxSeries:boolean[];txSeries:boolean[];sampledAt:string|null}
export interface BuiltTrafficSeries {
observations?:TrafficObservations
rxNow: number
txNow: number
rxPeak: number
@@ -65,10 +68,12 @@ export function rateBpsFromDelta(
prevAtMs: number,
nextAtMs: number,
): number | null {
if(![prevBytes,nextBytes,prevAtMs,nextAtMs].every(Number.isFinite)||prevBytes<0||nextBytes<0)return null
const dtSec = (nextAtMs - prevAtMs) / 1000
if (!(dtSec > 0) || !Number.isFinite(dtSec)) return null
if (nextBytes < prevBytes) return null
return Math.round(((nextBytes - prevBytes) * 8) / dtSec)
const rate=Math.round(((nextBytes - prevBytes) * 8) / dtSec)
return Number.isFinite(rate)?rate:null
}
export function bucketAvg(
@@ -91,8 +96,7 @@ export function bucketAvg(
}
function parseIsoMs(iso: string): number {
const t = Date.parse(iso)
return Number.isFinite(t) ? t : 0
return Date.parse(iso)
}
export function sampleSeriesKey(interfaceName: string, peerPublicKey = ""): string {
@@ -105,6 +109,7 @@ export function buildTrafficFromSamples(
rangeEndMs: number,
onlyInterface?: string | readonly string[],
peerPublicKey?: string,
includeObservations=false,
): BuiltTrafficSeries {
const empty: BuiltTrafficSeries = {
rxNow: 0,
@@ -117,7 +122,8 @@ export function buildTrafficFromSamples(
rxSeries: Array.from({ length: SERIES_POINTS }, () => 0),
txSeries: Array.from({ length: SERIES_POINTS }, () => 0),
}
if (rows.length === 0) return empty
const noObservations:TrafficObservations={rxNow:false,txNow:false,rxSeries:Array(SERIES_POINTS).fill(false),txSeries:Array(SERIES_POINTS).fill(false),sampledAt:null}
if (rows.length === 0) return includeObservations?{...empty,observations:noObservations}:empty
const bySeries = new Map<string, TrafficSampleLike[]>()
for (const r of rows) {
@@ -133,7 +139,8 @@ export function buildTrafficFromSamples(
const rxPoints: Array<{ t: number; v: number }> = []
const txPoints: Array<{ t: number; v: number }> = []
const byTs = new Map<number, { rx: number; tx: number }>()
const byTs = new Map<number, { rx: number; tx: number;rxObserved:boolean;txObserved:boolean;count:number }>()
let includedSeries=0,lastAttemptAt:number|null=null
let rxBytesDelta = 0
let txBytesDelta = 0
@@ -159,6 +166,7 @@ export function buildTrafficFromSamples(
if (!last) continue
if (!allowList && (!last.running || last.disabled)) continue
includedSeries++
if (last.running && !last.disabled) sessions += 1
const first = sorted[0]
@@ -176,12 +184,14 @@ export function buildTrafficFromSamples(
if (!allowList && (!cur.running || cur.disabled)) continue
const t0 = parseIsoMs(prev.sampledAt)
const t1 = parseIsoMs(cur.sampledAt)
if(Number.isFinite(Date.parse(cur.sampledAt))&&t1>=rangeStartMs&&t1<=rangeEndMs)lastAttemptAt=Math.max(lastAttemptAt??t1,t1)
const rxBps = rateBpsFromDelta(prev.rxBytes, cur.rxBytes, t0, t1)
const txBps = rateBpsFromDelta(prev.txBytes, cur.txBytes, t0, t1)
if (rxBps == null && txBps == null) continue
const rxMbps = bpsToMbps(rxBps ?? 0)
const txMbps = bpsToMbps(txBps ?? 0)
const acc = byTs.get(t1) ?? { rx: 0, tx: 0 }
const acc = byTs.get(t1) ?? { rx: 0, tx: 0,rxObserved:true,txObserved:true,count:0 }
acc.count++;acc.rxObserved&&=rxBps!==null;acc.txObserved&&=txBps!==null
acc.rx += rxMbps
acc.tx += txMbps
byTs.set(t1, acc)
@@ -210,6 +220,13 @@ export function buildTrafficFromSamples(
sessions,
rxSeries,
txSeries,
...(includeObservations?{observations:{
rxNow:lastAttemptAt!==null&&lastTs===lastAttemptAt&&!!last?.rxObserved&&last.count===includedSeries,
txNow:lastAttemptAt!==null&&lastTs===lastAttemptAt&&!!last?.txObserved&&last.count===includedSeries,
rxSeries:bucketAvg([...byTs].map(([t,v])=>({t,v:Number(v.rxObserved&&v.count===includedSeries&&t>=rangeStartMs&&t<=rangeEndMs)})),rangeStartMs,rangeEndMs).map(v=>v===1),
txSeries:bucketAvg([...byTs].map(([t,v])=>({t,v:Number(v.txObserved&&v.count===includedSeries&&t>=rangeStartMs&&t<=rangeEndMs)})),rangeStartMs,rangeEndMs).map(v=>v===1),
sampledAt:lastAttemptAt===null?null:new Date(lastAttemptAt).toISOString(),
}}:{}),
}
}