| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Self-hosted Grafana + Prometheus monitoring for the Raspberry Pi, covering:
Runs alongside (not replacing) both apps' existing Streamlit dashboards.
flowchart TD
subgraph HOST["🖥️ Raspberry Pi Host"]
VCGEN["vcgencmd get_throttled"]
CRON["⏱️ cron (1m)\npi-throttle-exporter.sh"]
TEXTFILE["📄 node-exporter-textfile/\npi_throttle.prom"]
GATEWAY["🔒 pi-gateway Caddy\n[network_mode: host]\nhttp://grafana.local → :3000\n(routing only, no Basic Auth)"]
AVAHI["avahi-alias-grafana.service\nmDNS: grafana.local"]
end
subgraph DOCKER["🐳 Docker Compose Stack [monitoring-net bridge]"]
subgraph GRAF["monitoring-grafana [127.0.0.1:3000]"]
GF["📊 Grafana OSS 11.4\n+ marcusolsson-treemap-panel\nNative login only\n16 dashboards / 3 folders\nplaylists · starred dashboards"]
end
subgraph PROM["monitoring-prometheus"]
PR["📈 Prometheus\nscrapes: self · node-exporter · cadvisor\n30d retention"]
end
subgraph NODEEXP["monitoring-node-exporter [pid: host]"]
NE["Host metrics\nCPU/mem/disk/net/PSI pressure\n+ textfile collector"]
end
subgraph CADV["monitoring-cadvisor [privileged]"]
CA["Per-container CPU/mem/net/OOM\ncontainerd-snapshotter aware\n(cadvisor:latest)"]
end
end
subgraph EXTERNAL["☁️ External"]
PPI_DB[("🗄️ PortfolioPi Supabase\ngrafana_reader role\nBYPASSRLS")]
OW_DB[("🗄️ OverwatcherPI Supabase\ngrafana_reader role\nBYPASSRLS")]
LAN["Any device on the LAN"]
end
%% Throttle exporter chain
CRON -->|"every 60s"| VCGEN
VCGEN --> CRON
CRON -->|"decoded bitmask"| TEXTFILE
TEXTFILE -->|"--collector.textfile.directory"| NE
%% Scrape chain
NE -->|":9100/metrics"| PR
CA -->|":8080/metrics"| PR
PR -->|"promQL"| GF
%% Postgres datasources
GF -->|"read-only SQL"| PPI_DB
GF -->|"read-only SQL"| OW_DB
%% Access chain
LAN -->|"http://grafana.local"| AVAHI
AVAHI -->|"resolves to 192.168.1.35"| LAN
LAN --> GATEWAY
GATEWAY -->|"reverse_proxy 127.0.0.1:3000"| GF
classDef container fill:#1e3a5f,stroke:#4a9eff,color:#fff
classDef db fill:#2d4a1e,stroke:#6abf4b,color:#fff
classDef host fill:#3a1f1f,stroke:#cc4444,color:#fff
classDef external fill:#2d2040,stroke:#9b6eff,color:#fff
class GRAF,PROM,NODEEXP,CADV container
class PPI_DB,OW_DB db
class HOST host
class EXTERNAL external
Grafana itself is bound to 127.0.0.1:3000 and is never reachable directly from the LAN — only pi-gateway's Caddy (host networking) can reach it, and only after resolving grafana.local via the dedicated avahi-alias-grafana.service mDNS publisher. Prometheus, node-exporter, and cAdvisor are never published to the host at all — reachable only via Docker service-name DNS on monitoring-net.
Reachable at http://grafana.local via the existing pi-gateway Caddy (added as a routing-only entry — no Basic Auth layer; Grafana's own native login is the sole credential check). Grafana itself is bound to 127.0.0.1:3000 on the Pi and is never reachable directly from the LAN.
Dashboards are authored live in the Grafana UI (folders are provisioned read-only via allowUiUpdates: false, so UI edits revert on the next 30s poll unless exported). Workflow: edit in the UI → Dashboard settings → JSON Model → copy → paste into grafana/provisioning/dashboards-json/<folder>/NN-name.json (strip the numeric id field, keep uid stable) → git commit.
Infrastructure stood up 2026-07-11, dashboards authored and fully verified 2026-07-12:
Bugs found and fixed during this pass:
See sql/create_grafana_reader_roles.sql for the read-only role DDL (including the required RLS bypass) applied to both Supabase projects.
scripts/pi-throttle-exporter.sh decodes vcgencmd get_throttled's bitmask into a Prometheus textfile metric (pi_throttle_status{condition=...,when="now"|"occurred"}) every 60s via a host cron job. This is Raspberry-Pi-specific under-voltage/frequency-cap/thermal-limit signal that node_exporter's generic node_cooling_device_* collector does not cover. Install with scripts/install-throttle-cron.sh (run once, directly on the Pi — vcgencmd needs host access, so this does not run in a container). node-exporter is configured with --collector.textfile.directory=/textfile-collector, bind-mounted from ./node-exporter-textfile/ on the host.
Second pass focused on filling gaps found by cross-referencing every dashboard's queries against the full Postgres schemas and node_exporter's metric catalog, plus pulling in one new panel plugin. Every new/changed panel was verified the same way as v1: executed directly against live data via /api/ds/query / /api/v1/query, not just JSON-validated.
Pi Infrastructure — System Health grew from 13 to 40 panels. Added: PSI pressure (CPU/memory/IO stall time), CPU depth (active governor, frequency min/max bounds, context switches, interrupts, process run/block queue), memory depth (OOM kill counter, major page faults), disk depth (I/O utilization %, open file descriptors), network depth (softnet drops, link-flap counter, conntrack table usage), clock drift (NTP offset), entropy pool, and the new Pi throttle-status panels. All of this was data node-exporter was already scraping every 30s but nothing had ever plotted — zero new exporters needed except the throttle script above.
Docker Container Health: added per-container OOM-kill visibility (container_oom_events_total). A per-container disk-usage panel was also attempted (container_fs_usage_bytes) but confirmed via a live query to only report root-cgroup-level usage under this host's containerd-snapshotter setup — no per-container label is ever populated for it — so it was left out rather than shipped showing misleading data.
PortfolioPi: "Current Allocation by Stock" upgraded from piechart to the new treemap plugin. New "Current Allocation by Sector" treemap (stock_metadata.sector — unused table, currently empty so it shows one "Unknown" bucket today, ready to break down for real the moment that table is populated). New Backfill Requests and Ticker Mapping Health tables (both previously-unused tables). Portfolio Value Over Time now plots total_pnl directly instead of leaving that column unused. Portfolio Analyser gained a per-stock trend-strength table (stage_history.sma_150/slope).
OverwatcherPI: Security Posture gained a Cooldown Detail row showing which metric/vendor/IP is on cooldown (previously only ever counted). Live Overview's Recent Jobs table now shows target, requester, duration, and result summary (4 more of the jobs table's columns). Device History's Bluetooth table gained RSSI min/max/avg computed from the previously-unused rssi_history JSON column — a native Table sparkline was considered but ruled out after confirming live that Postgres arrays come back as plain strings through Grafana's datasource, not a true array field. New reference-data panels for oui_mappings (shown as a loaded-count, not a ~40k-row dump), deferred_scans, and monitored_hosts.
Not pursued, on purpose: a real geo-enriched Threat Map. Grafana's Geomap panel is core (no plugin needed), but dns_queries.src_ip is the querying device's LAN IP — not geolocatable. A genuine version needs to resolve query_name to an external IP and geo-enrich that, which is new app-level data collection (a GeoIP lookup step), not a Grafana change. Flagged as a distinct future project rather than built against the wrong IP.
| Back | FazBrowse Home | New Git URL |