proxy,ui: add performance monitoring with Prometheus metrics (#743)
Add a comprehensive performance monitoring system that collects CPU, memory, swap, load average, network IO, and GPU stats. Provides both a REST API for the UI and a Prometheus /metrics endpoint. Backend changes: - New internal/perf package with configurable interval-based stats collection - GPU monitoring via LACT (Unix socket) and nvidia-smi fallback on Linux - Ring buffer (internal/ring) for time-series stat storage - Prometheus /metrics endpoint with all system and GPU metrics - Moved LogMonitor to internal/logmon package - New PerformanceConfig for hot-reloadable monitoring settings - REST /api/performance endpoint replacing SSE streaming UI changes: - New Performance page with real-time charts for CPU, memory, GPU, and network - Reusable PerformanceChart component - LLAMA_SWAP_URL environment variable support - Improved capture dialog display Other: - Example Grafana dashboard for Prometheus metrics - monitor-test standalone binary - Config schema and example updates fixes #596
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { Chart, registerables } from "chart.js";
|
||||
import { isDarkMode } from "../stores/theme";
|
||||
|
||||
Chart.register(...registerables);
|
||||
|
||||
interface Dataset {
|
||||
label: string;
|
||||
data: number[];
|
||||
borderColor: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
labels: string[];
|
||||
datasets: Dataset[];
|
||||
yMin?: number;
|
||||
yMax?: number;
|
||||
yLabel?: string;
|
||||
showLegend?: boolean;
|
||||
}
|
||||
|
||||
let { title, labels, datasets, yMin, yMax, yLabel, showLegend = true }: Props = $props();
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
let chart: Chart;
|
||||
|
||||
function getChartColors(dark: boolean) {
|
||||
return {
|
||||
grid: dark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)",
|
||||
tick: dark ? "#9ca3af" : "#6b7280",
|
||||
legend: dark ? "#d1d5db" : "#374151",
|
||||
tooltipBg: dark ? "#1f2937" : "#ffffff",
|
||||
tooltipText: dark ? "#f3f4f6" : "#111827",
|
||||
tooltipBorder: dark ? "#374151" : "#e5e7eb",
|
||||
};
|
||||
}
|
||||
|
||||
function buildOptions(dark: boolean) {
|
||||
const colors = getChartColors(dark);
|
||||
return {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: false as const,
|
||||
interaction: {
|
||||
mode: "index" as const,
|
||||
intersect: false,
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: showLegend,
|
||||
position: "top" as const,
|
||||
labels: {
|
||||
color: colors.legend,
|
||||
usePointStyle: true,
|
||||
pointStyle: "circle" as const,
|
||||
padding: 12,
|
||||
font: { size: 11 },
|
||||
},
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
color: colors.legend,
|
||||
font: { size: 14, weight: "bold" as const },
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: colors.tooltipBg,
|
||||
titleColor: colors.tooltipText,
|
||||
bodyColor: colors.tooltipText,
|
||||
borderColor: colors.tooltipBorder,
|
||||
borderWidth: 1,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
bounds: "data" as const,
|
||||
offset: false,
|
||||
ticks: { color: colors.tick, maxRotation: 0, font: { size: 10 }, maxTicksLimit: 10 },
|
||||
grid: { color: colors.grid },
|
||||
},
|
||||
y: {
|
||||
min: yMin,
|
||||
max: yMax,
|
||||
ticks: { color: colors.tick, font: { size: 10 } },
|
||||
grid: { color: colors.grid },
|
||||
title: yLabel
|
||||
? { display: true, text: yLabel, color: colors.tick }
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
chart = new Chart(canvas, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: [...labels],
|
||||
datasets: datasets.map((ds) => ({
|
||||
label: ds.label,
|
||||
data: [...ds.data],
|
||||
borderColor: ds.borderColor,
|
||||
backgroundColor: ds.borderColor + "20",
|
||||
borderWidth: 1.5,
|
||||
pointRadius: 0,
|
||||
tension: 0.4,
|
||||
fill: false,
|
||||
})),
|
||||
},
|
||||
options: buildOptions($isDarkMode),
|
||||
});
|
||||
|
||||
return () => {
|
||||
chart.destroy();
|
||||
};
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!chart) return;
|
||||
const _dark = $isDarkMode;
|
||||
chart.options = buildOptions(_dark);
|
||||
chart.update("none");
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!chart) return;
|
||||
const _l = labels;
|
||||
const _d = datasets;
|
||||
chart.data.labels = [..._l];
|
||||
chart.data.datasets = _d.map((ds) => ({
|
||||
label: ds.label,
|
||||
data: [...ds.data],
|
||||
borderColor: ds.borderColor,
|
||||
backgroundColor: ds.borderColor + "20",
|
||||
borderWidth: 1.5,
|
||||
pointRadius: 0,
|
||||
tension: 0.4,
|
||||
fill: false,
|
||||
}));
|
||||
chart.update("none");
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="card p-4 h-[300px]">
|
||||
<canvas bind:this={canvas}></canvas>
|
||||
</div>
|
||||
Reference in New Issue
Block a user