feat(traffic): показать поток NetFlow на карте сети
Docker images / prepare-release (push) Successful in 10s
Docker images / backend-image (push) Successful in 2m14s
Docker images / frontend-image (push) Successful in 3m14s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 46s
Docker images / publish-release (push) Successful in 11s
Docker images / prepare-release (push) Successful in 10s
Docker images / backend-image (push) Successful in 2m14s
Docker images / frontend-image (push) Successful in 3m14s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 46s
Docker images / publish-release (push) Successful in 11s
Скорость между узлами считается как в Трафике (5 мин, без overlay/mesh), отдельно от ёмкости WAN и BT. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import type { FlowMapHop } from "@mmapp/contracts/traffic-flow"
|
||||
import { fmtRate } from "@/lib/fmt-rate"
|
||||
|
||||
export interface MatchedNetflowHop {
|
||||
bps: number
|
||||
bpsFwd: number
|
||||
bpsRev: number
|
||||
bytes: number
|
||||
}
|
||||
|
||||
function ifaceNorm(s: string | undefined): string {
|
||||
return (s ?? "").trim().toLowerCase()
|
||||
}
|
||||
|
||||
function pairKey(a: string, b: string): string {
|
||||
const x = String(a)
|
||||
const y = String(b)
|
||||
return x <= y ? `${x}\t${y}` : `${y}\t${x}`
|
||||
}
|
||||
|
||||
function mergeDirected(hops: FlowMapHop[], mapFromId: string): MatchedNetflowHop {
|
||||
let bytes = 0
|
||||
let bpsFwd = 0
|
||||
let bpsRev = 0
|
||||
const from = String(mapFromId)
|
||||
for (const h of hops) {
|
||||
bytes += h.bytes
|
||||
if (h.fromId === from) {
|
||||
bpsFwd += h.bpsFwd
|
||||
bpsRev += h.bpsRev
|
||||
} else {
|
||||
bpsFwd += h.bpsRev
|
||||
bpsRev += h.bpsFwd
|
||||
}
|
||||
}
|
||||
return { bytes, bpsFwd, bpsRev, bps: bpsFwd + bpsRev }
|
||||
}
|
||||
|
||||
export function hopHasRate(h: MatchedNetflowHop | undefined): h is MatchedNetflowHop {
|
||||
return h != null && Number.isFinite(h.bps) && h.bps > 0
|
||||
}
|
||||
|
||||
export function formatNetflowRate(hop: MatchedNetflowHop): string {
|
||||
return fmtRate(hop.bps / 1_000_000)
|
||||
}
|
||||
|
||||
export function formatNetflowDir(hop: MatchedNetflowHop): string {
|
||||
return `↓${fmtRate(hop.bpsFwd / 1_000_000)} ↑${fmtRate(hop.bpsRev / 1_000_000)}`
|
||||
}
|
||||
|
||||
/** GRE: сначала имя интерфейса туннеля на любом конце, иначе пара узлов. */
|
||||
export function matchNetflowForGreEdge(
|
||||
edge: {
|
||||
tunnel: { name: string }
|
||||
fromServer: { id: string }
|
||||
toServer: { id: string }
|
||||
},
|
||||
hops: FlowMapHop[],
|
||||
): MatchedNetflowHop | undefined {
|
||||
const name = ifaceNorm(edge.tunnel.name)
|
||||
const fromId = String(edge.fromServer.id)
|
||||
const toId = String(edge.toServer.id)
|
||||
if (name) {
|
||||
const ifaceHits = hops.filter((h) =>
|
||||
h.kind === "iface"
|
||||
&& ifaceNorm(h.iface) === name
|
||||
&& (h.fromId === fromId || h.fromId === toId),
|
||||
)
|
||||
if (ifaceHits.length) return mergeDirected(ifaceHits, fromId)
|
||||
const greNamed = hops.filter((h) =>
|
||||
h.kind === "gre"
|
||||
&& ifaceNorm(h.iface) === name
|
||||
&& (h.fromId === fromId || h.fromId === toId || h.toId === fromId || h.toId === toId),
|
||||
)
|
||||
if (greNamed.length) return mergeDirected(greNamed, fromId)
|
||||
}
|
||||
const want = pairKey(fromId, toId)
|
||||
const pairHits = hops.filter((h) =>
|
||||
h.kind === "gre" && Boolean(h.toId) && pairKey(h.fromId, h.toId) === want,
|
||||
)
|
||||
if (pairHits.length) return mergeDirected(pairHits, fromId)
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** WAN-аплинк HR: kind wan, иначе iface с тем же именем на homeId. */
|
||||
export function matchNetflowForWan(
|
||||
homeId: string,
|
||||
wanIface: string,
|
||||
hops: FlowMapHop[],
|
||||
): MatchedNetflowHop | undefined {
|
||||
const id = String(homeId)
|
||||
const iface = ifaceNorm(wanIface)
|
||||
if (!iface) return undefined
|
||||
const wanHits = hops.filter((h) =>
|
||||
h.kind === "wan" && h.fromId === id && ifaceNorm(h.iface) === iface,
|
||||
)
|
||||
if (wanHits.length) return mergeDirected(wanHits, id)
|
||||
const ifaceHits = hops.filter((h) =>
|
||||
h.kind === "iface" && h.fromId === id && ifaceNorm(h.iface) === iface,
|
||||
)
|
||||
if (ifaceHits.length) return mergeDirected(ifaceHits, id)
|
||||
return undefined
|
||||
}
|
||||
Reference in New Issue
Block a user