Aggregert ressursforbruk-dashboard som spør mot resource_usage_log (oppgave 15.7). Tre visninger: totaler per ressurstype, per samling, og daglig tidsserie. AI drill-down viser forbruk per jobbtype og modellnivå (fast/smart/deep). Backend: GET /admin/usage med days- og collection_id-filtre. Frontend: /admin/usage med filterbare tabeller og fargekodede kort. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
238 lines
6.9 KiB
Markdown
238 lines
6.9 KiB
Markdown
# 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=<uuid>`
|
|
Frontend: `frontend/src/routes/admin/usage/+page.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.
|