Add persistent device traffic history charts
This commit is contained in:
@@ -9,6 +9,7 @@ export const DEVICE_INVENTORY_SCHEMA_VERSION = 2;
|
||||
const ONLINE_MS = 2 * 60 * 1000;
|
||||
const RECENT_MS = 24 * 60 * 60 * 1000;
|
||||
const RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
|
||||
const TRAFFIC_HISTORY_LIMIT = 120;
|
||||
const COUNTER_PATTERN = /^\d+$/;
|
||||
const DEVICE_ID_PATTERN = /^dev_[a-f0-9]{16}$/;
|
||||
const MAC_PATTERN = /^[0-9a-f]{2}(?::[0-9a-f]{2}){5}$/;
|
||||
@@ -302,6 +303,36 @@ export function createDeviceInventoryService({
|
||||
}) {
|
||||
let refreshPromise = null;
|
||||
let policyQueue = Promise.resolve();
|
||||
const trafficHistoryByMac = new Map();
|
||||
const trafficCursorByMac = new Map();
|
||||
|
||||
function captureTrafficHistory(state) {
|
||||
const knownMacs = new Set(state.devices.map(({ mac }) => mac));
|
||||
for (const device of state.devices) {
|
||||
const traffic = state.traffic.totalsByMac[device.mac];
|
||||
const proxy = state.traffic.proxy.totalsByMac[device.mac];
|
||||
const signature = `${traffic?.observedAt || ''}|${proxy?.observedAt || ''}`;
|
||||
if (signature === '|') continue;
|
||||
const gateway = BigInt(traffic?.uploadBytes || '0') + BigInt(traffic?.downloadBytes || '0');
|
||||
const proxyTotal = BigInt(proxy?.uploadBytes || '0') + BigInt(proxy?.downloadBytes || '0');
|
||||
const previous = trafficCursorByMac.get(device.mac);
|
||||
trafficCursorByMac.set(device.mac, { signature, gateway, proxy: proxyTotal });
|
||||
if (!previous || previous.signature === signature) continue;
|
||||
const observedAt = [traffic?.observedAt, proxy?.observedAt].filter(Boolean).sort().at(-1);
|
||||
const samples = trafficHistoryByMac.get(device.mac) || [];
|
||||
trafficHistoryByMac.set(device.mac, [...samples, {
|
||||
observedAt,
|
||||
gatewayBytes: gateway > previous.gateway ? (gateway - previous.gateway).toString() : '0',
|
||||
proxyBytes: proxyTotal > previous.proxy ? (proxyTotal - previous.proxy).toString() : '0',
|
||||
}].slice(-TRAFFIC_HISTORY_LIMIT));
|
||||
}
|
||||
for (const mac of trafficCursorByMac.keys()) {
|
||||
if (!knownMacs.has(mac)) {
|
||||
trafficCursorByMac.delete(mac);
|
||||
trafficHistoryByMac.delete(mac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function serializePolicy(action) {
|
||||
const result = policyQueue.then(action, action);
|
||||
@@ -363,6 +394,7 @@ export function createDeviceInventoryService({
|
||||
proxyUploadBytes: proxyTraffic?.uploadBytes || '0',
|
||||
proxyDownloadBytes: proxyTraffic?.downloadBytes || '0',
|
||||
proxyTrafficObservedAt: proxyTraffic?.observedAt || null,
|
||||
trafficHistory: trafficHistoryByMac.get(device.mac) || [],
|
||||
desiredPolicy: policy.desired,
|
||||
appliedPolicy: policy.applied,
|
||||
policyStatus: policy.status,
|
||||
@@ -376,6 +408,7 @@ export function createDeviceInventoryService({
|
||||
));
|
||||
return {
|
||||
revision: state.revision,
|
||||
trafficHistoryCapacity: TRAFFIC_HISTORY_LIMIT,
|
||||
source: {
|
||||
kind: 'neighbor',
|
||||
lastObservedAt: state.lastObservedAt,
|
||||
@@ -534,7 +567,7 @@ export function createDeviceInventoryService({
|
||||
identitiesByMac.get(mac).add(`${observation.ip}|${observation.interface || ''}`);
|
||||
}
|
||||
return serializePolicy(async () => {
|
||||
store.update((stored) => {
|
||||
const nextState = store.update((stored) => {
|
||||
const state = migrateDeviceInventoryState(stored);
|
||||
const byMac = new Map(state.devices.map((device) => [device.mac, device]));
|
||||
for (const observation of observations) {
|
||||
@@ -752,6 +785,7 @@ export function createDeviceInventoryService({
|
||||
devices,
|
||||
};
|
||||
});
|
||||
captureTrafficHistory(nextState);
|
||||
if (policyResult?.transportError) commitPolicyFailure(new Error(policyResult.transportError));
|
||||
return reconcileLocked(policyResult, false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user