Skip to content

Metrics Collection#

DUMB includes a comprehensive metrics collection system that monitors system resources and provides both real-time updates and historical data for analysis.


Overview#

The metrics system provides:

  • Real-time monitoring - Live CPU, memory, disk, and network stats
  • Historical tracking - Time-series data storage for trend analysis
  • Per-process metrics - Resource usage by individual service
  • Optional database health - Per-service SQL and persistent-store pressure indicators
  • WebSocket streaming - Push updates to connected clients
  • cgroup awareness - Accurate reporting in containerized environments

Metrics overview


Collected metrics#

System metrics#

Category Metrics
CPU Usage %, core count, load averages (⅕/15 min)
Memory Total, used, available, percentage
Swap Total, used, percentage
Disk Total, used, free, percentage
Inodes Total, used, free, percentage
Network Bytes/packets sent and received
System Boot time, uptime

Per-process metrics#

Metric Description
PID Process identifier
CPU % Process CPU utilization
Memory % Process memory utilization
Memory RSS Resident set size in bytes

Configuration#

Metrics are configured in dumb_config.json:

"dumb": {
  "metrics": {
    "system_scope": "auto",
    "history_enabled": true,
    "history_interval_sec": 5,
    "history_retention_days": 7,
    "history_max_file_mb": 50,
    "history_max_total_mb": 100,
    "history_dir": "/config/metrics",
    "storage": {
      "provider": "sqlite",
      "sqlite_path": "/config/metrics/metrics.sqlite",
      "migrate_jsonl": true,
      "postgresql": {
        "database": "dumb_metrics",
        "schema": "public",
        "local_retention_days": 7,
        "retry_interval_sec": 60
      }
    },
    "database_health": {
      "enabled": false,
      "interval_sec": 60,
      "log_tail_bytes": 262144,
      "services": {
        "nzbdav": {
          "enabled": true,
          "mode": "standard",
          "ignore_network_storage": false
        },
        "sonarr:Default": {
          "enabled": true,
          "mode": "enhanced"
        }
      }
    }
  }
}

Configuration options#

Option Default Description
system_scope auto Choose system scope for metrics (auto, host, container)
history_enabled true Store historical data
history_interval_sec 5 Seconds between samples
history_retention_days 7 Days to keep history
history_max_file_mb 50 Legacy JSONL rotation limit retained for compatibility with older DUMB releases
history_max_total_mb 100 Maximum compressed payload size retained in local SQLite
history_dir /config/metrics Legacy JSONL source directory used during migration
storage.provider sqlite Primary history backend: sqlite or postgresql
storage.sqlite_path /config/metrics/metrics.sqlite Dedicated local metrics database; also the PostgreSQL continuity store
storage.migrate_jsonl true Import existing rotating JSONL history once without deleting source files
storage.postgresql.database dumb_metrics Dedicated DUMB-managed PostgreSQL database
storage.postgresql.schema public PostgreSQL schema used for metrics tables
storage.postgresql.local_retention_days 7 Local history retained for outage fallback and replay
storage.postgresql.retry_interval_sec 60 Minimum PostgreSQL retry interval after a failure
database_health.enabled false Enable the database-health collector; individual services must still opt in
database_health.interval_sec 60 Seconds between database collections (15-3600)
database_health.log_tail_bytes 262144 Maximum new/recent service-log bytes examined per collection
database_health.services {} Per-service/instance settings keyed by the ID reported in /api/metrics

Database Health Monitoring#

Database Health Monitoring is disabled by default and enabled independently for each service. It recognizes every DUMB-managed service with a confirmed database or application-owned persistent store:

Provider class Services and observed stores
SQLite NzbDAV, Bazarr, CLI Debrid, CLI Battery, Emby, Jellyfin, Profilarr, Tautulli, and Plex; Sonarr, Radarr, Lidarr, Prowlarr, and Whisparr when PostgreSQL is disabled
SQLite or PostgreSQL AltMount, Pulsarr, and Seerr; DUMB detects the provider from their application config/environment
PostgreSQL DUMB PostgreSQL, pgAdmin, Riven Backend, Zilean, and Traefik Proxy Admin; Arr instances when postgres_enabled=true
Custom persistent stores Decypharr append-only logs, Phalanx DB Hyperbee/Corestore data, and the Zurg state directory

Services without a confirmed database or durable application store are not listed merely because they use files or caches. Multi-instance services receive one Database Health entry per enabled instance.

Two modes are available:

Mode Continuous observations
Standard Database/store/WAL/SHM size, filesystem type and placement, byte capacity/free space, inode usage/free inodes, read-only state, and service-log lock/busy/timeout/I/O signals
Enhanced Standard signals plus bounded, read-only SQLite metadata or PostgreSQL statistics queries when the detected provider supports them

Each service can set ignore_network_storage: true when its storage placement is intentional. DUMB continues to report the detected filesystem, but excludes that network mount from the service's pressure score and recommendation. Other evidence—including WAL growth, lock/busy/timeout errors, probe latency, deadlocks, and long transactions—continues to affect the result.

Filesystem capacity and inode observations are grouped by mount, so a service with multiple databases on one filesystem is not penalized repeatedly. DUMB also reports local DUMB-managed PostgreSQL storage; an external PostgreSQL host's filesystem cannot be observed from inside DUMB. The network-storage override affects only the network-placement penalty—low free space, inode exhaustion, and read-only state remain active indicators.

Automatic collection never runs VACUUM, ANALYZE, a WAL checkpoint, an integrity check, a repair, a migration, or an application data query. Plex remains passive even if Enhanced is selected because Plex uses a customized SQLite build and its live library database is treated conservatively. Decypharr, Phalanx DB, and Zurg also remain passive because their observed stores are not confirmed SQL databases. Directory-backed custom stores use a bounded size/file-count scan rather than opening application data.

What Zurg's database means here

The public Zurg build used by DUMB calls its in-memory torrent catalog a database, but it does not expose that catalog as SQLite or PostgreSQL. DUMB labels Zurg as zurg-state and passively samples the instance's data/ directory, which includes fixers.json in the public build and can contain other version-specific state. It cannot query the live in-memory torrent catalog or infer torrent-catalog performance from those files.

Interpret the result as evidence, not a benchmark

DUMB observes databases from outside the managed application. It cannot profile individual application queries, prove root cause, predict a PostgreSQL percentage improvement, guarantee that short-lived events were captured, repair a database, replace backups, or replace application-native diagnostics. A healthy result means DUMB observed no configured external pressure indicators during the collection window; it is not an integrity guarantee.

The pressure score considers recent database-related log errors, network-filesystem placement unless explicitly ignored, filesystem fullness, inode pressure, read-only mounts, WAL growth, read-only probe latency, PostgreSQL lock waiters/deadlocks, and long-running transactions. Filesystem fullness begins contributing at 90%, inode usage begins contributing at 90%, and increasingly severe thresholds contribute more evidence. If SQLite is on NFS/SMB or another network filesystem, local storage is normally preferred; use the per-service override only when you intentionally want to assess the remaining signals independently.

Pressure classifications use these score bands:

Classification Score Meaning
Healthy 0-19 No configured pressure indicator, or only low-weight evidence, was observed
Moderate 20-44 Evidence deserves continued observation and workload correlation
High 45-69 Strong evidence warrants investigation
Critical 70-100 Multiple or severe indicators require prompt investigation

observing, collecting, unavailable, and disabled describe collection state rather than a scored pressure band.

Compact database-health samples are included in normal metrics history without storing database paths or storage-source details.


Real-time streaming#

Metrics are streamed via WebSocket at /ws/metrics:

const ws = new WebSocket('ws://localhost:8000/ws/metrics?interval=2');

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'snapshot') {
    updateDashboard(data.data);
  }
};

Query parameters#

Parameter Default Description
interval 2 Update frequency (0.5-10 seconds)
history false Include historical data on connect
bootstrap true Send initial snapshot immediately
token - JWT token (if auth enabled)

Historical data#

Metrics history storage#

DUMB stores each complete snapshot as a timestamp-indexed, compressed payload. Metrics history is independent from the notification delivery database.

Backend Behavior
SQLite (default) Writes /config/metrics/metrics.sqlite. It is self-contained, survives container recreation through /config, and requires no managed service.
PostgreSQL (optional) Uses the configured DUMB PostgreSQL credentials and a dedicated dumb_metrics database by default. Applying the selection enables, provisions, and starts DUMB-managed PostgreSQL in place.

PostgreSQL is not a secondary backup in this design. When configured and healthy, it is the active history query and retention backend. DUMB first commits each sample to a bounded local SQLite continuity buffer, then copies missing samples to PostgreSQL in timestamp order. This write-ahead step prevents a PostgreSQL restart or outage from creating a gap. If PostgreSQL is stopped, starting, upgrading, or unreachable, history reads and writes continue through SQLite. After connectivity returns, DUMB performs an idempotent reconciliation of the retained SQLite window—including samples older than PostgreSQL's newest row—before PostgreSQL resumes serving history reads.

Selecting PostgreSQL from Metrics → Settings does not require a DUMB restart on backends that advertise metrics_history_hot_activation. Apply saves the Metrics configuration, enables PostgreSQL when no other service uses it, registers and creates the configured Metrics database, starts or reuses the managed PostgreSQL process, replays retained SQLite samples, and promotes PostgreSQL only after synchronization succeeds. The operation is idempotent, so Apply can safely retry a failed activation. If provisioning or synchronization fails, the API and frontend remain available and SQLite continues serving history.

Older DUMB backends without that capability retain the previous restart-based provisioning behavior; dmbdb identifies this case in the panel instead of attempting the unsupported endpoint.

Notifications do not depend on PostgreSQL

Notification delivery uses /config/notifications/notifications.sqlite. A PostgreSQL metrics outage therefore cannot prevent DUMB from queuing a PostgreSQL failure notification.

Existing JSONL history#

Earlier DUMB releases wrote rotating metrics-YYYYMMDD-NNN.jsonl files under /config/metrics. On first use of the database-backed history store, DUMB imports those files idempotently into SQLite. It records completion only after every line decodes and every batch writes successfully, and it leaves every JSONL file untouched for rollback. If a record is malformed or a batch fails, Metrics reports the imported and skipped counts, preserves the incomplete state, and retries on a later manual import or DUMB startup. Use Metrics → Settings → Import JSONL now to force a fresh rescan without duplicating timestamps, including after temporarily running an older DUMB release that created additional JSONL files. In PostgreSQL mode, a rescan temporarily returns history reads to SQLite and fully reconciles the retained window before PostgreSQL is promoted again, so an older imported sample is not skipped merely because PostgreSQL already contains newer rows.

The configured history_max_file_mb remains in the schema so the same runtime configuration can still be read by older releases, but database-backed DUMB no longer creates new JSONL files.

Storage size and compression#

Database storage normally uses less space than the equivalent JSONL snapshots because DUMB compresses each repeated-key JSON payload before writing it. The Metrics settings panel reports raw JSON bytes, compressed payload bytes, database file/relation size, and the observed compression ratio.

The filesystem database size will not exactly equal compressed payload size:

  • SQLite includes pages, indexes, free pages, and -wal/-shm files.
  • PostgreSQL includes row, page, index, visibility, and WAL overhead.
  • Recently deleted rows/pages may remain allocated for reuse.

For that reason, SQLite usually provides the clearest space reduction for a single DUMB container. PostgreSQL is primarily useful for longer retention, external queries, stronger concurrency, and centralized operations—not as a guaranteed compression improvement over SQLite.

Data structure#

Each history entry contains:

{
  "timestamp": "2025-01-15T10:30:00Z",
  "cpu": {
    "percent": 45.2,
    "count": 8,
    "load_avg": [1.5, 1.2, 0.9]
  },
  "memory": {
    "total": 17179869184,
    "available": 8589934592,
    "percent": 50.0
  },
  "disk": {
    "total": 500107862016,
    "used": 250053931008,
    "percent": 50.0
  },
  "network": {
    "bytes_sent": 1073741824,
    "bytes_recv": 2147483648
  }
}

Querying history#

Retrieve historical data via API:

# Get history newer than a Unix timestamp
curl "http://localhost:8000/api/metrics/history?since=1784300000&limit=5000"

# Get compact chart series with server-side buckets
curl "http://localhost:8000/api/metrics/history_series?since=1784300000&bucket_seconds=300&max_points=600"

# Inspect configured/active storage and compression
curl "http://localhost:8000/api/metrics/history/storage?probe_postgresql=true"

cgroup awareness#

DUMB automatically detects containerized environments and reports appropriate metrics:

Container mode#

When running in Docker/Kubernetes with resource limits:

  • CPU - Reports usage relative to container limit
  • Memory - Reports container memory limit, not host
  • Disk - Reports container filesystem stats
  • Inodes - Reports inode capacity and pressure for the container root filesystem

Host mode#

When running without cgroup limits:

  • Reports full host system resources

Detection

cgroup detection is automatic. DUMB checks for cgroup v1 and v2 interfaces.


API endpoints#

Current metrics#

GET /api/metrics

Returns the current metrics snapshot.

Database health#

GET /api/metrics/database-health
GET /api/metrics/database-health?process_name=NzbDAV&refresh=true

The optional process_name filter returns one service. refresh=true invalidates cached database probes before collecting; normal /api/metrics and WebSocket snapshots reuse the configured slower database interval.

Historical metrics#

GET /api/metrics/history
GET /api/metrics/history_series
GET /api/metrics/history/storage
POST /api/metrics/history/migrate

Query parameters:

Parameter Description
since Earliest Unix timestamp to return
full Skip the default six-hour lower bound when true
limit Maximum snapshots read before truncation
bucket_seconds Aggregation bucket size for history_series
max_points Maximum chart points returned by history_series

GET /api/metrics/history/storage reports the configured and active providers, fallback state, compression/storage sizes, sample ranges, last PostgreSQL error, and legacy migration state. Pass probe_postgresql=true to bypass the reconnect backoff for an operator-requested test. POST /api/metrics/history/migrate reruns the idempotent JSONL import; pass force=true to rescan after a completed migration.


Frontend integration#

Metrics page#

The frontend Metrics page displays:

  • Real-time gauges for CPU, memory, disk, and inode pressure
  • Historical line charts, including inode usage
  • Per-process resource table
  • System information panel
  • Database Health immediately above System, with expandable per-service details

Dashboard alerts#

Configure thresholds in Settings to show alerts:

Resource Default Threshold
CPU 85%
Memory 85%
Disk 90%
Inodes 90%
Database Health Disabled by default; optional Moderate, High, or Critical minimum

Alerts appear as banners when thresholds are exceeded. Database Health alert inclusion and its minimum pressure level are browser-local preferences, matching the existing CPU, memory, disk, and inode alert controls; they do not change collection or scoring.

Backend outbound notifications have independent persistent thresholds for CPU, memory, disk, inode, and Database Health conditions. See Notifications. Enabling a backend notification threshold does not alter browser-local alert preferences.


Data retention#

Automatic cleanup#

Old metrics samples are automatically removed based on:

  • history_retention_days - Primary-backend samples older than this are deleted
  • history_max_total_mb - Oldest local SQLite payloads are deleted when exceeded
  • storage.postgresql.local_retention_days - SQLite fallback/replay window while PostgreSQL is primary

Retention and size maintenance runs periodically rather than for every sample, so a database can temporarily exceed a newly selected limit until the next maintenance pass. Changing Metrics storage settings schedules maintenance for the next sample.

Manual cleanup#

Do not delete a live database while DUMB is writing it. Stop DUMB first, then back up or remove the relevant database if a full reset is intentional. The old JSONL files can be removed after you have validated the imported sample range and retained any rollback copy you need:

rm /config/metrics/metrics-*.jsonl

Performance considerations#

Collection overhead#

Metrics collection has minimal performance impact:

  • CPU sampling uses /proc/stat
  • Memory from /proc/meminfo
  • Non-blocking I/O operations

Storage requirements#

Estimate storage needs:

Interval Daily Size (approx)
5 seconds ~5 MB
10 seconds ~2.5 MB
30 seconds ~800 KB

Troubleshooting#

Metrics not updating#

  1. Check WebSocket connection status
  2. Verify metrics.enabled is true
  3. Check for JavaScript errors in browser console

High storage usage#

  1. Reduce history_retention_days
  2. Increase history_interval
  3. Lower history_max_total_mb
  4. Compare compressed payload and database allocation in Metrics → Settings → History Storage
  5. If PostgreSQL is active, review its relation size and normal PostgreSQL maintenance behavior

Incorrect resource values#

  1. Verify cgroup detection is working
  2. Check container resource limits
  3. Restart DUMB to refresh detection