fix(traffic): сохранять точность объёмов и обозначать бинарные единицы
This commit is contained in:
1 parent
2358ecef60
commit
2b3d583e3b
29 files changed
+1452
-39
No files matched your search
@@ -28,6 +28,7 @@ import {
|
||||
} from "../services/traffic-users.js"
|
||||
|
||||
interface TrafficServerDto {
|
||||
volumeUnit: "GiB"
|
||||
observations?:TrafficObservations
|
||||
id: string
|
||||
name: string
|
||||
@@ -95,6 +96,7 @@ function buildServerTraffic(
|
||||
): TrafficServerDto {
|
||||
const built = buildTrafficFromSamples(rows, rangeStartMs, rangeEndMs, onlyInterface,undefined,true)
|
||||
return {
|
||||
volumeUnit: "GiB",
|
||||
id: String(s.id),
|
||||
name: s.name || s.host,
|
||||
site: s.site || "—",
|
||||
|
||||
@@ -58,6 +58,7 @@ const samples: TrafficSampleLike[] = [
|
||||
const built = buildTrafficFromSamples(samples, start, end)
|
||||
assert.ok(built.rxPeak > 0, "peak RX from delta")
|
||||
assert.equal(built.rxNow, 1, "last valid delta after reset skip")
|
||||
assert.equal(built.rxTotalGiB,3_750_000/1024**3,"only observed positive delta counts; reset is not volume")
|
||||
assert.ok(built.rxSeries.some((v) => v > 0), "bucket series not flat")
|
||||
assert.ok(built.rxPeak <= 1.1, "lo excluded from peak")
|
||||
|
||||
|
||||
@@ -216,8 +216,8 @@ export function buildTrafficFromSamples(
|
||||
txNow: last?.tx ?? 0,
|
||||
rxPeak,
|
||||
txPeak,
|
||||
rxTotalGiB: Number((rxBytesDelta / (1024 ** 3)).toFixed(1)),
|
||||
txTotalGiB: Number((txBytesDelta / (1024 ** 3)).toFixed(1)),
|
||||
rxTotalGiB: rxBytesDelta / (1024 ** 3),
|
||||
txTotalGiB: txBytesDelta / (1024 ** 3),
|
||||
sessions,
|
||||
rxSeries,
|
||||
txSeries,
|
||||
@@ -268,8 +268,6 @@ export function mergeBuiltTraffic(parts: BuiltTrafficSeries[]): BuiltTrafficSeri
|
||||
acc.txSeries[i] += p.txSeries[i] ?? 0
|
||||
}
|
||||
}
|
||||
acc.rxTotalGiB = Number(acc.rxTotalGiB.toFixed(1))
|
||||
acc.txTotalGiB = Number(acc.txTotalGiB.toFixed(1))
|
||||
return parts.every(p => p.observations) ? { ...acc, observations: mergeTrafficObservations(parts.map(p => p.observations!)) } : acc
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { eq } from "drizzle-orm"
|
||||
import { db } from "../db/index.js"
|
||||
import { servers } from "../db/schema.js"
|
||||
import { listUsers } from "../modules/users/service/users-service.js"
|
||||
@@ -13,6 +12,7 @@ import { applyTrafficFreshness } from "./traffic-freshness.js"
|
||||
import type { TrafficObservations } from "./traffic-rate.js"
|
||||
|
||||
export interface BoundIfaceTrafficDto {
|
||||
volumeUnit: "GiB"
|
||||
observations?: TrafficObservations
|
||||
id: string
|
||||
bindingId: string
|
||||
@@ -40,6 +40,7 @@ export interface BoundIfaceTrafficDto {
|
||||
}
|
||||
|
||||
export interface UserTrafficDto {
|
||||
volumeUnit: "GiB"
|
||||
observations?: TrafficObservations
|
||||
id: string
|
||||
login: string
|
||||
@@ -59,6 +60,7 @@ export interface UserTrafficDto {
|
||||
|
||||
function seriesFromBuilt(built: BuiltTrafficSeries) {
|
||||
return {
|
||||
volumeUnit: "GiB" as const,
|
||||
observations: built.observations,
|
||||
rxNow: built.rxNow,
|
||||
txNow: built.txNow,
|
||||
@@ -71,12 +73,6 @@ function seriesFromBuilt(built: BuiltTrafficSeries) {
|
||||
}
|
||||
}
|
||||
|
||||
async function serverStatus(serverId: number): Promise<"online" | "offline"> {
|
||||
const row = (await db.select().from(servers).where(eq(servers.id, serverId)).limit(1))[0]
|
||||
if (!row?.enabled) return "offline"
|
||||
return "online"
|
||||
}
|
||||
|
||||
export async function buildUserTrafficList(rangeStartMs: number, rangeEndMs: number): Promise<UserTrafficDto[]> {
|
||||
const sinceIso = new Date(rangeStartMs).toISOString()
|
||||
const users = await listUsers()
|
||||
@@ -122,7 +118,7 @@ export async function buildUserTrafficList(rangeStartMs: number, rangeEndMs: num
|
||||
serverSite: b.serverSite,
|
||||
serverCountry: b.serverCountry,
|
||||
...seriesFromBuilt(built),
|
||||
status: running && (await serverStatus(b.serverId)) === "online" ? "online" : "offline",
|
||||
status: running && server?.enabled ? "online" : "offline",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import assert from "node:assert/strict"
|
||||
import Fastify from "fastify"
|
||||
import { pool, closePool } from "../db/index.js"
|
||||
import { initDatabase } from "../db/bootstrap.js"
|
||||
import trafficRoutes from "../routes/traffic.js"
|
||||
const database=process.env.MMAPP_OWN_SQL_DB
|
||||
if(!database)console.log("traffic volume SQL: skipped; explicit owned database required")
|
||||
else {
|
||||
assert(/^mm_accept_166_\d+$/.test(database));assert.equal(new URL(process.env.DATABASE_URL!).pathname,"/"+database)
|
||||
assert.equal((await pool.query("SELECT current_database() AS name")).rows[0].name,database)
|
||||
const prefix="own-volume180-"+Date.now()+"-",userId=prefix+"user",at=new Date(Date.now()-5000).toISOString(),updatedAt=new Date(Date.now()-3600_000).toISOString()
|
||||
let serverId:number|undefined,cases=0
|
||||
const app=Fastify();await app.register(trafficRoutes)
|
||||
const get=async(path:string)=>{const r=await app.inject({method:"GET",url:path});assert.equal(r.statusCode,200);return r.json()}
|
||||
const put=async(name:string,offset:number,bytes:number)=>pool.query("INSERT INTO traffic_samples(server_id,interface_name,sampled_at,rx_bytes,tx_bytes,flags) VALUES($1,$2,$3,$4,$4,5)",[serverId,name,new Date(Date.parse(at)+offset).toISOString(),bytes])
|
||||
try {
|
||||
await initDatabase()
|
||||
serverId=Number((await pool.query("INSERT INTO servers(name,host,port,use_ssl,enabled,username,password,updated_at) VALUES($1,'127.0.0.1',1,false,true,'fixture','fixture',$2) RETURNING id",[prefix+"server",updatedAt])).rows[0].id)
|
||||
await pool.query("INSERT INTO traffic_settings(id,enabled,interval_sec) VALUES(1,true,30) ON CONFLICT(id) DO UPDATE SET enabled=true,interval_sec=30")
|
||||
await pool.query("INSERT INTO traffic_collection_state(server_id,server_updated_at,last_attempt_at,last_succeeded_at,status) VALUES($1,$2,$3,$3,'ok')",[serverId,updatedAt,at])
|
||||
await pool.query("INSERT INTO app_users(id,login,name) VALUES($1,$2,'Volume fixture')",[userId,prefix+"login"])
|
||||
await pool.query("INSERT INTO user_interface_bindings(id,user_id,server_id,interface_name,interface_type) VALUES($1,$2,$3,'ether1','ether')",[prefix+"binding",userId,serverId])
|
||||
for(const bytes of[1,1024,1024**2,1024**3]){
|
||||
await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId]);await put("ether1",-30_000,0);await put("ether1",0,bytes)
|
||||
const s=(await get(`/traffic/servers/${serverId}?range=1h`)).server
|
||||
assert.equal(s.volumeUnit,"GiB");assert.equal(s.rxTotal,bytes/1024**3);assert.equal(s.observations.rxNow,true)
|
||||
const user=(await get("/traffic/users?range=1h")).users.find((u:{id:string})=>u.id===userId)
|
||||
assert.equal(user.volumeUnit,"GiB");assert.equal(user.rxTotal,s.rxTotal)
|
||||
const iface=(await get("/traffic/bound-interfaces?range=1h")).interfaces.find((i:{userId:string})=>i.userId===userId)
|
||||
assert.equal(iface.volumeUnit,"GiB");assert.equal(iface.rxTotal,s.rxTotal);cases++
|
||||
}
|
||||
await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId]);await pool.query("DELETE FROM user_interface_bindings WHERE user_id=$1",[userId])
|
||||
for(let i=0;i<100;i++){const name=`ether${i}`;await put(name,-30_000,0);await put(name,0,1024**2);await pool.query("INSERT INTO user_interface_bindings(id,user_id,server_id,interface_name,interface_type) VALUES($1,$2,$3,$4,'ether')",[prefix+"binding"+i,userId,serverId,name])}
|
||||
let s=(await get(`/traffic/servers/${serverId}?range=1h`)).server;assert.equal(s.rxTotal,100/1024)
|
||||
const user=(await get("/traffic/users?range=1h")).users.find((u:{id:string})=>u.id===userId);assert.equal(user.rxTotal,100/1024);cases++
|
||||
await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId]);await put("ether1",-90_000,0);await put("ether1",-60_000,1024**2);await put("ether1",-30_000,512);await put("ether1",0,1024)
|
||||
s=(await get(`/traffic/servers/${serverId}?range=1h`)).server;assert.equal(s.rxTotal,(1024**2+512)/1024**3);cases++
|
||||
console.log(`traffic volume owned SQL: ${cases} cases; exact bytes/binary unit/server/user/binding/100-part aggregate/reset passed`)
|
||||
}finally{await app.close();await pool.query("DELETE FROM app_users WHERE id=$1",[userId]);if(serverId!==undefined){await pool.query("DELETE FROM traffic_samples WHERE server_id=$1",[serverId]);await pool.query("DELETE FROM servers WHERE id=$1",[serverId])}await closePool()}
|
||||
}
|
||||
Reference in new issue
Block a user