From 5d1ece1fd503f1a816d07eb128ba33ee84e1af69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 9 May 2026 23:46:56 +0200 Subject: [PATCH] feat(db): HostMetric model for host-load sampling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the per-minute snapshot table that backs the new admin host-load chart above /admin/system-status. One row per minute, captured by the pg-boss sampler in the next commit, with 7-day retention enforced inside the worker so the table stays under a few thousand rows. Schema mirrors what the chart needs: - load_avg_1/5/15 (DOUBLE PRECISION) — os.loadavg() output - mem_used_bytes / mem_total_bytes — derived from os.totalmem() and os.freemem(), kept as raw bytes so the API can compute either a percent or a MiB delta downstream - disk_read_bytes / disk_write_bytes — nullable cumulative byte counters from /proc/diskstats. NULL on macOS/Windows dev hosts; Linux production picks them up automatically. The API endpoint converts deltas to bytes-per-second between samples Indexed by captured_at so the "last 2 hours" query stays index-only. Migration 0032_host_metric. integration setup.ts adds host_metrics to the truncate list so testcontainers stays clean across runs. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../migrations/0032_host_metric/migration.sql | 31 +++++++++++++++++++ prisma/schema.prisma | 26 ++++++++++++++++ tests/integration/setup.ts | 1 + 3 files changed, 58 insertions(+) create mode 100644 prisma/migrations/0032_host_metric/migration.sql diff --git a/prisma/migrations/0032_host_metric/migration.sql b/prisma/migrations/0032_host_metric/migration.sql new file mode 100644 index 000000000..e76902ec3 --- /dev/null +++ b/prisma/migrations/0032_host_metric/migration.sql @@ -0,0 +1,31 @@ +-- v1.4.16 phase B3: per-minute host-load snapshot table. +-- +-- The admin /system-status section now renders a 2-hour host-load chart +-- above the existing facts grid (Marc: "über dem Systemstatus vielleicht +-- nochmal so ein Graf der Auslastung des Hosts der letzten zwei Stunden"). +-- +-- A pg-boss `host-metric-sample` job inserts one row per minute carrying +-- `os.loadavg()` (1/5/15-minute) + `os.totalmem() - os.freemem()` and, +-- on Linux, a rolling cumulative byte counter from /proc/diskstats. +-- Disk fields are nullable so non-Linux dev hosts keep working. +-- +-- Retention: 7 days, enforced inside the sampler at the end of each +-- tick. Indexed by `captured_at` so the API's `WHERE captured_at >= +-- now() - 2h ORDER BY captured_at` query is index-only. + +CREATE TABLE "host_metrics" ( + "id" TEXT NOT NULL, + "captured_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "load_avg_1" DOUBLE PRECISION NOT NULL, + "load_avg_5" DOUBLE PRECISION NOT NULL, + "load_avg_15" DOUBLE PRECISION NOT NULL, + "mem_used_bytes" BIGINT NOT NULL, + "mem_total_bytes" BIGINT NOT NULL, + "disk_read_bytes" BIGINT, + "disk_write_bytes" BIGINT, + + CONSTRAINT "host_metrics_pkey" PRIMARY KEY ("id") +); + +CREATE INDEX "host_metrics_captured_at_idx" + ON "host_metrics" ("captured_at"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0b9aea443..7aa78a626 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -757,3 +757,29 @@ model UserAchievement { @@index([userId]) @@map("user_achievements") } + +// ─── Host Metrics (admin system-status host-load chart) ────── + +/// Per-minute snapshot of host-process load + memory captured by the +/// pg-boss `host-metric-sample` job. Read by `/api/admin/host-metrics` +/// to render the host-load trend above the system-status section. +/// 7-day retention is enforced inside the sampler — we keep the table +/// small enough that a single `findMany` over the last 2 hours stays +/// well under 200 rows. +model HostMetric { + id String @id @default(cuid()) + capturedAt DateTime @default(now()) @map("captured_at") + loadAvg1 Float @map("load_avg_1") + loadAvg5 Float @map("load_avg_5") + loadAvg15 Float @map("load_avg_15") + memUsedBytes BigInt @map("mem_used_bytes") + memTotalBytes BigInt @map("mem_total_bytes") + // Optional rolling-window byte counters from /proc/diskstats. Null + // outside Linux (macOS dev machines, Windows). The API endpoint + // converts deltas into bytes-per-second client-side. + diskReadBytes BigInt? @map("disk_read_bytes") + diskWriteBytes BigInt? @map("disk_write_bytes") + + @@index([capturedAt]) + @@map("host_metrics") +} diff --git a/tests/integration/setup.ts b/tests/integration/setup.ts index 846c6200b..5a5c0b1ba 100644 --- a/tests/integration/setup.ts +++ b/tests/integration/setup.ts @@ -43,6 +43,7 @@ export async function truncateAllTables(client: PrismaClient): Promise { "data_backups", "devices", "feedback", + "host_metrics", "idempotency_keys", "integration_statuses", "measurements",