- 曲线空态和图共用一个定高容器,数据到来不再挤下面的内容 - 路由行去掉行间分割线:它会吃掉 hover 高亮的圆角 - 每路走势线先合并成 28 桶再画。3 秒一个采样直接全画,在一条 百来像素宽的线上只剩噪声;没采集的桶画断口,不画假零 - 环形图两段加圆角,交界用底色描边让出间隙 - 内核卡片从右列搬到服务器和路由之间(左列本来就矮一截),加 「重启内核」:POST /restart 后内核自我 exec、PID 不变,恢复没有 任何通知,只能每 2 秒探一次,半分钟探不到交还给外壳心跳
132 lines
4.0 KiB
TypeScript
132 lines
4.0 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
import {
|
|
bucketRates,
|
|
lastRate,
|
|
pushWindow,
|
|
routeDeltas,
|
|
windowBytes,
|
|
type ConnTotals,
|
|
type RouteSample,
|
|
} from './telemetry.ts'
|
|
import type { ConnectionItem } from './connections.ts'
|
|
|
|
function conn(id: string, inbound: string, upload: number, download: number): ConnectionItem {
|
|
return {
|
|
id,
|
|
upload,
|
|
download,
|
|
start: '2026-08-11T00:00:00+08:00',
|
|
chains: ['DIRECT'],
|
|
rule: 'Match',
|
|
rulePayload: '',
|
|
metadata: {
|
|
host: 'example.com',
|
|
destinationIP: '1.2.3.4',
|
|
destinationPort: '443',
|
|
inboundName: inbound,
|
|
inboundPort: '7890',
|
|
},
|
|
}
|
|
}
|
|
|
|
describe('pushWindow', () => {
|
|
test('把窗口外的老点挤掉,保留窗口内的', () => {
|
|
const list = [{ t: 0 }, { t: 5_000 }, { t: 9_000 }]
|
|
const next = pushWindow(list, { t: 12_000 }, 5_000)
|
|
expect(next.map((p) => p.t)).toEqual([9_000, 12_000])
|
|
})
|
|
|
|
test('全部过期时只剩新点', () => {
|
|
const next = pushWindow([{ t: 0 }], { t: 60_000 }, 5_000)
|
|
expect(next.map((p) => p.t)).toEqual([60_000])
|
|
})
|
|
})
|
|
|
|
describe('routeDeltas', () => {
|
|
test('已有连接算差值,并按入站聚合', () => {
|
|
const prev: ConnTotals = new Map([
|
|
['a', { up: 100, down: 200 }],
|
|
['b', { up: 10, down: 20 }],
|
|
])
|
|
const { deltas } = routeDeltas(prev, [
|
|
conn('a', 'lan-a', 150, 260),
|
|
conn('b', 'lan-a', 30, 21),
|
|
])
|
|
expect(deltas['lan-a']).toEqual({ up: 70, down: 61 })
|
|
})
|
|
|
|
test('新连接把累计全部记入本次间隔', () => {
|
|
const { deltas } = routeDeltas(new Map(), [conn('a', 'lan-a', 500, 700)])
|
|
expect(deltas['lan-a']).toEqual({ up: 500, down: 700 })
|
|
})
|
|
|
|
test('累计变小(内核重启)按全新连接处理,不出负数', () => {
|
|
const prev: ConnTotals = new Map([['a', { up: 900, down: 900 }]])
|
|
const { deltas } = routeDeltas(prev, [conn('a', 'lan-a', 50, 60)])
|
|
expect(deltas['lan-a']).toEqual({ up: 50, down: 60 })
|
|
})
|
|
|
|
test('消失的连接不再出现在 next 里', () => {
|
|
const prev: ConnTotals = new Map([['gone', { up: 1, down: 1 }]])
|
|
const { next } = routeDeltas(prev, [conn('a', 'lan-a', 5, 5)])
|
|
expect(next.has('gone')).toBe(false)
|
|
expect(next.get('a')).toEqual({ up: 5, down: 5 })
|
|
})
|
|
|
|
test('没有名字的入站退回端口做键', () => {
|
|
const item = conn('a', '', 5, 5)
|
|
const { deltas } = routeDeltas(new Map(), [item])
|
|
expect(Object.keys(deltas)).toEqual(['7890'])
|
|
})
|
|
})
|
|
|
|
describe('bucketRates', () => {
|
|
test('样本按桶平均,桶的时间戳在桶中央', () => {
|
|
const samples: RouteSample[] = [
|
|
{ t: 1_000, ms: 1_000, up: 100, down: 0 },
|
|
{ t: 2_000, ms: 1_000, up: 300, down: 0 },
|
|
{ t: 6_000, ms: 1_000, up: 50, down: 50 },
|
|
]
|
|
// 0-10s 分两桶:前桶 400 字节 / 2 秒 = 200 B/s,后桶 100 字节 / 1 秒 = 100 B/s
|
|
expect(bucketRates(samples, 0, 10_000, 2)).toEqual([
|
|
[2_500, 200],
|
|
[7_500, 100],
|
|
])
|
|
})
|
|
|
|
test('开头没数据的桶被裁掉,中间的留成 null', () => {
|
|
const samples: RouteSample[] = [
|
|
{ t: 3_000, ms: 1_000, up: 100, down: 0 },
|
|
{ t: 9_000, ms: 1_000, up: 100, down: 0 },
|
|
]
|
|
const points = bucketRates(samples, 0, 10_000, 5)
|
|
expect(points).toHaveLength(4)
|
|
expect(points[0]).toEqual([3_000, 100])
|
|
expect(points[1]![1]).toBeNull()
|
|
})
|
|
|
|
test('没有任何样本返回空数组', () => {
|
|
expect(bucketRates([], 0, 10_000, 5)).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('windowBytes / lastRate', () => {
|
|
const samples: RouteSample[] = [
|
|
{ t: 0, ms: 3_000, up: 100, down: 100 },
|
|
{ t: 3_000, ms: 3_000, up: 300, down: 300 },
|
|
{ t: 6_000, ms: 2_000, up: 400, down: 600 },
|
|
]
|
|
|
|
test('windowBytes 只算窗口内的样本', () => {
|
|
expect(windowBytes(samples, 3_000)).toBe(300 + 300 + 400 + 600)
|
|
})
|
|
|
|
test('lastRate 按实际间隔换算成每秒', () => {
|
|
expect(lastRate(samples)).toEqual({ up: 200, down: 300 })
|
|
})
|
|
|
|
test('lastRate 没有样本时是 null', () => {
|
|
expect(lastRate([])).toBeNull()
|
|
})
|
|
})
|