diff --git a/adguard-exporter b/adguard-exporter deleted file mode 160000 index 1113358..0000000 --- a/adguard-exporter +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1113358cac51354eaf1280d940a50e344a4e045b diff --git a/adguard-exporter/.env.example b/adguard-exporter/.env.example new file mode 100644 index 0000000..3b4b13a --- /dev/null +++ b/adguard-exporter/.env.example @@ -0,0 +1,19 @@ +# Contoh konfigurasi, silakan sesuaikan +#Fill up your adguard url, if didn't works try docker ips (172.x.x.x) (required) +ADGUARD_HOST=http://172.x.x.x:20271 + +#Your AdGuard username and password for authentication that you setup during the init (required) +ADGUARD_USER= +ADGUARD_PASS= + +#Your exporter port (optional, default 9617) +EXPORTER_PORT=9200 + +#Scrape Interval by prometheus (optional, default 15s) +SCRAPE_INTERVAL=15s + +#Fill up your LogLevel you want it to, (DEBUG,WARN,ERROR,INFO) +LOG_LEVEL=DEBUG + +#GEOIP_DB File download => wget https://raw.githubusercontent.com/P3TERX/GeoLite.mmdb/download/GeoLite2-City.mmdb +GEOIP_DB=/path/to/your/GeoLite2-City.mmdb diff --git a/adguard-exporter/.github/renovate.json b/adguard-exporter/.github/renovate.json new file mode 100644 index 0000000..01c9f06 --- /dev/null +++ b/adguard-exporter/.github/renovate.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ], + "labels": [ + "renovate::dependencies", + "{{#if category}}renovate::{{category}}{{/if}}", + "{{#if updateType}}renovate::{{updateType}}{{/if}}", + "{{#if datasource}}renovate::{{datasource}}{{/if}}", + "{{#if manager}}renovate::{{manager}}{{/if}}", + "{{#if vulnerabilitySeverity}}renovate::{{vulnerabilitySeverity}}{{/if}}", + "renovate::{{#if isVulnerabilityAlert}}vulnerability{{else}}not-vulnerability{{/if}}" + ], + "pinDigests": false, + "enabled": true, + "separateMajorMinor": true, + "automerge": true, + "packageRules": [ + { + "matchManagers": [ + "gomod" + ], + "postUpdateOptions": [ + "gomodTidy" + ] + } + ] +} diff --git a/adguard-exporter/.github/workflows/go-ci.yml b/adguard-exporter/.github/workflows/go-ci.yml new file mode 100644 index 0000000..36ad5c7 --- /dev/null +++ b/adguard-exporter/.github/workflows/go-ci.yml @@ -0,0 +1,44 @@ +name: Go CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + workflow_dispatch: + +jobs: + test: + name: Run Tests & Linter + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: "1.26" + + # - name: Cache Go modules + # uses: actions/cache@v3 + # with: + # path: | + # ~/.cache/go-build + # ~/go/pkg/mod + # key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + # restore-keys: | + # ${{ runner.os }}-go- + + - name: Install dependencies + run: go mod tidy + + - name: Run unit tests + run: go test -v ./... + + - name: Run linter (golangci-lint) + uses: golangci/golangci-lint-action@v9 + with: + version: latest + args: --timeout=3m diff --git a/adguard-exporter/.github/workflows/release.yml b/adguard-exporter/.github/workflows/release.yml new file mode 100644 index 0000000..6b0b805 --- /dev/null +++ b/adguard-exporter/.github/workflows/release.yml @@ -0,0 +1,32 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + packages: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v7 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: 1.26 + + - name: Login to GitHub Container Registry + run: echo "${{ secrets.CR_PAT }}" | docker login ghcr.io -u znand-dev --password-stdin + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v7 + with: + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/adguard-exporter/.gitignore b/adguard-exporter/.gitignore new file mode 100644 index 0000000..10ab642 --- /dev/null +++ b/adguard-exporter/.gitignore @@ -0,0 +1,13 @@ +# Binaries +adguardexporter +dist/ +# Env file +.env + +# OS / Editor files +.DS_Store +*.swp + +#db +GeoLite2-City.mmdb +geolite2-db/GeoLite2-City.mmdb diff --git a/adguard-exporter/.goreleaser.yml b/adguard-exporter/.goreleaser.yml new file mode 100644 index 0000000..8bb91e7 --- /dev/null +++ b/adguard-exporter/.goreleaser.yml @@ -0,0 +1,79 @@ +project_name: adguard-exporter + +before: + hooks: + - go mod tidy + +builds: + - id: universal + main: main.go + binary: adguard-exporter + env: [CGO_ENABLED=0] + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 + - arm + +archives: + - format: tar.gz + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + files: + - LICENSE + - README.md + +checksum: + name_template: 'checksums.txt' + +dockers: + - id: docker-amd64 + ids: [universal] + goos: linux + goarch: amd64 + use: buildx + dockerfile: Dockerfile + build_flag_templates: + - "--platform=linux/amd64" + image_templates: + - ghcr.io/znand-dev/adguardexporter:{{ .Tag }}-amd64 + - ghcr.io/znand-dev/adguardexporter:latest-amd64 + + - id: docker-arm64 + ids: [universal] + goos: linux + goarch: arm64 + use: buildx + dockerfile: Dockerfile + build_flag_templates: + - "--platform=linux/arm64" + image_templates: + - ghcr.io/znand-dev/adguardexporter:{{ .Tag }}-arm64v8 + - ghcr.io/znand-dev/adguardexporter:latest-arm64v8 + + - id: docker-arm + ids: [universal] + goos: linux + goarch: arm + use: buildx + dockerfile: Dockerfile + build_flag_templates: + - "--platform=linux/arm/v6" + image_templates: + - ghcr.io/znand-dev/adguardexporter:{{ .Tag }}-armv6 + - ghcr.io/znand-dev/adguardexporter:latest-armv6 + +docker_manifests: + - name_template: ghcr.io/znand-dev/adguardexporter:{{ .Tag }} + image_templates: + - ghcr.io/znand-dev/adguardexporter:{{ .Tag }}-amd64 + - ghcr.io/znand-dev/adguardexporter:{{ .Tag }}-arm64v8 + - ghcr.io/znand-dev/adguardexporter:{{ .Tag }}-armv6 + + - name_template: ghcr.io/znand-dev/adguardexporter:latest + image_templates: + - ghcr.io/znand-dev/adguardexporter:latest-amd64 + - ghcr.io/znand-dev/adguardexporter:latest-arm64v8 + - ghcr.io/znand-dev/adguardexporter:latest-armv6 diff --git a/adguard-exporter/CHANGELOG.md b/adguard-exporter/CHANGELOG.md new file mode 100644 index 0000000..698c93e --- /dev/null +++ b/adguard-exporter/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +## v1.1.0 - 2025-06-18 +### ✨ Added +- `adguard_replaced_parental`: total queries replaced due to parental control +- `adguard_safe_search`: total queries forced into Safe Search (YouTube, Google, Bing) +- `adguard_safe_browsing`: total Safe Browsing protections triggered +- `adguard_top_upstreams`: top DNS upstreams based on query count +- `adguard_top_clients`: top client IPs querying AdGuard +- `adguard_top_queried_domains`: most frequently queried domains +- `adguard_upstream_avg_response_time_seconds`: average DNS upstream response time + +### πŸ›  Fixed +- Fixed: missing `upstream` and `client` labels due to empty maps or parsing errors +- Fixed: more reliable parsing of nested JSON from `/control/stats` and `/control/status` +- Fixed: exporter now exposes partial metrics even if part of the JSON fails + +### πŸ”„ Refactored +- Cleaned up metric and label names to follow Prometheus naming conventions +- Fixed Dockerfile lint warning: unified casing of `FROM ... AS` keyword for best practices diff --git a/adguard-exporter/Dockerfile b/adguard-exporter/Dockerfile new file mode 100644 index 0000000..467eec7 --- /dev/null +++ b/adguard-exporter/Dockerfile @@ -0,0 +1,31 @@ +FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS build +WORKDIR /app + +RUN apk add --no-cache ca-certificates git + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +ARG TARGETOS +ARG TARGETARCH + +RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \ + go build -ldflags="-s -w" -o adguard-exporter . + +FROM alpine:3.24 AS certs +RUN apk add --no-cache ca-certificates + +FROM scratch +WORKDIR / + +COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=build /app/adguard-exporter /adguard-exporter +COPY --from=build /app/GeoLite2-City.mmdb /GeoLite2-City.mmdb + +USER 65532:65532 + +EXPOSE 9200 + +ENTRYPOINT ["/adguard-exporter"] diff --git a/adguard-exporter/LICENSE b/adguard-exporter/LICENSE new file mode 100644 index 0000000..c9eab41 --- /dev/null +++ b/adguard-exporter/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Januar Ismunandar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/adguard-exporter/README.md b/adguard-exporter/README.md new file mode 100644 index 0000000..e4b4706 --- /dev/null +++ b/adguard-exporter/README.md @@ -0,0 +1,307 @@ +# AdGuard Exporter for Prometheus + +A lightweight Prometheus exporter written in Go that exposes detailed metrics from your AdGuard Home instance β€” including DNS statistics, blocked domains, upstreams, and client data. + +--- + +## Features + +| Category | Capabilities | +|----------|--------------| +| API Integration | β€’ Authenticated access to AdGuard Home API
β€’ Supports `/control/status`, `/control/stats`, and `/control/querylog` endpoints | +| DNS Analytics | β€’ Total DNS queries metrics
β€’ Blocked DNS queries tracking
β€’ Upstream DNS statistics
β€’ Per-client query statistics
β€’ Query reason statistics
β€’ DNS upstream latency histograms | +| Query Log Processing | β€’ Real-time query log analysis
β€’ Query deduplication to prevent metric inflation
β€’ Domain and query reason tracking | +| GeoIP Enrichment | β€’ MaxMind GeoLite2 IP geolocation lookup
β€’ DNS client geographic visualization (Grafana Geomap)
β€’ DNS threat map for blocked queries
β€’ GeoIP caching for improved performance | +| Traffic Analytics | β€’ DNS queries grouped by ISP organization
β€’ Country-level DNS traffic insights
β€’ ISP-based DNS traffic monitoring | +| Exporter Monitoring | β€’ Deduplication cache metrics
β€’ GeoIP cache usage monitoring
β€’ Deduplication hit counters
β€’ Exporter scrape performance metrics | +| Deployment | β€’ Customizable scrape interval
β€’ Lightweight single binary deployment
β€’ Docker-friendly deployment | +--- + +## Built With + +- [Go (Golang)](https://golang.org/) +- [Prometheus Client Library](https://github.com/prometheus/client_golang) +- [AdGuard Home](https://github.com/AdguardTeam/AdGuardHome) + +--- +## Prerequisites + +Before running this exporter, make sure: + +- AdGuard Home is up and running +- You have a valid AdGuard username & password (without special character (!,@,`,* etc.)) +- Prometheus is configured to scrape this exporter +- Docker installed (or alternatively Go 1.20+ for building from source) +- Port `9617` is available on your system + +To make sure the endpoint is valid, you can check the endpoints via curl: + +``` +curl -v -u : :/control/stats +curl -v -u : :/control/status +curl -v -u : :/control/querylog +``` +After that, you should got the output like this: + +``` +* Trying 172.31.20.12:80... +* Connected to 172.31.20.12 (172.31.20.12) port 80 (#0) +* Server auth using Basic with user 'admin' +> GET /control/stats HTTP/1.1 +> Host: 172.31.20.12 +> Authorization: Basic Xxxxxxxxxxxxxxxxxxxxxxxxx +> User-Agent: curl/7.88.1 +> Accept: */* +> +< HTTP/1.1 200 OK +< Access-Control-Allow-Origin: http://172.31.20.12 +< Content-Type: application/json +< Server: AdGuardHome/v0.107.62 +< Vary: Origin +< Vary: Accept-Encoding +< Date: Tue, 08 Jul 2025 16:45:56 GMT +< Transfer-Encoding: chunked +< +``` + +--- + +--- +## Environment Variables + +| Variable | Description | Required | Example | +|------------------|---------------------------- | -------------------|------------------- +| `ADGUARD_HOST`| AdGuard Home base URL | βœ… | `http://192.168.1.1:3000,` | +| `ADGUARD_USER`| AdGuard Home username | βœ… | `admin` | +| `ADGUARD_PASS`| AdGuard Home password | βœ… | `secretpassword` | +| `EXPORTER_PORT` | Port to expose metrics (default: 9617) | ❌ | `9200` | +| `SCRAPE_INTERVAL` | How often to scrape (default: 15s) | ❌ | `30s` | +| `LOG_LEVEL` | Log Level to analyze, INFO, WARN, DEBUG | ❌ | `DEBUG`,`WARN`,`INFO` | + +--- + +### Quick Start + +## Run via Docker Compose + +### 1. Create docker-compose.yml in your root dir + +```yaml +version: '3.8' + +services: + adguard-exporter: + image: ghcr.io/znandev/adguardexporter:latest + container_name: adguard_exporter + restart: unless-stopped + ports: + - "9617:9617" + volumes: + - .//GeoLite2-City.mmdb:/GeoLite2-City.mmdb + environment: + - ADGUARD_HOST= + - ADGUARD_USER= + - ADGUARD_PASS= + - EXPORTER_PORT=9617 + - SCRAPE_INTERVAL=15s + - LOG_LEVEL=DEBUG +``` + +### 2. Run with Docker Compose + +```bash +docker-compose up -d +``` + +--- + +## Metrics Endpoint + +Once running, your exporter will be available at: + +``` +http://:9617/metrics +``` + +Ready to scrape by Prometheus! + +--- + +## Example Prometheus Job + +```yaml +- job_name: 'adguard-exporter' + scrape_interval: 15s + static_configs: + - targets: ['adguard-exporter:9617'] +``` +--- +## Available Prometheus Metrics + +This exporter exposes the following metrics from AdGuard Home. + +--- + +### Core Metrics + +* `adguard_dns_queries_total` + Total DNS queries processed by AdGuard Home. + +* `adguard_blocked_filtering_total` + Total DNS queries blocked by filtering rules. + +* `adguard_replaced_parental` + Queries replaced by parental filtering. + +* `adguard_avg_processing_time` + Average DNS query processing time in milliseconds. + +--- + +### AdGuard Status + +* `adguard_protection_enabled` + Whether DNS filtering protection is enabled (1 = enabled, 0 = disabled). + +* `adguard_running` + Whether the AdGuard Home DNS service is running. + +* `adguard_dhcp_available` + Indicates if DHCP functionality is available. + +* `adguard_protection_disabled_duration_seconds` + Time since DNS protection was disabled. + +--- + +### Top Queries + +Metrics with labels: + +* `adguard_top_queried_domain_total{domain="example.com"}` +* `adguard_top_blocked_domain_total{domain="ads.example.com"}` +* `adguard_top_client_total{client="192.168.1.2"}` +* `adguard_top_upstream_total{upstream="8.8.8.8"}` +* `adguard_upstream_avg_response_time_seconds{upstream="8.8.8.8"}` + +--- + +### Query Log Metrics + +Metrics generated from AdGuard query log analysis. + +* `adguard_query_domain_total{domain="example.com"}` +* `adguard_query_reason_total{reason="FilteredBlackList"}` +* `adguard_query_type_total{type="A"}` +* `adguard_query_upstream_total{upstream="8.8.8.8"}` +* `adguard_query_client_reason_total{client="192.168.1.10",reason="FilteredBlackList"}` + +--- + +### Upstream Latency Metrics + +Histogram metrics that track DNS upstream response time distribution. + +* `adguard_upstream_latency_seconds` + +This metric allows latency percentile calculations such as **p95 or p99 upstream DNS latency**. + +Generated metrics: + +* `adguard_upstream_latency_seconds_bucket` +* `adguard_upstream_latency_seconds_sum` +* `adguard_upstream_latency_seconds_count` + +Example: + +``` +adguard_upstream_latency_seconds_bucket{upstream="1.1.1.1",le="0.005"} 50 +``` + +--- + +### GeoIP Metrics + +#### `adguard_client_geo_queries` + +Total DNS queries per client enriched with geographic information. + +Labels: + +* `client` – Client IP address +* `country` – ISO country code +* `lat` – Latitude +* `lon` – Longitude + +Example: + +``` +adguard_client_geo_queries{client="118.99.94.204",country="ID",lat="-2.969000",lon="104.744200"} 1000 +``` + +--- + +#### `adguard_blocked_geo_queries` + +Counts DNS queries that were blocked by filtering rules and enriches them with geographic information. + +Labels: + +* `client` +* `country` +* `lat` +* `lon` + +Example: + +``` +adguard_blocked_geo_queries{client="118.99.94.204",country="ID",lat="-2.969000",lon="104.744200"} 306 +``` + +--- + +### Exporter Health Metrics + +Metrics used to monitor the exporter itself. + +* `adguard_exporter_up` β€” Indicates if the exporter successfully scraped the AdGuard API. +* `adguard_exporter_scrape_duration_seconds` β€” Time taken for the exporter to scrape AdGuard API data. +* `adguard_exporter_scrape_errors_total` β€” Total number of errors encountered during scraping. +* `adguard_exporter_query_cache_size` β€” Number of cached DNS query entries used for deduplication. +* `adguard_exporter_geo_cache_size` β€” Number of cached GeoIP lookup entries. +* `adguard_exporter_dedup_hits_total` β€” Total number of duplicate DNS queries skipped by the exporter. + +--- + +## License + +This project is licensed under the MIT License β€” see the [LICENSE](LICENSE) file for details. + +--- + +## Screenshots + +Grafana Dashboard Preview + +[Live preview and monitoring, click here](https://stats.znand.my.id/public-dashboards/a0b05deb37464a17b01cbc69eb65ac5a?orgId=1&refresh=5s) + +[Deploy yours here](https://grafana.com/grafana/dashboards/23579-adguard-metrics-statistics/) +β€” new dashboard.json for grafana will be uploaded soon + +![dashboard1](./grafana/dashboard01.png) + +![dashboard2](./grafana/dashboard02.png) + +![dashboard3](./grafana/dashboard03.png) + +![dashboard4](./grafana/dashboard04.png) + +![dashboard5](./grafana/dashboard06.png) + +--- + +## πŸ’¬ Feedback + +Pull Requests, Issues, and Suggestions are always welcome! + +**Made with ❀️ by [znanddev](https://github.com/znand-dev)** diff --git a/adguard-exporter/docker-compose.yml b/adguard-exporter/docker-compose.yml new file mode 100644 index 0000000..7cee3a3 --- /dev/null +++ b/adguard-exporter/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3.8' + +services: + adguard-exporter: + image: ghcr.io/znand-dev/adguardexporter:latest + container_name: adguard_exporter + restart: unless-stopped + + ports: + - "9617:9617" + + volumes: + - .//GeoLite2-City.mmdb:/GeoLite2-City.mmdb # your geolite2 db file + + environment: + - ADGUARD_HOST= # try docker ips if didn't works http://172.xx.x.x for example + - ADGUARD_USER= # required + - ADGUARD_PASS= # required + - EXPORTER_PORT=9200 # optional, default 9617 + - SCRAPE_INTERVAL=15s # optional + - LOG_LEVEL=DEBUG # optional diff --git a/adguard-exporter/geolite2-db/README.md b/adguard-exporter/geolite2-db/README.md new file mode 100644 index 0000000..96d3a10 --- /dev/null +++ b/adguard-exporter/geolite2-db/README.md @@ -0,0 +1,8 @@ +Download GeoLite2 City database from MaxMind: + +https://dev.maxmind.com/geoip/geolite2-free-geolocation-data + +Place the file here: + +geoip/GeoLite2-City.mmdb + diff --git a/adguard-exporter/go.mod b/adguard-exporter/go.mod new file mode 100644 index 0000000..5b13801 --- /dev/null +++ b/adguard-exporter/go.mod @@ -0,0 +1,25 @@ +module github.com/znand-dev/adguardexporter + +go 1.24.0 + +toolchain go1.26.4 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/oschwald/geoip2-golang v1.13.0 + github.com/prometheus/client_golang v1.23.2 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oschwald/maxminddb-golang v1.13.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/sys v0.38.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect +) diff --git a/adguard-exporter/go.sum b/adguard-exporter/go.sum new file mode 100644 index 0000000..a8aa348 --- /dev/null +++ b/adguard-exporter/go.sum @@ -0,0 +1,52 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI= +github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo= +github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU= +github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/adguard-exporter/grafana/dashboard+node.json b/adguard-exporter/grafana/dashboard+node.json new file mode 100644 index 0000000..e679962 --- /dev/null +++ b/adguard-exporter/grafana/dashboard+node.json @@ -0,0 +1,1540 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "A powerful Grafana dashboard for monitoring AdGuard Home DNS performance using Prometheus as the data source.", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 23579, + "graphTooltip": 0, + "id": 7, + "links": [], + "liveNow": true, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 23, + "panels": [], + "title": "Node Exporter", + "type": "row" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "description": "Displays the total CPU utilization percentage (average across all cores). \nIt is calculated as 100% minus the average idle time of the CPU \nover the last 30 seconds.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlRd" + }, + "custom": { + "axisBorderShow": true, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 4, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 21, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "100 - (avg(rate(node_cpu_seconds_total{mode=\"idle\"}[30s])) * 100)", + "instant": false, + "legendFormat": "CPU Usage (%)", + "range": true, + "refId": "A" + } + ], + "title": "CPU Usage (%)", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-BlPu" + }, + "custom": { + "axisBorderShow": true, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 4, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 22, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "avg(100 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100))", + "instant": false, + "legendFormat": "RAM Usage (%)", + "range": true, + "refId": "A" + } + ], + "title": "RAM Usage (%)", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "custom": { + "axisBorderShow": true, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 5, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "KiBs" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 9, + "x": 12, + "y": 1 + }, + "id": 25, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "rate(node_network_transmit_bytes_total{device=\"eth0\",instance=\"172.31.18.251:9100\"}[5m]) * 8 / 1e3", + "instant": false, + "legendFormat": "TX Rate", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "rate(node_network_receive_bytes_total{device=\"eth0\",instance=\"172.31.18.251:9100\"}[5m]) * 8 / 1e3", + "hide": false, + "instant": false, + "legendFormat": "RX Rate", + "range": true, + "refId": "B" + } + ], + "title": "Network Traffic (KiB/s)", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 3, + "x": 21, + "y": 1 + }, + "id": 24, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "(avg(node_memory_SwapTotal_bytes - node_memory_SwapFree_bytes) / avg(node_memory_SwapTotal_bytes)) * 100", + "instant": false, + "legendFormat": "Swap Usage (%)", + "range": true, + "refId": "A" + } + ], + "title": "Swap Usage (%)", + "type": "gauge" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 18, + "panels": [], + "title": "AdGuard Statistics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "semi-dark-red", + "index": 1, + "text": "Disabled" + }, + "1": { + "color": "semi-dark-green", + "index": 0, + "text": "Enabled" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 0, + "y": 11 + }, + "id": 4, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "vertical", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "adguard_protection_enabled", + "hide": false, + "instant": false, + "legendFormat": "Protection Status", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "adguard_running", + "hide": false, + "instant": false, + "legendFormat": "Service Status", + "range": true, + "refId": "C" + } + ], + "title": "πŸ›‘Protection & Service", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "semi-dark-red", + "index": 1, + "text": "Stopped" + }, + "1": { + "color": "semi-dark-green", + "index": 0, + "text": "Running" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 4, + "y": 11 + }, + "id": 1, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "adguard_running", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "πŸ”ŒStatus", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 6, + "y": 11 + }, + "id": 13, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": true, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "(adguard_blocked_filtering_total / adguard_dns_queries_total) * 100", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Block Rate (%)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 9, + "y": 11 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "adguard_avg_processing_time * 1000", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Avg Processing Time", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "description": "\nThis panel displays the **real-time DNS query rate per second**, calculated over the last 1 minute.\n\n- Based on the metric: `adguard_dns_queries_total`\n- Uses the Prometheus `rate()` function:\n\n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-BlPu", + "seriesBy": "min" + }, + "custom": { + "axisBorderShow": true, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 29, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "req/s" + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 12, + "x": 12, + "y": 11 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "12.0.1+security-01", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "rate(adguard_dns_queries_total[1m])", + "legendFormat": "DNS QPS", + "range": true, + "refId": "A" + } + ], + "title": "⚑ πŸ“ˆ Total DNS Query Rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "description": "This panel shows the **average number of DNS queries per second**, grouped by query type (A, AAAA, PTR, etc.).\n\n- Based on the metric: `adguard_query_type_total`\n- Uses the Prometheus `rate()` function with a 5-minute range:", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisGridShow": true, + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 5, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 15 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.1+security-01", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(adguard_query_type_total[5m])) by (type)", + "format": "time_series", + "instant": false, + "legendFormat": "{{type}}", + "range": true, + "refId": "A" + } + ], + "title": "πŸ” DNS Query Types", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "description": "This panel displays the **average response time of DNS upstream servers**, in milliseconds.\n\n- Based on metric: `adguard_upstream_avg_response_time_seconds`\n- Converted to milliseconds by multiplying with 1000:", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisGridShow": true, + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 6, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 24 + }, + "id": 20, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.1+security-01", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "adguard_upstream_avg_response_time_seconds * 1000", + "legendFormat": "{{upstream}}", + "range": true, + "refId": "A" + } + ], + "title": "🌐 Avg Upstream Response Time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "description": "This panel displays the **real-time DNS query block rate per second**, grouped by client IP and the reason why the query was blocked or allowed.\n\n- Based on metric: `adguard_query_client_reason_total`\n- Uses Prometheus query:", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisGridShow": true, + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "log": 2, + "type": "symlog" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "fieldMinMax": false, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "o/s" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 24 + }, + "id": 14, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.1+security-01", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "sum(rate(adguard_query_client_reason_total[1m])) by (client, reason)", + "legendFormat": "{{client}} - {{reason}}", + "range": true, + "refId": "A" + } + ], + "title": "πŸ“ˆ Query Blocks - Real-Time per Client/Reason", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 9, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "count" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 35 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.1+security-01", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "topk(10, sum(adguard_query_client_reason_total) by (client))", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "πŸ“‘Top Clients", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 35 + }, + "id": 8, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "BrBG", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "right", + "reverse": false + } + }, + "pluginVersion": "11.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "topk(10, adguard_query_domain_total)", + "legendFormat": "{{domain}}", + "range": true, + "refId": "A" + } + ], + "title": "🌍 Top Queried Domains", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic-by-name" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisGridShow": true, + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 12, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 46 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.1+security-01", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "sum(adguard_query_reason_total) by (reason)\r\n", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "🚫 Block Reasons", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 46 + }, + "id": 10, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "right", + "reverse": false + } + }, + "pluginVersion": "11.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "cew4qvofsq48wf" + }, + "editorMode": "code", + "expr": "topk(10, adguard_top_blocked_domain_total)", + "legendFormat": "{{domain}}", + "range": true, + "refId": "A" + } + ], + "title": "🚫 Top Blocked Domains", + "type": "heatmap" + } + ], + "refresh": "5s", + "schemaVersion": 39, + "tags": [ + "DNS", + "AdGuard", + "Server" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "nowDelay": "5s" + }, + "timezone": "browser", + "title": "AdGuard Metrics Statistics", + "uid": "5fd5f4c0-d0b3-446b-a5b0-9bc11c02b1e02", + "version": 6, + "weekStart": "" +} \ No newline at end of file diff --git a/adguard-exporter/grafana/dashboard.json b/adguard-exporter/grafana/dashboard.json new file mode 100644 index 0000000..c82ac23 --- /dev/null +++ b/adguard-exporter/grafana/dashboard.json @@ -0,0 +1,3470 @@ +{ + "apiVersion": "dashboard.grafana.app/v2beta1", + "kind": "DashboardWithAccessInfo", + "metadata": { + "name": "5fd5f4c0-d0b3-446b-a5b0-9bc11c02b1e02", + "generation": 71, + "creationTimestamp": "2026-03-09T14:51:34Z", + "labels": {}, + "annotations": {} + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "builtIn": true, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "query": { + "group": "grafana", + "kind": "DataQuery", + "spec": {}, + "version": "v0" + } + } + } + ], + "cursorSync": "Off", + "description": "A powerful Grafana dashboard for monitoring AdGuard Home DNS performance using Prometheus as the data source.", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "adguard_running", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 1, + "links": [], + "title": "πŸ”ŒStatus", + "vizConfig": { + "group": "stat", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "semi-dark-red", + "index": 1, + "text": "Stopped" + }, + "1": { + "color": "semi-dark-green", + "index": 0, + "text": "Running" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + } + }, + "version": "12.4.0" + } + } + }, + "panel-10": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "topk(10, adguard_top_blocked_domain_total)", + "legendFormat": "{{domain}}", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 10, + "links": [], + "title": "🚫 Top Blocked Domains", + "vizConfig": { + "group": "heatmap", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "right", + "reverse": false + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-11": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "topk(10, sum(adguard_query_client_reason_total) by (client))", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 11, + "links": [], + "title": "πŸ“‘Top Clients", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 2, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "queries" + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-13": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "(adguard_blocked_filtering_total / adguard_dns_queries_total) * 100", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 13, + "links": [], + "title": "Block Rate (%)", + "vizConfig": { + "group": "stat", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": true, + "textMode": "auto", + "wideLayout": true + } + }, + "version": "12.4.0" + } + } + }, + "panel-14": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "topk(5, sum by (reason) (\r\n rate(adguard_query_client_reason_total[5m])\r\n))", + "legendFormat": "{{reason}}", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "This panel displays the **real-time DNS query block rate per second**, grouped reason why the query was blocked or allowed.\n\n- Based on metric: `adguard_query_client_reason_total`\n- Uses Prometheus query:", + "id": 14, + "links": [], + "title": "DNS Query Reasons", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisGridShow": true, + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 1, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 4, + "pointSize": 5, + "scaleDistribution": { + "log": 2, + "type": "symlog" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "fieldMinMax": false, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "req/s" + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-20": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "adguard_upstream_avg_response_time_seconds * 1000", + "legendFormat": "{{upstream}}", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "This panel displays the **average response time of DNS upstream servers**, in milliseconds.\n\n- Based on metric: `adguard_upstream_avg_response_time_seconds`\n- Converted to milliseconds by multiplying with 1000:", + "id": 20, + "links": [], + "title": "🌐 Avg Upstream Response Time", + "vizConfig": { + "group": "bargauge", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "max": 300, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "#EAB839", + "value": 70 + }, + { + "color": "red", + "value": 150 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "options": { + "displayMode": "lcd", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 34, + "minVizWidth": 8, + "namePlacement": "top", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "text": { + "titleSize": 15 + }, + "valueMode": "color" + } + }, + "version": "12.4.0" + } + } + }, + "panel-21": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "exemplar": false, + "expr": "sum(adguard_client_geo_queries) by (lat, lon, client, country)", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "range": false + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": {} + } + }, + { + "kind": "convertFieldType", + "spec": { + "id": "convertFieldType", + "options": { + "conversions": [ + { + "destinationType": "number", + "targetField": "lat" + }, + { + "destinationType": "number", + "targetField": "lon" + } + ], + "fields": {} + } + } + } + ] + } + }, + "description": "", + "id": 21, + "links": [], + "title": "GeoIP Clients", + "vizConfig": { + "group": "geomap", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "basemap": { + "config": {}, + "name": "Layer 0", + "noRepeat": false, + "type": "default" + }, + "controls": { + "mouseWheelZoom": true, + "showAttribution": true, + "showDebug": false, + "showMeasure": true, + "showScale": true, + "showZoom": true + }, + "layers": [ + { + "config": { + "arrow": 0, + "style": { + "color": { + "fixed": "blue" + }, + "lineWidth": 2, + "opacity": 0.4, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "text": { + "field": "", + "fixed": "", + "mode": "field" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "filterData": { + "id": "byRefId", + "options": "A" + }, + "layer-tooltip": true, + "location": { + "latitude": "lat", + "longitude": "lon", + "mode": "coords" + }, + "name": "GarisKoneksi", + "opacity": 0.4, + "tooltip": true, + "type": "route" + }, + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "dark-red" + }, + "opacity": 1, + "rotation": { + "field": "Value", + "fixed": 3, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "text": { + "fixed": "", + "mode": "field" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "filterData": { + "id": "byRefId", + "options": "A" + }, + "location": { + "latitude": "lat", + "longitude": "lon", + "mode": "coords" + }, + "name": "Layer 1", + "tooltip": true, + "type": "markers" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "coords", + "lat": 19.5, + "lon": 80, + "noRepeat": false, + "shared": false, + "zoom": 4 + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-22": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "adguard_exporter_up", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "If exporter crashed or failed to scrape", + "id": 22, + "links": [], + "title": "AdGuard Exporter Status", + "vizConfig": { + "group": "stat", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "fieldMinMax": false, + "mappings": [ + { + "options": { + "0": { + "color": "dark-red", + "index": 1, + "text": "Down" + }, + "1": { + "color": "semi-dark-green", + "index": 0, + "text": "Up" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + } + }, + "version": "12.4.0" + } + } + }, + "panel-24": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "adguard_exporter_scrape_duration_seconds", + "legendFormat": "Scrape Duration", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "How long the exporter scrapes the AdGuard API", + "id": 24, + "links": [], + "title": "Exporter Scrape Duration", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic-by-name" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisGridShow": true, + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 2, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "#EAB839", + "value": 0.5 + }, + { + "color": "dark-red", + "value": 1 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "timezone": [ + "browser" + ], + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-25": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "increase(adguard_exporter_scrape_errors_total[5m])", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "Count how much API scrapes failure", + "id": 25, + "links": [], + "title": "Exporter Scrape Errors", + "vizConfig": { + "group": "stat", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "fieldMinMax": false, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "semi-dark-green", + "value": 0 + }, + { + "color": "#EAB839", + "value": 10 + }, + { + "color": "dark-red", + "value": 20 + } + ] + } + }, + "overrides": [] + }, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + } + }, + "version": "12.4.0" + } + } + }, + "panel-26": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "(\r\nsum by (upstream) (rate(adguard_upstream_latency_seconds_sum[5m]))\r\n/\r\nsum by (upstream) (rate(adguard_upstream_latency_seconds_count[5m]))\r\n) * 1000", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 26, + "links": [], + "title": "Average Latency per Upstream", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 2, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "#EAB839", + "value": 80 + }, + { + "color": "dark-red", + "value": 200 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-27": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "histogram_quantile(\r\n0.95,\r\nsum(rate(adguard_upstream_latency_seconds_bucket[5m])) by (le, upstream)\r\n) * 1000", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 27, + "links": [], + "title": "P95 Latency", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisGridShow": true, + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-28": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "histogram_quantile(\r\n0.99,\r\nsum(rate(adguard_upstream_latency_seconds_bucket[5m])) by (le, upstream)\r\n) * 1000", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "Detect spike DNS", + "id": 28, + "links": [], + "title": "P99 Latency", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 1, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "#EAB839", + "value": 200 + }, + { + "color": "dark-red", + "value": 500 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "median" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-29": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "sum by (upstream) (\r\nrate(adguard_upstream_latency_seconds_count[5m])\r\n)", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 29, + "links": [], + "title": "Requests to upstream", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "rate(adguard_dns_queries_total[1m])", + "legendFormat": "DNS QPS", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "\nThis panel displays the **real-time DNS query rate per second**, calculated over the last 1 minute.\n\n- Based on the metric: `adguard_dns_queries_total`\n- Uses the Prometheus `rate()` function:\n\n", + "id": 3, + "links": [], + "title": "⚑ πŸ“ˆ Total DNS Query Rate", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-BlPu", + "seriesBy": "min" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 5, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 3, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "fieldMinMax": false, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "req/s" + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-31": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "increase(adguard_exporter_dedup_hits_total[5m])", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "Number of duplicate DNS queries skipped by exporter deduplication.", + "id": 31, + "links": [], + "title": "Deduplicated Queries (last 5m)", + "vizConfig": { + "group": "stat", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "dark-green", + "value": 0 + }, + { + "color": "orange", + "value": 250 + }, + { + "color": "dark-red", + "value": 1000 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": true, + "textMode": "auto", + "wideLayout": true + } + }, + "version": "12.4.0" + } + } + }, + "panel-32": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "adguard_exporter_query_cache_size", + "legendFormat": "Query Cache", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + }, + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "adguard_exporter_geo_cache_size", + "instant": false, + "legendFormat": "GeoIP Cache", + "range": true + }, + "version": "v0" + }, + "refId": "B" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "Number of query entries currently stored in exporter deduplication cache.", + "id": 32, + "links": [], + "title": "Exporter Cache Size", + "vizConfig": { + "group": "stat", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + } + }, + "version": "12.4.0" + } + } + }, + "panel-33": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "sum(rate(adguard_query_isp_total[5m])) by (isp)", + "legendFormat": "{{isp}}", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 33, + "links": [], + "title": "DNS Queries by ISP (QPS)", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 2, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 4, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "qps" + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-34": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "exemplar": false, + "expr": "sum(adguard_query_isp_total) by (country, isp)", + "format": "time_series", + "instant": true, + "legendFormat": "__auto", + "range": false + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": { + "mode": "rows", + "valueLabel": "country" + } + } + }, + { + "kind": "merge", + "spec": { + "id": "merge", + "options": {} + } + } + ] + } + }, + "description": "", + "id": 34, + "links": [], + "title": "DNS Traffic Map by ISP Country", + "vizConfig": { + "group": "geomap", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "fixed" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "basemap": { + "config": {}, + "name": "Layer 0", + "noRepeat": true, + "type": "default" + }, + "controls": { + "mouseWheelZoom": true, + "showAttribution": true, + "showDebug": false, + "showMeasure": true, + "showScale": true, + "showZoom": true + }, + "layers": [ + { + "config": { + "arrow": 1, + "style": { + "color": { + "fixed": "dark-orange" + }, + "lineWidth": 2, + "opacity": 0.4, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "text": { + "field": "", + "fixed": "", + "mode": "field" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "layer-tooltip": false, + "location": { + "lookup": "value", + "mode": "lookup" + }, + "name": "Layer 2", + "opacity": 0.4, + "tooltip": true, + "type": "route" + }, + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "dark-red" + }, + "opacity": 1, + "rotation": { + "fixed": 1, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "field": "value", + "fixed": 15, + "max": 5, + "min": 5 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "text": { + "fixed": "", + "mode": "field" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "filterData": { + "id": "byRefId", + "options": "merge-A-A-A-A-A-A-A" + }, + "layer-tooltip": true, + "location": { + "gazetteer": "public/build/gazetteer/countries.json", + "lookup": "value", + "mode": "lookup" + }, + "name": "Layer 1", + "tooltip": true, + "type": "markers" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "zero", + "lat": 0, + "lon": 0, + "noRepeat": true, + "shared": true, + "zoom": 1 + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-35": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "exemplar": false, + "expr": "sum(adguard_blocked_geo_queries) by (lat, lon, client, country)", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "range": false + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [ + { + "kind": "labelsToFields", + "spec": { + "id": "labelsToFields", + "options": {} + } + }, + { + "kind": "convertFieldType", + "spec": { + "id": "convertFieldType", + "options": { + "conversions": [ + { + "destinationType": "number", + "targetField": "lat" + }, + { + "destinationType": "number", + "targetField": "lon" + } + ], + "fields": {} + } + } + } + ] + } + }, + "description": "", + "id": 35, + "links": [], + "title": "GeoIP Blocked Queries by AdGuard", + "vizConfig": { + "group": "geomap", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "basemap": { + "config": {}, + "name": "Layer 0", + "noRepeat": false, + "type": "default" + }, + "controls": { + "mouseWheelZoom": true, + "showAttribution": true, + "showDebug": false, + "showMeasure": true, + "showScale": true, + "showZoom": true + }, + "layers": [ + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "dark-red" + }, + "opacity": 1, + "rotation": { + "field": "Value", + "fixed": 3, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "filterData": { + "id": "byRefId", + "options": "A" + }, + "location": { + "latitude": "lat", + "longitude": "lon", + "mode": "coords" + }, + "name": "Layer 1", + "tooltip": true, + "type": "markers" + }, + { + "config": { + "arrow": 0, + "style": { + "color": { + "fixed": "dark-purple" + }, + "lineWidth": 2, + "opacity": 0.4, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 15, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "location": { + "latitude": "lat", + "longitude": "lon", + "mode": "coords" + }, + "name": "GarisKoneksi", + "opacity": 0.4, + "tooltip": true, + "type": "route" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "coords", + "lat": 19.5, + "lon": 80, + "noRepeat": false, + "shared": false, + "zoom": 4 + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "adguard_protection_enabled", + "instant": false, + "legendFormat": "Protection Status", + "range": true + }, + "version": "v0" + }, + "refId": "B" + } + }, + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "adguard_running", + "instant": false, + "legendFormat": "Service Status", + "range": true + }, + "version": "v0" + }, + "refId": "C" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 4, + "links": [], + "title": "πŸ›‘Protection & Service", + "vizConfig": { + "group": "stat", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "semi-dark-red", + "index": 1, + "text": "Disabled" + }, + "1": { + "color": "semi-dark-green", + "index": 0, + "text": "Enabled" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "vertical", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + } + }, + "version": "12.4.0" + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "adguard_avg_processing_time * 1000", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 5, + "links": [], + "title": "Avg Processing Time", + "vizConfig": { + "group": "stat", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + } + }, + "version": "12.4.0" + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "exemplar": false, + "expr": "sum(increase(adguard_query_type_total[5m])) by (type)", + "format": "time_series", + "instant": false, + "legendFormat": "{{type}}", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "This panel shows the **number of DNS queries per second**, grouped by query type (A, AAAA, PTR, etc.).\n\n- Based on the metric: `adguard_query_type_total`\n- Uses the Prometheus `rate()` function with a 5-minute range:", + "id": 6, + "links": [], + "title": "πŸ” DNS Query Types", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisGridShow": true, + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 2, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 4, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "max": 500, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "queries" + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + } + }, + "version": "12.4.0" + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "sum(adguard_query_reason_total) by (reason)\r\n", + "legendFormat": "{{reason}}", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 7, + "links": [], + "title": "🚫 Block Reasons", + "vizConfig": { + "group": "bargauge", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "fieldMinMax": false, + "max": 470000, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "queries" + }, + "overrides": [] + }, + "options": { + "displayMode": "lcd", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 203, + "minVizHeight": 0, + "minVizWidth": 8, + "namePlacement": "top", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + } + }, + "version": "12.4.0" + } + } + }, + "panel-8": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "code", + "expr": "topk(10, adguard_query_domain_total)", + "legendFormat": "{{domain}}", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } + }, + "description": "", + "id": 8, + "links": [], + "title": "🌍 Top Queried Domains", + "vizConfig": { + "group": "heatmap", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "BrBG", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "right", + "reverse": false + } + } + }, + "version": "12.4.0" + } + } + } + }, + "layout": { + "kind": "RowsLayout", + "spec": { + "rows": [ + { + "kind": "RowsLayoutRow", + "spec": { + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-4" + }, + "height": 4, + "width": 4, + "x": 0, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-1" + }, + "height": 4, + "width": 2, + "x": 4, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-13" + }, + "height": 4, + "width": 3, + "x": 6, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-5" + }, + "height": 4, + "width": 3, + "x": 9, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-31" + }, + "height": 4, + "width": 12, + "x": 12, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-20" + }, + "height": 6, + "width": 12, + "x": 0, + "y": 4 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-7" + }, + "height": 6, + "width": 12, + "x": 12, + "y": 4 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-6" + }, + "height": 11, + "width": 12, + "x": 0, + "y": 10 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-33" + }, + "height": 11, + "width": 12, + "x": 12, + "y": 10 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-11" + }, + "height": 11, + "width": 12, + "x": 0, + "y": 21 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-3" + }, + "height": 11, + "width": 12, + "x": 12, + "y": 21 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-10" + }, + "height": 11, + "width": 12, + "x": 0, + "y": 32 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-8" + }, + "height": 11, + "width": 12, + "x": 12, + "y": 32 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-21" + }, + "height": 11, + "width": 12, + "x": 0, + "y": 43 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-14" + }, + "height": 11, + "width": 12, + "x": 12, + "y": 43 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-35" + }, + "height": 11, + "width": 12, + "x": 0, + "y": 54 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-34" + }, + "height": 11, + "width": 12, + "x": 12, + "y": 54 + } + } + ] + } + }, + "title": "AdGuard Statistics" + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-29" + }, + "height": 10, + "width": 12, + "x": 0, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-26" + }, + "height": 10, + "width": 12, + "x": 12, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-27" + }, + "height": 10, + "width": 12, + "x": 0, + "y": 10 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-28" + }, + "height": 10, + "width": 12, + "x": 12, + "y": 10 + } + } + ] + } + }, + "title": "Latency Data Monitoring for Upstream DNS" + } + }, + { + "kind": "RowsLayoutRow", + "spec": { + "collapse": false, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-22" + }, + "height": 4, + "width": 4, + "x": 0, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-32" + }, + "height": 3, + "width": 20, + "x": 4, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-24" + }, + "height": 5, + "width": 20, + "x": 4, + "y": 3 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-25" + }, + "height": 4, + "width": 4, + "x": 0, + "y": 4 + } + } + ] + } + }, + "title": "Exporter Healths" + } + } + ] + } + }, + "links": [], + "liveNow": true, + "preload": false, + "tags": [ + "DNS", + "AdGuard", + "Server" + ], + "timeSettings": { + "autoRefresh": "5s", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "fiscalYearStartMonth": 0, + "from": "now-1h", + "hideTimepicker": false, + "nowDelay": "5s", + "timezone": "browser", + "to": "now" + }, + "title": "AdGuard Metrics Statistics", + "variables": [] + }, + "status": {} +} \ No newline at end of file diff --git a/adguard-exporter/grafana/dashboard01.png b/adguard-exporter/grafana/dashboard01.png new file mode 100644 index 0000000..4a6c632 Binary files /dev/null and b/adguard-exporter/grafana/dashboard01.png differ diff --git a/adguard-exporter/grafana/dashboard02.png b/adguard-exporter/grafana/dashboard02.png new file mode 100644 index 0000000..32ae91e Binary files /dev/null and b/adguard-exporter/grafana/dashboard02.png differ diff --git a/adguard-exporter/grafana/dashboard03.png b/adguard-exporter/grafana/dashboard03.png new file mode 100644 index 0000000..cf41ed2 Binary files /dev/null and b/adguard-exporter/grafana/dashboard03.png differ diff --git a/adguard-exporter/grafana/dashboard04.png b/adguard-exporter/grafana/dashboard04.png new file mode 100644 index 0000000..815e0fa Binary files /dev/null and b/adguard-exporter/grafana/dashboard04.png differ diff --git a/adguard-exporter/grafana/dashboard06.png b/adguard-exporter/grafana/dashboard06.png new file mode 100644 index 0000000..7d23884 Binary files /dev/null and b/adguard-exporter/grafana/dashboard06.png differ diff --git a/adguard-exporter/grafana/dashboard1.png b/adguard-exporter/grafana/dashboard1.png new file mode 100644 index 0000000..65868b0 Binary files /dev/null and b/adguard-exporter/grafana/dashboard1.png differ diff --git a/adguard-exporter/grafana/dashboard2.png b/adguard-exporter/grafana/dashboard2.png new file mode 100644 index 0000000..d879696 Binary files /dev/null and b/adguard-exporter/grafana/dashboard2.png differ diff --git a/adguard-exporter/grafana/dashboard3.png b/adguard-exporter/grafana/dashboard3.png new file mode 100644 index 0000000..7a4fee1 Binary files /dev/null and b/adguard-exporter/grafana/dashboard3.png differ diff --git a/adguard-exporter/grafana/dashboard4.png b/adguard-exporter/grafana/dashboard4.png new file mode 100644 index 0000000..5b5f647 Binary files /dev/null and b/adguard-exporter/grafana/dashboard4.png differ diff --git a/adguard-exporter/main.go b/adguard-exporter/main.go new file mode 100644 index 0000000..3659adc --- /dev/null +++ b/adguard-exporter/main.go @@ -0,0 +1,860 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net" + "net/http" + "os" + "strconv" + "sync" + "time" + + "github.com/joho/godotenv" + "github.com/oschwald/geoip2-golang" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +/* + πŸ“¦ AdGuard Exporter for Prometheus + ---------------------------------- + Author : @znand-dev + License : MIT + Repo : https://github.com/znand-dev/adguardexporter + + This Go application fetches stats from AdGuard Home via API endpoints + and exposes them as Prometheus metrics at `/metrics`. + + Required ENV variables: + - ADGUARD_HOST : AdGuard Home base URL (e.g. http://192.168.1.1:3000) + - ADGUARD_USER : API username (your adguard user) + - ADGUARD_PASS : API password (your adguard pass) + - EXPORTER_PORT : Port to expose metrics (default: 9617) + - SCRAPE_INTERVAL : Interval (in seconds) to fetch new stats (default: 15) + - LOG_LEVEL : Logging level (options: DEBUG, INFO, WARN, ERROR β€” default: INFO) + - GEOIP_DB : GeoLite2-City.mmdb +*/ + +var logLevelMap = map[string]int{"ERROR": 1, "WARN": 2, "INFO": 3, "DEBUG": 4} +var currentLogLevel = 3 + +func initLogger() { + level := os.Getenv("LOG_LEVEL") + if level == "" { + level = "INFO" + } + if val, ok := logLevelMap[level]; ok { + currentLogLevel = val + } +} + +func logX(level string, format string, args ...interface{}) { + if logLevelMap[level] <= currentLogLevel { + log.Printf("[%s] %s", level, fmt.Sprintf(format, args...)) + } +} + +//function hash query +func buildQueryKey(client, domain, upstream, reason, elapsed string) string { + return client + "|" + domain + "|" + upstream + "|" + reason + "|" + elapsed +} + +type GeoCache struct { + Country string + Lat float64 + Lon float64 + TS int64 +} + +var ( + geoDB *geoip2.Reader + geoCache = map[string]GeoCache{} + geoMutex sync.RWMutex + // cache query hash + querySeen = map[string]int64{} + queryMutex sync.Mutex + queryTTL = int64(300) // 5 menit + geoTTL = int64(86400) // 24 jam + dedupHits int64 +) + +type AdGuardStats struct { + NumDNSQueries float64 `json:"num_dns_queries"` + NumBlockedFiltering float64 `json:"num_blocked_filtering"` + NumReplacedParental float64 `json:"num_replaced_parental"` + AvgProcessingTime float64 `json:"avg_processing_time"` + TopQueriedDomains []map[string]float64 `json:"top_queried_domains"` + TopBlockedDomains []map[string]float64 `json:"top_blocked_domains"` + TopClients []map[string]float64 `json:"top_clients"` + TopUpstream []map[string]float64 `json:"top_upstreams_responses"` + TopUpstreamTime []map[string]float64 `json:"top_upstreams_avg_time"` +} + +type AdGuardStatus struct { + Version string `json:"version"` + Language string `json:"language"` + DNSAddresses []string `json:"dns_addresses"` + DNSPort int `json:"dns_port"` + HTTPPort int `json:"http_port"` + ProtectionDisabledDuration int `json:"protection_disabled_duration"` + ProtectionEnabled bool `json:"protection_enabled"` + DHCPAvailable bool `json:"dhcp_available"` + Running bool `json:"running"` +} + +type AdGuardQueryLog struct { + Data []struct { + Question struct { + Type string `json:"type"` + Name string `json:"name"` + } `json:"question"` + Answer []interface{} `json:"answer"` + Reason string `json:"reason"` + Client string `json:"client"` + Elapsed string `json:"elapsedMs"` + Upstream string `json:"upstream"` + + ClientInfo struct { + Whois struct { + Country string `json:"country"` + OrgName string `json:"orgname"` + } `json:"whois"` + } `json:"client_info"` + + } `json:"data"` +} + +var ( + + dnsQueries = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "adguard_dns_queries_total", Help: "Total DNS queries received", + }) + + blockedFiltering = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "adguard_blocked_filtering_total", Help: "Total DNS queries blocked", + }) + + replacedParental = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "adguard_replaced_parental", Help: "Total parental replaced queries", + }) + + avgProcessingTime = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "adguard_avg_processing_time", Help: "Avg DNS processing time", + }) + + statusProtectionEnabled = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "adguard_protection_enabled", Help: "Protection enabled (1/0)", + }) + + statusRunning = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "adguard_running", Help: "AdGuard running", + }) + + statusDHCPAvailable = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "adguard_dhcp_available", Help: "DHCP available", + }) + + statusDisabledDuration = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "adguard_protection_disabled_duration_seconds", + Help: "Time since protection disabled", + }) + + versionInfo = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "adguard_version_info", + Help: "AdGuard version", + }, + []string{"version"}, + ) + + topQueriedDomains = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "adguard_top_queried_domain_total", + Help: "Top queried domains", + }, + []string{"domain"}, + ) + + topBlockedDomains = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "adguard_top_blocked_domain_total", + Help: "Top blocked domains", + }, + []string{"domain"}, + ) + + topClients = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "adguard_top_client_total", + Help: "Top client IPs", + }, + []string{"client"}, + ) + + topUpstreams = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "adguard_top_upstream_total", + Help: "Top upstream servers", + }, + []string{"upstream"}, + ) + + topUpstreamTime = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "adguard_upstream_avg_response_time_seconds", + Help: "Avg response time per upstream", + }, + []string{"upstream"}, + ) + + queryCountByReason = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "adguard_query_reason_total", + Help: "Queries by reason", + }, + []string{"reason"}, + ) + + queryCountByType = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "adguard_query_type_total", + Help: "Queries by DNS type", + }, + []string{"type"}, + ) + + queryHistogramByClient = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "adguard_query_elapsed_ms", + Help: "Query duration by client", + Buckets: prometheus.LinearBuckets(1, 5, 10), + }, + []string{"client"}, + ) + + queryCountByUpstream = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "adguard_query_upstream_total", + Help: "Queries per upstream", + }, + []string{"upstream"}, + ) + + queryCountByDomain = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "adguard_query_domain_total", + Help: "Queries per domain", + }, + []string{"domain"}, + ) + + queryCountClientReason = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "adguard_query_client_reason_total", + Help: "Queries per client per reason", + }, + []string{"client", "reason"}, + ) + + clientGeoQueries = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "adguard_client_geo_queries", + Help: "Queries per client with geographic info", + }, + []string{"client", "country", "lat", "lon"}, + ) + + blockedGeoQueries = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "adguard_blocked_geo_queries", + Help: "Blocked DNS queries per client with geographic info", + }, + []string{"client", "country", "lat", "lon"}, + ) + + upstreamLatencyHistogram = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "adguard_upstream_latency_seconds", + Help: "Latency distribution per upstream DNS server", + Buckets: prometheus.ExponentialBuckets( + 0.001, // 1ms + 2, + 10, + ), + }, + []string{"upstream"}, + ) + + exporterUp = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "adguard_exporter_up", + Help: "Exporter scrape success", + }, + ) + + exporterScrapeDuration = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "adguard_exporter_scrape_duration_seconds", + Help: "Exporter scrape duration", + }, + ) + + exporterErrors = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "adguard_exporter_scrape_errors_total", + Help: "Total exporter errors", + }, + ) + + exporterQueryCacheSize = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "adguard_exporter_query_cache_size", + Help: "Current number of entries in query deduplication cache", + }, + ) + + exporterGeoCacheSize = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "adguard_exporter_geo_cache_size", + Help: "Current number of cached GeoIP entries", + }, + ) + + exporterDedupHits = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "adguard_exporter_dedup_hits_total", + Help: "Total number of duplicate queries skipped by deduplication", + }, + ) + + queryCountByISP = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "adguard_query_isp_total", + Help: "DNS queries grouped by ISP organization", + }, + []string{"isp", "country"}, + ) +) + +func init() { + _ = godotenv.Load() + initLogger() + + prometheus.MustRegister( + dnsQueries, + blockedFiltering, + replacedParental, + avgProcessingTime, + statusProtectionEnabled, + statusRunning, + statusDHCPAvailable, + statusDisabledDuration, + versionInfo, + topQueriedDomains, + topBlockedDomains, + topClients, + topUpstreams, + topUpstreamTime, + queryCountByReason, + queryCountByType, + queryHistogramByClient, + queryCountByUpstream, + queryCountByDomain, + queryCountClientReason, + clientGeoQueries, + blockedGeoQueries, + exporterUp, + exporterScrapeDuration, + exporterErrors, + upstreamLatencyHistogram, + exporterQueryCacheSize, + exporterGeoCacheSize, + exporterDedupHits, + queryCountByISP, + ) +} + +func resolveGeo(ipStr string) (GeoCache, bool) { + if geoDB == nil { + return GeoCache{}, false + } + + ip := net.ParseIP(ipStr) + if ip == nil || ip.IsPrivate() || ip.IsLoopback() { + return GeoCache{}, false + } + + now := time.Now().Unix() + + geoMutex.RLock() + if val, ok := geoCache[ipStr]; ok { + + // cache valid + if now-val.TS < geoTTL { + geoMutex.RUnlock() + return val, true + } + + } + geoMutex.RUnlock() + + record, err := geoDB.City(ip) + if err != nil { + return GeoCache{}, false + } + + cache := GeoCache{ + Country: record.Country.IsoCode, + Lat: record.Location.Latitude, + Lon: record.Location.Longitude, + TS: now, + } + + geoMutex.Lock() + geoCache[ipStr] = cache + geoMutex.Unlock() + + return cache, true +} + +func fetchQueryLog() (*AdGuardQueryLog, error) { + + host := os.Getenv("ADGUARD_HOST") + user := os.Getenv("ADGUARD_USER") + pass := os.Getenv("ADGUARD_PASS") + + url := host + "/control/querylog" + + req, _ := http.NewRequest("GET", url, nil) + req.SetBasicAuth(user, pass) + + client := &http.Client{Timeout: 10 * time.Second} + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer func() { + if err := resp.Body.Close(); err != nil { + logX("WARN", "failed to close response body: %v", err) + } + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var logData AdGuardQueryLog + + err = json.Unmarshal(body, &logData) + + return &logData, err +} + +func fetchStats() (*AdGuardStats, error) { + + host := os.Getenv("ADGUARD_HOST") + user := os.Getenv("ADGUARD_USER") + pass := os.Getenv("ADGUARD_PASS") + + url := host + "/control/stats" + + req, _ := http.NewRequest("GET", url, nil) + req.SetBasicAuth(user, pass) + + client := &http.Client{Timeout: 10 * time.Second} + + resp, err := client.Do(req) + + if err != nil { + return nil, err + } + + defer func() { + if err := resp.Body.Close(); err != nil { + logX("WARN", "failed to close response body: %v", err) + } + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var stats AdGuardStats + + err = json.Unmarshal(body, &stats) + + return &stats, err +} + +func fetchStatus() (*AdGuardStatus, error) { + + host := os.Getenv("ADGUARD_HOST") + user := os.Getenv("ADGUARD_USER") + pass := os.Getenv("ADGUARD_PASS") + + url := host + "/control/status" + + req, _ := http.NewRequest("GET", url, nil) + req.SetBasicAuth(user, pass) + + client := &http.Client{Timeout: 10 * time.Second} + + resp, err := client.Do(req) + + if err != nil { + return nil, err + } + + defer func() { + if err := resp.Body.Close(); err != nil { + logX("WARN", "failed to close response body: %v", err) + } + }() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var status AdGuardStatus + + err = json.Unmarshal(body, &status) + + return &status, err +} + +func updateStatsMetrics() { + + stats, err := fetchStats() + + if err != nil { + logX("ERROR", "Failed fetch stats: %v", err) + return + } + + dnsQueries.Set(stats.NumDNSQueries) + blockedFiltering.Set(stats.NumBlockedFiltering) + replacedParental.Set(stats.NumReplacedParental) + avgProcessingTime.Set(stats.AvgProcessingTime) + + // reset gauge vectors + topQueriedDomains.Reset() + topBlockedDomains.Reset() + topClients.Reset() + topUpstreams.Reset() + topUpstreamTime.Reset() + + // top queried domains + for _, d := range stats.TopQueriedDomains { + for domain, count := range d { + topQueriedDomains.WithLabelValues(domain).Set(count) + } + } + + // top blocked domains + for _, d := range stats.TopBlockedDomains { + for domain, count := range d { + topBlockedDomains.WithLabelValues(domain).Set(count) + } + } + + // top clients + for _, c := range stats.TopClients { + for client, count := range c { + topClients.WithLabelValues(client).Set(count) + } + } + + // upstream responses + for _, u := range stats.TopUpstream { + for upstream, count := range u { + topUpstreams.WithLabelValues(upstream).Set(count) + } + } + + // upstream avg response time + for _, u := range stats.TopUpstreamTime { + for upstream, time := range u { + topUpstreamTime.WithLabelValues(upstream).Set(time) + } + } + + logX( + "DEBUG", + "Fetched stats: queries=%.0f blocked=%.0f replaced=%.0f avgTime=%.2fms topDomains=%d", + stats.NumDNSQueries, + stats.NumBlockedFiltering, + stats.NumReplacedParental, + stats.AvgProcessingTime, + len(stats.TopQueriedDomains), + ) +} + +func updateStatusMetrics() { + + status, err := fetchStatus() + + if err != nil { + logX("ERROR", "Failed fetch status: %v", err) + return + } + + if status.Running { + statusRunning.Set(1) + } else { + statusRunning.Set(0) + } + + if status.ProtectionEnabled { + statusProtectionEnabled.Set(1) + } else { + statusProtectionEnabled.Set(0) + } + + if status.DHCPAvailable { + statusDHCPAvailable.Set(1) + } else { + statusDHCPAvailable.Set(0) + } + + statusDisabledDuration.Set(float64(status.ProtectionDisabledDuration)) + + versionInfo.WithLabelValues(status.Version).Set(1) + + logX( + "DEBUG", + "Fetched status: running=%v protection=%v DHCP=%v version=%s", + status.Running, + status.ProtectionEnabled, + status.DHCPAvailable, + status.Version, + ) +} + +func updateQueryLogMetrics() { + + scanned := 0 + processed := 0 + skipped := 0 + geoResolved := 0 + privateClients := 0 + publicClients := 0 + + logData, err := fetchQueryLog() + + if err != nil { + logX("ERROR", "Failed querylog: %v", err) + return + } + + for _, q := range logData.Data { + + scanned++ + + ip := net.ParseIP(q.Client) + + if ip != nil { + if ip.IsPrivate() || ip.IsLoopback() { + privateClients++ + } else { + publicClients++ + } + + } + + // dedup logic + key := buildQueryKey( + q.Client, + q.Question.Name, + q.Upstream, + q.Reason, + q.Elapsed, + ) + + now := time.Now().Unix() + + queryMutex.Lock() + + if ts, exists := querySeen[key]; exists { + if now-ts < queryTTL { + queryMutex.Unlock() + skipped++ + exporterDedupHits.Inc() + continue + } + } + + querySeen[key] = now + queryMutex.Unlock() + + processed++ + + queryCountByReason.WithLabelValues(q.Reason).Inc() + queryCountByType.WithLabelValues(q.Question.Type).Inc() + + elapsedMs, err := strconv.ParseFloat(q.Elapsed, 64) + + if err == nil { + + queryHistogramByClient.WithLabelValues(q.Client).Observe(elapsedMs) + + if q.Upstream != "" { + upstreamLatencyHistogram.WithLabelValues(q.Upstream).Observe(elapsedMs / 1000) + } + } + + queryCountByUpstream.WithLabelValues(q.Upstream).Inc() + queryCountByDomain.WithLabelValues(q.Question.Name).Inc() + queryCountClientReason.WithLabelValues(q.Client, q.Reason).Inc() + + geo, ok := resolveGeo(q.Client) + + if ok { + + geoResolved++ + + clientGeoQueries.WithLabelValues( + q.Client, + geo.Country, + fmt.Sprintf("%f", geo.Lat), + fmt.Sprintf("%f", geo.Lon), + ).Inc() + + if q.Reason == "FilteredBlackList" || + q.Reason == "FilteredSafeBrowsing" || + q.Reason == "FilteredParental" { + + blockedGeoQueries.WithLabelValues( + q.Client, + geo.Country, + fmt.Sprintf("%f", geo.Lat), + fmt.Sprintf("%f", geo.Lon), + ).Inc() + } + } + + isp := q.ClientInfo.Whois.OrgName + country := q.ClientInfo.Whois.Country + + if isp != "" { + queryCountByISP.WithLabelValues(isp, country).Inc() + + } + + } + + logX("DEBUG", + "Querylog: scanned=%d new=%d skipped=%d geoip=%d dedupHits=%d", + scanned, + processed, + skipped, + geoResolved, + dedupHits, + ) +} + +//cleanup memory +func cleanupQueryCache() { + + now := time.Now().Unix() + + queryMutex.Lock() + + for k, v := range querySeen { + + if now-v > queryTTL { + delete(querySeen, k) + } + + } + + queryMutex.Unlock() +} + +func main() { + + dbPath := os.Getenv("GEOIP_DB") + + if dbPath == "" { + dbPath = "GeoLite2-City.mmdb" + } + + db, err := geoip2.Open(dbPath) + + if err != nil { + logX("WARN", "GeoIP DB not found (%v) β€” geo metrics disabled", err) + } else { + geoDB = db + logX("INFO", "GeoIP database loaded") + } + + scrapeIntervalStr := os.Getenv("SCRAPE_INTERVAL") + port := os.Getenv("EXPORTER_PORT") + + if port == "" { + port = "9617" + } + + interval, err := strconv.Atoi(scrapeIntervalStr) + + if err != nil || interval < 1 { + interval = 15 + } + + logX("INFO", "Scrape interval set to %ds", interval) + + go func() { + + for { + + start := time.Now() + + updateStatsMetrics() + updateStatusMetrics() + updateQueryLogMetrics() + + cleanupQueryCache() + + duration := time.Since(start).Seconds() + + exporterScrapeDuration.Set(duration) + exporterUp.Set(1) + + exporterQueryCacheSize.Set(float64(len(querySeen))) + exporterGeoCacheSize.Set(float64(len(geoCache))) + + logX( + "DEBUG", + "Exporter caches: query_cache_entries=%d geo_cache_entries=%d dedup_hits_total=%d", + len(querySeen), + len(geoCache), + dedupHits, + ) + + logX("DEBUG", "Scrape finished in %.3fs", duration) + + time.Sleep(time.Duration(interval) * time.Second) + + } + + }() + + http.Handle("/metrics", promhttp.Handler()) + + logX("INFO", "Starting exporter at :%s ..", port) + + err = http.ListenAndServe(":"+port, nil) + + if err != nil { + logX("ERROR", "Server failed: %v", err) + os.Exit(1) + } + +}