English | 中文
Turn your AdGuard Home DNS logs into a real-time analytics dashboard — automatically.
AdGuard Home → Python ETL → PostgreSQL → Metabase
(DNS logs) (every 5 min) (14-day store) (3 dashboards)
Today's KPIs, hourly query trends, block rate, and latency (avg + P95).
Per-device query volume, block rankings, and top domains per device.
Top queried / blocked domains, late-night traffic, and per-domain device breakdown.
All dashboards support Date Range, Device IP, and Domain filters.
AdGuard Home's built-in UI only shows the last few hundred log entries with no trend analysis or cross-device comparison. This project answers questions like:
- Which device is making the most DNS queries?
- Which domains are blocked most often, and by whom?
- Is there suspicious late-night traffic?
- How has my block rate changed over time?
┌─────────────────────────────────────────────────────┐
│ Docker Compose │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │PostgreSQL│ │ ETL (Python)│ │ Metabase │ │
│ │ │←──│ │ │ port 3001 │ │
│ │ port 5432│ │ cron: 5 min │ │ │ │
│ └──────────┘ └──────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────┘
↑ ↑
stores data polls AGH API
/control/querylog
ETL pipeline:
- Polls AGH
/control/querylogevery 5 minutes (cursor-based, no duplicates) - Normalises records — extracts
root_domain(tldextract), maps AGH reasons →response_status, computes SHA-256 dedup fingerprint - Inserts into
dns_queriesraw table (ON CONFLICT DO NOTHING) - Hourly job rebuilds 5 aggregate tables (1h / 1d buckets) for fast dashboard queries
Design choices:
| Choice | Reason |
|---|---|
| Python ETL | AGH API needs cursor pagination and field normalisation |
| PostgreSQL | inet type, window functions, Metabase compatibility |
| Aggregate tables | Raw table grows to 100k+ rows; pre-aggregation keeps dashboards fast |
| Docker Compose | Single command deploy, auto-restart on reboot |
| Native SQL in Metabase | More flexible than GUI builder; supports template tag filters |
- Docker + Docker Compose
- AdGuard Home with API access enabled
- ~1 GB disk for PostgreSQL data (14-day retention)
- ~512 MB RAM for Metabase JVM
git clone https://github.com/Johnny-Kao/agh-analytics.git
cd agh-analytics/agh_pg_etl
cp .env.example .envEdit .env:
AGH_BASE_URL=http://YOUR_AGH_HOST/control # AGH control API base URL
AGH_USERNAME=your-username
AGH_PASSWORD=your-password
PG_PASSWORD=change-me-strong-password # Choose a strong passworddocker compose up -dOn first start:
- PostgreSQL schema is created automatically
- ETL backfills the last 7 days of AGH query logs
- Metabase is available at
http://YOUR_HOST:3001
- Open
http://YOUR_HOST:3001and complete the setup wizard - Add a PostgreSQL database connection:
- Host: your server IP (not
postgres— Metabase connects externally) - Port:
5432/ Database:agh_analytics/ User:agh - Password: value of
PG_PASSWORDin your.env
- Host: your server IP (not
- Create native SQL questions using the schema in
metabase_dashboard_brief.md
docker compose ps # all 3 services should be Up
docker logs agh-etl --tail 20 # check ETL ingest logs| Column | Type | Description |
|---|---|---|
event_time |
timestamptz | Query timestamp (UTC) |
client_key |
text | Device ID: agh:<id> / name:<n> / ip:<ip> |
client_name |
text | Device name from AGH (nullable) |
client_ip |
inet | Device IP |
root_domain |
text | Root domain, e.g. google.com |
qtype |
text | Query type: A, AAAA, PTR… |
response_status |
text | allowed / blocked / cached / rewrite |
elapsed_ms |
numeric | Query latency in ms |
event_fingerprint |
text | SHA-256 dedup key (unique) |
| Table | Description |
|---|---|
agg_overview |
Global stats per time bucket |
agg_client_usage |
Per-device stats |
agg_client_domain_usage |
Device × domain cross |
agg_domain_usage |
Per-domain stats |
agg_domain_client_usage |
Domain × device cross |
All have bucket_size = '1h' (last 3 days) or '1d' (last 14 days).
Full schema → metabase_dashboard_brief.md
| Data | Retention |
|---|---|
dns_queries raw |
14 days |
| Aggregate 1h buckets | 3 days |
| Aggregate 1d buckets | 14 days |
agh_pg_etl/
├── app/
│ ├── main.py # CLI: ingest / backfill / aggregate / init-db
│ ├── agh_client.py # AGH HTTP API client (cursor pagination)
│ ├── transform.py # AGH JSON → DnsQueryRow (pydantic)
│ ├── loader.py # PostgreSQL insert + ETL state
│ └── aggregator.py # Aggregate rebuild + retention cleanup
├── sql/
│ ├── 001_init.sql # Core tables
│ ├── 002_indexes.sql # Performance indexes
│ └── 003_aggregates.sql
├── Dockerfile
├── docker-compose.yml
├── entrypoint.sh
└── .env.example
docs/ # Dashboard screenshots
metabase_dashboard_brief.md
MIT
English | 中文
將 AdGuard Home 的 DNS 查詢紀錄,自動化轉化為即時分析儀表板。
AdGuard Home → Python ETL → PostgreSQL → Metabase
(DNS 紀錄) (每 5 分鐘) (保留 14 天) (3 個儀表板)
AdGuard Home 內建 UI 只能看最近幾百筆紀錄,沒有趨勢分析、沒有跨裝置比較。這個專案幫你回答:
- 哪台設備查詢 DNS 最頻繁?
- 哪些域名最常被封鎖?被哪些設備觸發?
- 深夜有沒有可疑流量?
- 封鎖率的趨勢怎麼走?
┌─────────────────────────────────────────────────────┐
│ Docker Compose │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │PostgreSQL│ │ ETL (Python)│ │ Metabase │ │
│ │ │←──│ │ │ port 3001 │ │
│ │ port 5432│ │ cron 5 分鐘 │ │ │ │
│ └──────────┘ └──────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────┘
ETL 流程:
- 每 5 分鐘 poll AGH
/control/querylog(cursor 分頁,自動 dedup) - 正規化資料:用 tldextract 提取
root_domain、將 AGH reason 對應到response_status、計算 SHA-256 fingerprint - 寫入
dns_queriesraw 表(ON CONFLICT DO NOTHING) - 每小時重建 5 張 aggregate 表(1h / 1d bucket),讓 Dashboard 查詢秒回應
設計決策:
| 選擇 | 原因 |
|---|---|
| Python ETL | AGH API 需要 cursor 分頁和欄位正規化邏輯 |
| PostgreSQL | 支援 inet 型別、window function、與 Metabase 相容性好 |
| Aggregate 表 | raw 表累積到 10 萬筆以上;預計算讓儀表板保持快速 |
| Docker Compose | 一個指令部署全套,重開機自動恢復 |
| Metabase Native SQL | 比 GUI builder 靈活,支援 template tag filter |
git clone https://github.com/Johnny-Kao/agh-analytics.git
cd agh-analytics/agh_pg_etl
cp .env.example .env編輯 .env,填入你的 AGH 位址、帳密和 DB 密碼:
AGH_BASE_URL=http://你的AGH主機/control
AGH_USERNAME=你的帳號
AGH_PASSWORD=你的密碼
PG_PASSWORD=設定一個強密碼docker compose up -d首次啟動會自動:
- 建立 PostgreSQL schema
- Backfill 過去 7 天的 AGH query log
- 在 port 3001 提供 Metabase
- 開啟
http://你的主機IP:3001,完成設定精靈 - 新增 PostgreSQL 資料庫連線:
- Host:填伺服器 IP(不是
postgres,Metabase 從自身視角連線) - Port:
5432/ Database:agh_analytics/ User:agh - Password:
.env裡的PG_PASSWORD
- Host:填伺服器 IP(不是
- 用
metabase_dashboard_brief.md裡的 SQL 建立 Question 和 Dashboard
docker compose ps # 3 個服務都應顯示 Up
docker logs agh-etl --tail 20 # 查看 ETL 同步紀錄| 資料 | 保留期限 |
|---|---|
dns_queries raw 表 |
14 天 |
| Aggregate 1h bucket | 3 天 |
| Aggregate 1d bucket | 14 天 |
所有儀表板都支援 日期範圍、設備 IP、Domain 篩選器。
今日 KPI(總查詢/封鎖/快取)、每小時趨勢、封鎖率、DNS 延遲。
各設備查詢量/封鎖量排行、設備查詢趨勢、單設備 Top Domain。
Top 查詢/封鎖域名、深夜高頻域名、域名被哪些設備查詢。
MIT


