feat(web): compute memory usage client-side and add app-specific metrics to telemetry dashboard

This commit is contained in:
MythEclipse
2026-06-08 00:54:59 +07:00
parent f5ac0a8795
commit 26f99fa4d4
+66 -5
View File
@@ -236,6 +236,7 @@ export function TelemetryPage() {
const [stats, setStats] = useState<DashboardStats | null>(null); const [stats, setStats] = useState<DashboardStats | null>(null);
const [metrics, setMetrics] = useState<DiscoveredMetric[]>([]); const [metrics, setMetrics] = useState<DiscoveredMetric[]>([]);
const [chartMap, setChartMap] = useState<Map<string, ChartPoint[]>>(new Map()); const [chartMap, setChartMap] = useState<Map<string, ChartPoint[]>>(new Map());
const [memChartData, setMemChartData] = useState<ChartPoint[] | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
@@ -255,22 +256,51 @@ export function TelemetryPage() {
setStats(statsData); setStats(statsData);
setMetrics(discoverData); setMetrics(discoverData);
// Fetch charts for top metrics // Fetch charts for top metrics (excluding prometheus noise)
const topMetrics = discoverData const topMetrics = discoverData
.filter((m) => !m.metric_name.startsWith("prometheus_") && m.service !== "prometheus") .filter((m) => !m.metric_name.startsWith("prometheus_") && m.service !== "prometheus")
.slice(0, 6); .slice(0, 4);
const chartPanels = topMetrics.map((m) => ({ const chartPanels = topMetrics.map((m) => ({
key: m.metric_name, key: m.metric_name,
metric: m.metric_name, metric: m.metric_name,
})); }));
// Add CPU and disk // Built-in computations + app metrics
chartPanels.unshift({ key: "_cpu_usage_pct", metric: "_cpu_usage_pct" }); chartPanels.unshift({ key: "_cpu_usage_pct", metric: "_cpu_usage_pct" });
chartPanels.unshift({ key: "_disk_usage_pct", metric: "_disk_usage_pct" }); chartPanels.unshift({ key: "_disk_usage_pct", metric: "_disk_usage_pct" });
// Memory charts (raw values, % computed client-side)
chartPanels.push({ key: "node_memory_MemTotal_bytes", metric: "node_memory_MemTotal_bytes" });
chartPanels.push({ key: "node_memory_MemAvailable_bytes", metric: "node_memory_MemAvailable_bytes" });
// App-specific metrics
for (const appMetric of ["zeavis_api_http_requests_total", "zeavis_api_http_requests_active"]) {
if (discoverData.find((m) => m.metric_name === appMetric)) {
chartPanels.push({ key: appMetric, metric: appMetric });
}
}
const charts = await api.charts(chartPanels); const charts = await api.charts(chartPanels);
setChartMap(charts); setChartMap(charts);
// Compute memory usage % = (total - available) / total
const memTotalData = charts.get("node_memory_MemTotal_bytes");
const memAvailData = charts.get("node_memory_MemAvailable_bytes");
if (memTotalData && memAvailData && memTotalData.length > 0 && memAvailData.length > 0) {
const merged: ChartPoint[] = [];
for (let i = 0; i < Math.min(memTotalData.length, memAvailData.length); i++) {
const total = memTotalData[i].value;
const avail = memAvailData[i].value;
if (total > 0) {
merged.push({ time: memTotalData[i].time, value: 1 - avail / total });
}
}
setMemChartData(merged);
} else {
setMemChartData([]);
}
setChartMap(charts);
} catch (e) { } catch (e) {
setError(e instanceof Error ? e.message : "Unknown error"); setError(e instanceof Error ? e.message : "Unknown error");
} finally { } finally {
@@ -402,14 +432,20 @@ export function TelemetryPage() {
</div> </div>
)} )}
{/* CPU & Disk charts */} {/* CPU, Memory, Disk charts */}
<div className="grid gap-4 md:grid-cols-2"> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<MetricChart <MetricChart
title="CPU Usage (last hour)" title="CPU Usage (last hour)"
data={chartMap.get("_cpu_usage_pct") ?? []} data={chartMap.get("_cpu_usage_pct") ?? []}
loading={loading} loading={loading}
color="#2563eb" color="#2563eb"
/> />
<MetricChart
title="Memory Usage (last hour)"
data={memChartData ?? []}
loading={loading}
color="#8b5cf6"
/>
<MetricChart <MetricChart
title="Disk Usage (last hour)" title="Disk Usage (last hour)"
data={chartMap.get("_disk_usage_pct") ?? []} data={chartMap.get("_disk_usage_pct") ?? []}
@@ -418,6 +454,31 @@ export function TelemetryPage() {
/> />
</div> </div>
{/* Application Metrics */}
{(chartMap.has("zeavis_api_http_requests_total") || chartMap.has("zeavis_api_http_requests_active")) && (
<section className="space-y-4">
<h3 className="text-lg font-semibold text-[#214B11]">Application Metrics</h3>
<div className="grid gap-4 md:grid-cols-1 lg:grid-cols-2">
{chartMap.has("zeavis_api_http_requests_total") && (
<MetricChart
title="HTTP Requests Total (API)"
data={chartMap.get("zeavis_api_http_requests_total") ?? []}
loading={loading}
color="#ec4899"
/>
)}
{chartMap.has("zeavis_api_http_requests_active") && (
<MetricChart
title="Active HTTP Requests (API)"
data={chartMap.get("zeavis_api_http_requests_active") ?? []}
loading={loading}
color="#14b8a6"
/>
)}
</div>
</section>
)}
{/* Top Metrics */} {/* Top Metrics */}
<Card className="border-slate-200 shadow-sm"> <Card className="border-slate-200 shadow-sm">
<CardHeader className="pb-2 px-4 pt-4"> <CardHeader className="pb-2 px-4 pt-4">