better history for metrics

This commit is contained in:
KingKDot
2026-05-01 19:13:11 -05:00
parent 5380282726
commit 73c860c530
6 changed files with 54 additions and 9 deletions
+20 -2
View File
@@ -30,12 +30,16 @@ function initCharts() {
backgroundColor: "rgba(96, 165, 250, 0.1)", backgroundColor: "rgba(96, 165, 250, 0.1)",
fill: true, fill: true,
tension: 0.4, tension: 0.4,
pointRadius: 0,
pointHoverRadius: 4,
borderWidth: 2,
}, },
], ],
}, },
options: { options: {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
animation: false,
plugins: { plugins: {
legend: { display: false }, legend: { display: false },
tooltip: { tooltip: {
@@ -53,7 +57,12 @@ function initCharts() {
grid: { color: "rgba(100, 116, 139, 0.1)" }, grid: { color: "rgba(100, 116, 139, 0.1)" },
}, },
x: { x: {
ticks: { color: "#94a3b8", maxTicksLimit: 10 }, ticks: {
color: "#94a3b8",
maxTicksLimit: 6,
autoSkip: true,
maxRotation: 0,
},
grid: { color: "rgba(100, 116, 139, 0.1)" }, grid: { color: "rgba(100, 116, 139, 0.1)" },
}, },
}, },
@@ -72,12 +81,16 @@ function initCharts() {
backgroundColor: "rgba(192, 132, 252, 0.1)", backgroundColor: "rgba(192, 132, 252, 0.1)",
fill: true, fill: true,
tension: 0.4, tension: 0.4,
pointRadius: 0,
pointHoverRadius: 4,
borderWidth: 2,
}, },
], ],
}, },
options: { options: {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
animation: false,
plugins: { plugins: {
legend: { display: false }, legend: { display: false },
tooltip: { tooltip: {
@@ -95,7 +108,12 @@ function initCharts() {
grid: { color: "rgba(100, 116, 139, 0.1)" }, grid: { color: "rgba(100, 116, 139, 0.1)" },
}, },
x: { x: {
ticks: { color: "#94a3b8", maxTicksLimit: 10 }, ticks: {
color: "#94a3b8",
maxTicksLimit: 6,
autoSkip: true,
maxRotation: 0,
},
grid: { color: "rgba(100, 116, 139, 0.1)" }, grid: { color: "rgba(100, 116, 139, 0.1)" },
}, },
}, },
+14 -1
View File
@@ -5,7 +5,7 @@ import { fileURLToPath } from "url";
import path from "path"; import path from "path";
import fs from "fs/promises"; import fs from "fs/promises";
import { existsSync } from "node:fs"; import { existsSync } from "node:fs";
import { upsertClientRow, setOnlineState, listClients, markAllClientsOffline, getBuild, getBuildByTag, getAllBuilds, deleteExpiredBuilds, deleteBuild, getNotificationScreenshot, clearNotificationScreenshots, deleteClientRow, getClientIp, banIp, isIpBanned, clientExists, deleteExpiredSharedFiles, getChatHistory, insertChatMessage, getOnlineClientCountForUser, deleteExpiredChatMessages, recordBuildClaim } from "./db"; import { upsertClientRow, setOnlineState, listClients, markAllClientsOffline, getBuild, getBuildByTag, getAllBuilds, deleteExpiredBuilds, deleteBuild, getNotificationScreenshot, clearNotificationScreenshots, deleteClientRow, getClientIp, banIp, isIpBanned, clientExists, deleteExpiredSharedFiles, getChatHistory, insertChatMessage, getOnlineClientCountForUser, deleteExpiredChatMessages, recordBuildClaim, getClientMetricsSummary } from "./db";
import { handleFrame, handleHello, handlePing, handlePong } from "./wsHandlers"; import { handleFrame, handleHello, handlePing, handlePong } from "./wsHandlers";
import { getMessageByteLength, getMaxPayloadLimit, isAllowedClientMessageType } from "./wsValidation"; import { getMessageByteLength, getMaxPayloadLimit, isAllowedClientMessageType } from "./wsValidation";
import { ClientInfo, ClientRole } from "./types"; import { ClientInfo, ClientRole } from "./types";
@@ -140,6 +140,19 @@ import type { SocketData } from "./sessions/types";
import { SERVER_VERSION } from "./version"; import { SERVER_VERSION } from "./version";
metrics.setSnapshotEnricher((snapshot) => {
const summary = getClientMetricsSummary();
snapshot.clients.total = summary.total;
snapshot.clients.online = summary.online;
snapshot.clients.offline = summary.total - summary.online;
snapshot.clients.byOS = summary.byOS;
snapshot.clients.byCountry = summary.byCountry;
snapshot.sessions.console = sessionManager.getConsoleSessionCount();
snapshot.sessions.remoteDesktop = sessionManager.getRdSessionCount();
snapshot.sessions.fileBrowser = sessionManager.getFileBrowserSessionCount();
snapshot.sessions.process = sessionManager.getProcessSessionCount();
});
const config = loadConfig(); const config = loadConfig();
const isAuthorizedAgent = (req: Request, url: URL) => const isAuthorizedAgent = (req: Request, url: URL) =>
isAuthorizedAgentRequest(req, url, config.auth.agentToken); isAuthorizedAgentRequest(req, url, config.auth.agentToken);
+2 -2
View File
@@ -178,13 +178,13 @@ describe("MetricsCollector", () => {
}); });
test("history is capped at maxHistoryPoints", () => { test("history is capped at maxHistoryPoints", () => {
for (let i = 0; i < 100; i++) { for (let i = 0; i < 4500; i++) {
const snap = metrics.getSnapshot(); const snap = metrics.getSnapshot();
metrics.recordHistoryEntry(snap); metrics.recordHistoryEntry(snap);
} }
const history = metrics.getHistory(); const history = metrics.getHistory();
expect(history.length).toBeLessThanOrEqual(60); expect(history.length).toBeLessThanOrEqual(4320);
}); });
test("getHistory returns a copy", () => { test("getHistory returns a copy", () => {
+17 -2
View File
@@ -94,7 +94,8 @@ class MetricsCollector {
private receivedPerSecond: number = 0; private receivedPerSecond: number = 0;
private history: MetricsHistory[] = []; private history: MetricsHistory[] = [];
private maxHistoryPoints: number = 60; private maxHistoryPoints: number = 4320;
private snapshotEnricher: ((snapshot: MetricsSnapshot) => void) | null = null;
private pingValues: number[] = []; private pingValues: number[] = [];
private maxPingHistory: number = 1000; private maxPingHistory: number = 1000;
@@ -205,7 +206,21 @@ class MetricsCollector {
return { min, max, avg, count: this.pingValues.length }; return { min, max, avg, count: this.pingValues.length };
} }
private recordHistory() {} setSnapshotEnricher(fn: ((snapshot: MetricsSnapshot) => void) | null): void {
this.snapshotEnricher = fn;
}
private recordHistory() {
const snapshot = this.getSnapshot();
if (this.snapshotEnricher) {
try {
this.snapshotEnricher(snapshot);
} catch (err) {
console.error("metrics snapshot enricher failed:", err);
}
}
this.recordHistoryEntry(snapshot);
}
private trackEventLoopDelay() { private trackEventLoopDelay() {
const intervalMs = 1000; const intervalMs = 1000;
@@ -54,8 +54,6 @@ export async function handleMiscRoutes(
snapshot.sessions.fileBrowser = deps.getFileBrowserSessionCount(); snapshot.sessions.fileBrowser = deps.getFileBrowserSessionCount();
snapshot.sessions.process = deps.getProcessSessionCount(); snapshot.sessions.process = deps.getProcessSessionCount();
metrics.recordHistoryEntry(snapshot);
const history = metrics.getHistory(); const history = metrics.getHistory();
return new Response(JSON.stringify({ snapshot, history }), { return new Response(JSON.stringify({ snapshot, history }), {
+1
View File
@@ -1,5 +1,6 @@
github.com/gen2brain/x264-go/x264c v0.0.0-20221204084822-82ee2951dea2 h1:4GzTsZK/uG+tHvWR2ORcTCGTC4+mgOPhkUdz/Ro9alg= github.com/gen2brain/x264-go/x264c v0.0.0-20221204084822-82ee2951dea2 h1:4GzTsZK/uG+tHvWR2ORcTCGTC4+mgOPhkUdz/Ro9alg=
github.com/gen2brain/x264-go/x264c v0.0.0-20221204084822-82ee2951dea2/go.mod h1:MUMv+ZURlTO3qH8QAt2gxiIY5X4NMaxObz2ImZOMvyA= github.com/gen2brain/x264-go/x264c v0.0.0-20221204084822-82ee2951dea2/go.mod h1:MUMv+ZURlTO3qH8QAt2gxiIY5X4NMaxObz2ImZOMvyA=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=