# Ressursforbruk — Måling og synliggjøring ## Konsept Alle ressurskrevende operasjoner logges med naturlige enheter. Forbruket akkumuleres på tre akser: noden som ble behandlet, brukeren som utløste det, og samlingen det skjedde i. Formålet er synliggjøring og innsikt, ikke fakturering. ## Ressurstyper | Ressurstype | Enhet | Hva måles | |---|---|---| | `ai` | tokens inn / tokens ut | LLM-kall via AI Gateway | | `whisper` | sekunder prosessert lyd | Transkripsjons-pipeline | | `tts` | tegn | Tekst-til-tale-generering | | `cas` | bytes | Lagring i CAS (store/delete) | | `bandwidth` | bytes ut | Servering av mediefiler og publisert innhold | | `livekit` | deltaker-minutter | WebRTC-sesjoner (møter, opptak) | | `graph` | noder / edges | Opprettelse av noder og edges i grafen | ## Logg-skjema ```sql CREATE TABLE resource_usage_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), target_node_id UUID NOT NULL REFERENCES nodes(id), triggered_by UUID REFERENCES nodes(id), -- null for system-jobber collection_id UUID REFERENCES nodes(id), resource_type TEXT NOT NULL, -- 'ai', 'whisper', 'tts', 'cas', 'bandwidth', 'livekit' detail JSONB NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_resource_usage_target ON resource_usage_log(target_node_id); CREATE INDEX idx_resource_usage_triggered ON resource_usage_log(triggered_by); CREATE INDEX idx_resource_usage_collection ON resource_usage_log(collection_id); CREATE INDEX idx_resource_usage_type_time ON resource_usage_log(resource_type, created_at); ``` ## Detail-struktur per type ### AI (LLM-kall) ```jsonc { "model_level": "fast", // "fast" | "smart" | "deep" "model_id": "gemini-2.0-flash", "tokens_in": 1240, "tokens_out": 380, "job_type": "auto_tag" // hva jobben var } ``` Modellnivåer: | Nivå | Semantikk | Typiske modeller | |---|---|---| | `fast` | Billig, lav latens | Gemini Flash, Haiku | | `smart` | Balansert | Sonnet, Gemini Pro | | `deep` | Grundig, dyr | Opus, GPT-4 | ### Whisper (transkripsjon) ```jsonc { "model": "medium", // "small" | "medium" | "large-v3" "duration_seconds": 2520, // lengde på prosessert lyd "language": "no", "mode": "batch" // "live" | "batch" } ``` ### TTS (tekst-til-tale) ```jsonc { "provider": "elevenlabs", // "elevenlabs" | "local" "characters": 8200, "voice_id": "norwegian_male_1" } ``` ### CAS (lagring) ```jsonc { "hash": "sha256-abc123...", "size_bytes": 84000000, "mime": "audio/mp3", "operation": "store" // "store" | "delete" } ``` ### Bandwidth (servering) ```jsonc { "size_bytes": 84000000, "path": "/media/podcast/ep47.mp3", "client": "Apple Podcasts" // parsert fra User-Agent } ``` ### LiveKit (sanntid) ```jsonc { "room_id": "meeting-abc123", "participant_minutes": 180, "tracks": 4 // antall aktive lyd/video-spor } ``` ### Graph (noder og edges) Trenger ikke logges i `resource_usage_log` — kan telles direkte fra `nodes` og `edges`-tabellene med `COUNT` + `GROUP BY created_by` eller `GROUP BY collection`. Billig spørring, ingen ekstra lagring. Vises i bruker- og samlingsvisning som kontekst: ``` Vegard: 423 noder opprettet 1 204 edges Sidelinja: 2 891 noder 8 340 edges ``` ## Aggregering Tre naturlige visninger, alle er GROUP BY-spørringer mot samme tabell: ### Per node Synlig i node-detaljer for eieren. Gir innsikt i hva en spesifikk node har kostet i ressurser. ``` Episode 47: AI (smart) 12k tokens inn, 3k ut — 4 jobber Whisper 42 min prosessert (medium) TTS 8 200 tegn CAS 84 MB lagret Båndbredde 2.3 GB servert LiveKit 180 deltaker-minutter 12 noder, 34 edges ``` ### Per bruker Synlig for brukeren selv i sin profil/innstillinger. Sum av alle noder brukeren har utløst arbeid på. ``` Vegard denne måneden: AI fast: 42k / smart: 18k / deep: 3k tokens Whisper 3.2 timer prosessert TTS 24k tegn 423 noder opprettet, 1 204 edges ``` ### Per samling Synlig for samlingens eiere/admins. Sum av alt forbruk i samlingen. Nyttig for å forstå hvilke samlinger som bruker mest ressurser. ``` Sidelinja (mars 2026): AI 148k tokens totalt Whisper 12.4 timer prosessert CAS 2.1 GB lagret Båndbredde 48 GB servert 2 891 noder, 8 340 edges ``` ## Triggered-by-regler | Scenario | triggered_by | |---|---| | Bruker klikker "oppsummer" | Brukeren | | Bruker sender melding som trigger auto-tag | Brukeren | | Nattlig samlings-digest | null (system) | | Podcast-nedlasting av ekstern lytter | null (system) | Når `triggered_by` er null, tilhører forbruket kun samlingen — det belaster ingen spesifikk bruker. ## Logging-ansvar Maskinrommet logger all ressursbruk. Hver handler (AI, Whisper, TTS, CAS, LiveKit) skriver til `resource_usage_log` som siste steg etter vellykket operasjon. Feilede jobber logges ikke — ingen ressurs ble forbrukt. Båndbredde-logging skjer via Caddy-logg-parsing i nattlig batch-jobb (samme mønster som `docs/features/podcast_statistikk.md`). ## Implementeringsstatus Følgende ressurstyper logges til `resource_usage_log`: | Ressurstype | Handler | Status | |---|---|---| | `ai` | `summarize.rs`, `ai_edges.rs`, `agent.rs` | Implementert. Token-telling fra LiteLLM `usage`-feltet. Agent bruker `claude` CLI og logger 0 tokens (CLI gir ikke token-info). | | `whisper` | `transcribe.rs` | Implementert. Logger `duration_seconds`, `model`, `language`, `mode`. | | `tts` | `tts.rs` | Implementert. Logger `provider`, `characters`, `voice_id`. | | `cas` | `intentions.rs` (upload_media) | Implementert. Logger kun nye filer (ikke dedup). `hash`, `size_bytes`, `mime`, `operation`. | | `livekit` | `intentions.rs` (join_communication) | Implementert. Logger `join`-hendelser. Faktisk `participant_minutes` krever LiveKit webhook-integrasjon (fremtidig). | | `bandwidth` | `bandwidth.rs` | Implementert. Nattlig jobb (kl 03:00) parser Caddy JSON-access-logger. | ### Sentralisert hjelpemodul `resource_usage.rs` tilbyr `log()` og `find_collection_for_node()`. Alle handlers bruker denne for konsistent logging. ### Admin-dashboard (oppgave 15.8) `/admin/usage` viser aggregert forbruksoversikt: - **Totalkort** per ressurstype med naturlige enheter (tokens, timer, GB, tegn, minutter) - **Per samling**-tabell: filtrerbar på ressurstype og tidsperiode (7/30/90/365 dager) - **AI drill-down**: per jobbtype og modellnivå (fast/smart/deep), tokens inn/ut - **Daglig tidsserie**: aktivitet per dag og ressurstype - Samlings- og ressurstype-filtre med live-oppdatering Backend: `maskinrommet/src/usage_overview.rs` → `GET /admin/usage?days=30&collection_id=` Frontend: `frontend/src/routes/admin/usage/+page.svelte` ### Brukersynlig forbruk (oppgave 15.9) To nye endepunkter i maskinrommet: - `GET /my/usage?days=30` — brukerens eget forbruk (filtrert på `triggered_by`) - `GET /query/node_usage?node_id=&days=30` — forbruk for én node (kun eier) Begge returnerer `by_type` (aggregert per ressurstype) og `daily` (tidsserie). `/my/usage` inkluderer også `graph` (noder/edges opprettet av brukeren). Tilgangssjekk for nodeforbruk: brukeren må ha opprettet noden (`created_by`) eller ha `owner`/`admin`-edge til den. Frontend: - `/profile` — profilside med grafstatistikk, totalkort per ressurstype, daglig tidsserie. Lenket fra brukernavnet i headeren. - `NodeUsage.svelte` — kollapserbart panel som viser nodeforbruk. Integrert i samlings-detaljsiden (`/collection/[id]`). Backend: `maskinrommet/src/user_usage.rs` Frontend: `frontend/src/routes/profile/+page.svelte`, `frontend/src/lib/components/NodeUsage.svelte` ### Caddy-oppsett JSON access logging er konfigurert i Caddyfile for `sidelinja.org` og `synops.no`. Logger skrives til `/var/log/caddy/access-*.log` med 100 MiB rotasjon og 7 filer beholdt.