Github Mirror / telegraf-plugins: 7861ebde




Add plugin

Add plugin

Commit 7861ebde.

Authored 2023-04-10T11:01:23.000+01:00 by B Tasker in project Github Mirror / telegraf-plugins

+1064 lines -0 lines

Commit Signature

Changes

diff --git a/pihole-granular-stats/README.md b/pihole-granular-stats/README.md
--- a/pihole-granular-stats/README.md
+++ b/pihole-granular-stats/README.md
# @@ -0,0 +1,102 @@
# +# Per Client Pi-Hole stats for Telegraf
# +
# +This `exec` plugin for [Telegraf](https://github.com/influxdata/telegraf) collects statistics from [Pi-Hole's](https://pi-hole.net/) API in order to expose *per-client* statistics on block-rates.
# +
# +----
# +
# +### System Wide Stats
# +
# +Before creating this plugin, I already collected stats from Pi-Hole using a HTTP input like the one [described here](https://www.alteredtech.io/posts/pihole-telegraph/)
# +```ini
# + [[inputs.http]]
# + #pihole URL for data in json format
# + urls=["http://x.x.x.x:8080/admin/api.php"]
# +
# + method = "GET"
# +
# + #Overwrite measurement name from default http to pihole_stats
# + name_override = "pihole_stats"
# +
# + #exclude host items from tags
# + tagexclude = ["host"]
# +
# + #data from http in JSON format
# + data_format = "json"
# +
# + #JSON values to set as string fields
# + json_string_fields = ["url","status"]
# +
# + insecure_skip_verify = true
# +```
# +So, this plugin doesn't attempt to reimplement those.
# +
# +----
# +
# +### Deploying and Configuring
# +
# +The script should be saved somewhere that Telegraf can find it (I tend to use `/usr/local/src/telegraf_plugins/`).
# +
# +At the head of the script are some configuration items
# +```python
# +# Pihole connection info
# +PIHOLE_ADDRESS="http://127.0.0.1:8080"
# +
# +# Auth token
# +# get this from settings -> API -> Show API Token
# +PIHOLE_TOKEN=""
# +
# +# How many minutes of Pihole logs to query on each
# +# iteration
# +QUERY_TIME_RANGE=15
# +
# +# The measurement name to use in output LP
# +MEASUREMENT="pihole_clients"
# +```
# +
# +----
# +
# +### Telegraf Config File
# +
# +The plugin simply needs to be referenced in an exec plugin statement:
# +```ini
# +[[inputs.exec]]
# + commands = [
# + "/usr/local/src/telegraf_plugins/pihole-granular-stats.py"
# + ]
# + timeout = "60s"
# + interval = "5m"
# + name_suffix = ""
# + data_format = "influx"
# +```
# +
# +----
# +
# +### Stats
# +
# +Default Measurement: `pihole_clients`
# +tags: `client`
# +
# +Fields:
# +
# +* blocklisted
# +* forwarded
# +* cachedresponse
# +* wildcardblock
# +* total
# +
# +----
# +
# +### Graphing
# +
# +This repo contains an example [Grafana Dashboard](grafana/Pi-Hole_Per_Client.json)
# +
# +![Screenshot of Grafana dashboard showing per-client metrics](grafana/screenshot.png)
# +
# +
# +----
# +
# +### Copyright
# +
# +Copyright (c) 2023 [Ben Tasker](https://www.bentasker.co.uk)
# +
# +Released under [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.txt)
#
diff --git a/pihole-granular-stats/grafana/Pi-Hole_Per_Client.json b/pihole-granular-stats/grafana/Pi-Hole_Per_Client.json
--- a/pihole-granular-stats/grafana/Pi-Hole_Per_Client.json
+++ b/pihole-granular-stats/grafana/Pi-Hole_Per_Client.json
# @@ -0,0 +1,806 @@
# +{
# + "__inputs": [
# + {
# + "name": "DS_INFLUXDB",
# + "label": "InfluxDB",
# + "description": "",
# + "type": "datasource",
# + "pluginId": "influxdb",
# + "pluginName": "InfluxDB"
# + }
# + ],
# + "__elements": {},
# + "__requires": [
# + {
# + "type": "grafana",
# + "id": "grafana",
# + "name": "Grafana",
# + "version": "9.3.6"
# + },
# + {
# + "type": "datasource",
# + "id": "influxdb",
# + "name": "InfluxDB",
# + "version": "1.0.0"
# + },
# + {
# + "type": "panel",
# + "id": "stat",
# + "name": "Stat",
# + "version": ""
# + },
# + {
# + "type": "panel",
# + "id": "timeseries",
# + "name": "Time series",
# + "version": ""
# + }
# + ],
# + "annotations": {
# + "list": [
# + {
# + "builtIn": 1,
# + "datasource": {
# + "type": "grafana",
# + "uid": "-- Grafana --"
# + },
# + "enable": true,
# + "hide": true,
# + "iconColor": "rgba(0, 211, 255, 1)",
# + "name": "Annotations & Alerts",
# + "target": {
# + "limit": 100,
# + "matchAny": false,
# + "tags": [],
# + "type": "dashboard"
# + },
# + "type": "dashboard"
# + }
# + ]
# + },
# + "editable": true,
# + "fiscalYearStartMonth": 0,
# + "graphTooltip": 0,
# + "id": null,
# + "links": [],
# + "liveNow": false,
# + "panels": [
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "description": "",
# + "fieldConfig": {
# + "defaults": {
# + "color": {
# + "mode": "thresholds"
# + },
# + "mappings": [],
# + "thresholds": {
# + "mode": "absolute",
# + "steps": [
# + {
# + "color": "green",
# + "value": null
# + },
# + {
# + "color": "red",
# + "value": 80
# + }
# + ]
# + }
# + },
# + "overrides": []
# + },
# + "gridPos": {
# + "h": 4,
# + "w": 6,
# + "x": 0,
# + "y": 0
# + },
# + "id": 2,
# + "options": {
# + "colorMode": "value",
# + "graphMode": "area",
# + "justifyMode": "auto",
# + "orientation": "auto",
# + "reduceOptions": {
# + "calcs": [
# + "lastNotNull"
# + ],
# + "fields": "",
# + "values": false
# + },
# + "textMode": "auto"
# + },
# + "pluginVersion": "9.3.6",
# + "targets": [
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "query": "from(bucket: \"telegraf/autogen\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"pihole_clients\")\n |> filter(fn: (r) => r._field == \"total\")\n |> filter(fn: (r) => r.client == \"${client}\")\n |> sum()\n ",
# + "refId": "A"
# + }
# + ],
# + "title": "DNS Queries in Period",
# + "type": "stat"
# + },
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "description": "",
# + "fieldConfig": {
# + "defaults": {
# + "color": {
# + "mode": "thresholds"
# + },
# + "mappings": [],
# + "thresholds": {
# + "mode": "absolute",
# + "steps": [
# + {
# + "color": "green",
# + "value": null
# + },
# + {
# + "color": "red",
# + "value": 80
# + }
# + ]
# + }
# + },
# + "overrides": []
# + },
# + "gridPos": {
# + "h": 4,
# + "w": 6,
# + "x": 6,
# + "y": 0
# + },
# + "id": 4,
# + "options": {
# + "colorMode": "value",
# + "graphMode": "area",
# + "justifyMode": "auto",
# + "orientation": "auto",
# + "reduceOptions": {
# + "calcs": [
# + "lastNotNull"
# + ],
# + "fields": "",
# + "values": false
# + },
# + "textMode": "auto"
# + },
# + "pluginVersion": "9.3.6",
# + "targets": [
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "query": "from(bucket: \"telegraf/autogen\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"pihole_clients\")\n |> filter(fn: (r) => r._field == \"blocklisted\")\n |> filter(fn: (r) => r.client == \"${client}\")\n |> sum()",
# + "refId": "A"
# + }
# + ],
# + "title": "Domains Blocked in Period",
# + "type": "stat"
# + },
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "description": "",
# + "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": 6,
# + "x": 12,
# + "y": 0
# + },
# + "id": 6,
# + "options": {
# + "colorMode": "value",
# + "graphMode": "area",
# + "justifyMode": "auto",
# + "orientation": "auto",
# + "reduceOptions": {
# + "calcs": [
# + "lastNotNull"
# + ],
# + "fields": "",
# + "values": false
# + },
# + "textMode": "auto"
# + },
# + "pluginVersion": "9.3.6",
# + "targets": [
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "query": "blocked = from(bucket: \"telegraf/autogen\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"pihole_clients\")\n |> filter(fn: (r) => r._field == \"blocklisted\")\n |> filter(fn: (r) => r.client == \"${client}\")\n |> aggregateWindow(every: 1d, fn: sum)\n \ntotal = from(bucket: \"telegraf/autogen\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"pihole_clients\")\n |> filter(fn: (r) => r._field == \"total\")\n |> filter(fn: (r) => r.client == \"${client}\")\n |> aggregateWindow(every: 1d, fn: sum)\n \n \njoin(tables: {t1: total, t2: blocked}, on: [\"_time\"])\n |> map(fn: (r) => ({ \n _value: (float(v: r._value_t2) / float(v: r._value_t1)) * 100.0\n }))\n |> last()",
# + "refId": "A"
# + }
# + ],
# + "title": "Blocked %",
# + "type": "stat"
# + },
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "fieldConfig": {
# + "defaults": {
# + "color": {
# + "mode": "thresholds"
# + },
# + "mappings": [],
# + "thresholds": {
# + "mode": "absolute",
# + "steps": [
# + {
# + "color": "green",
# + "value": null
# + },
# + {
# + "color": "red",
# + "value": 80
# + }
# + ]
# + }
# + },
# + "overrides": []
# + },
# + "gridPos": {
# + "h": 4,
# + "w": 6,
# + "x": 18,
# + "y": 0
# + },
# + "id": 8,
# + "options": {
# + "colorMode": "value",
# + "graphMode": "area",
# + "justifyMode": "auto",
# + "orientation": "auto",
# + "reduceOptions": {
# + "calcs": [
# + "lastNotNull"
# + ],
# + "fields": "",
# + "values": false
# + },
# + "textMode": "auto"
# + },
# + "pluginVersion": "9.3.6",
# + "targets": [
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "query": "// This relies on the pihole_stats measurement\n\nfrom(bucket: \"telegraf/autogen\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"pihole_stats\")\n |> filter(fn: (r) => r._field == \"domains_being_blocked\")\n |> last()",
# + "refId": "A"
# + }
# + ],
# + "title": "Domains on Blocklist",
# + "type": "stat"
# + },
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "fieldConfig": {
# + "defaults": {
# + "color": {
# + "mode": "palette-classic"
# + },
# + "custom": {
# + "axisCenteredZero": false,
# + "axisColorMode": "text",
# + "axisLabel": "",
# + "axisPlacement": "auto",
# + "barAlignment": 0,
# + "drawStyle": "line",
# + "fillOpacity": 0,
# + "gradientMode": "none",
# + "hideFrom": {
# + "legend": false,
# + "tooltip": false,
# + "viz": false
# + },
# + "lineInterpolation": "linear",
# + "lineWidth": 1,
# + "pointSize": 5,
# + "scaleDistribution": {
# + "type": "linear"
# + },
# + "showPoints": "auto",
# + "spanNulls": true,
# + "stacking": {
# + "group": "A",
# + "mode": "none"
# + },
# + "thresholdsStyle": {
# + "mode": "off"
# + }
# + },
# + "mappings": [],
# + "thresholds": {
# + "mode": "absolute",
# + "steps": [
# + {
# + "color": "green",
# + "value": null
# + },
# + {
# + "color": "red",
# + "value": 80
# + }
# + ]
# + }
# + },
# + "overrides": []
# + },
# + "gridPos": {
# + "h": 8,
# + "w": 12,
# + "x": 0,
# + "y": 4
# + },
# + "id": 10,
# + "options": {
# + "legend": {
# + "calcs": [],
# + "displayMode": "list",
# + "placement": "bottom",
# + "showLegend": true
# + },
# + "tooltip": {
# + "mode": "single",
# + "sort": "none"
# + }
# + },
# + "targets": [
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "query": "from(bucket: \"telegraf/autogen\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"pihole_clients\")\n |> filter(fn: (r) => r._field == \"total\")\n |> filter(fn: (r) => r.client == \"${client}\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean)",
# + "refId": "A"
# + }
# + ],
# + "title": "DNS Queries",
# + "type": "timeseries"
# + },
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "fieldConfig": {
# + "defaults": {
# + "color": {
# + "mode": "palette-classic"
# + },
# + "custom": {
# + "axisCenteredZero": false,
# + "axisColorMode": "text",
# + "axisLabel": "",
# + "axisPlacement": "auto",
# + "barAlignment": 0,
# + "drawStyle": "line",
# + "fillOpacity": 0,
# + "gradientMode": "none",
# + "hideFrom": {
# + "legend": false,
# + "tooltip": false,
# + "viz": false
# + },
# + "lineInterpolation": "linear",
# + "lineWidth": 1,
# + "pointSize": 5,
# + "scaleDistribution": {
# + "type": "linear"
# + },
# + "showPoints": "auto",
# + "spanNulls": true,
# + "stacking": {
# + "group": "A",
# + "mode": "none"
# + },
# + "thresholdsStyle": {
# + "mode": "off"
# + }
# + },
# + "mappings": [],
# + "thresholds": {
# + "mode": "absolute",
# + "steps": [
# + {
# + "color": "green",
# + "value": null
# + },
# + {
# + "color": "red",
# + "value": 80
# + }
# + ]
# + },
# + "unit": "q/ps"
# + },
# + "overrides": []
# + },
# + "gridPos": {
# + "h": 8,
# + "w": 12,
# + "x": 12,
# + "y": 4
# + },
# + "id": 11,
# + "options": {
# + "legend": {
# + "calcs": [],
# + "displayMode": "list",
# + "placement": "bottom",
# + "showLegend": true
# + },
# + "tooltip": {
# + "mode": "single",
# + "sort": "none"
# + }
# + },
# + "targets": [
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "query": "from(bucket: \"telegraf/autogen\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"pihole_clients\")\n |> filter(fn: (r) => r._field == \"total\")\n |> filter(fn: (r) => r.client == \"${client}\")\n |> derivative(nonNegative: true)\n |> aggregateWindow(every: v.windowPeriod, fn: mean)",
# + "refId": "A"
# + }
# + ],
# + "title": "DNS Queries Per Second",
# + "type": "timeseries"
# + },
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "description": "",
# + "fieldConfig": {
# + "defaults": {
# + "color": {
# + "mode": "palette-classic"
# + },
# + "custom": {
# + "axisCenteredZero": false,
# + "axisColorMode": "text",
# + "axisLabel": "",
# + "axisPlacement": "auto",
# + "barAlignment": 0,
# + "drawStyle": "line",
# + "fillOpacity": 0,
# + "gradientMode": "none",
# + "hideFrom": {
# + "legend": false,
# + "tooltip": false,
# + "viz": false
# + },
# + "lineInterpolation": "linear",
# + "lineWidth": 1,
# + "pointSize": 5,
# + "scaleDistribution": {
# + "type": "linear"
# + },
# + "showPoints": "auto",
# + "spanNulls": true,
# + "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": 24,
# + "x": 0,
# + "y": 12
# + },
# + "id": 12,
# + "options": {
# + "legend": {
# + "calcs": [],
# + "displayMode": "list",
# + "placement": "bottom",
# + "showLegend": true
# + },
# + "tooltip": {
# + "mode": "single",
# + "sort": "none"
# + }
# + },
# + "targets": [
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "query": "from(bucket: \"telegraf/autogen\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"pihole_clients\")\n |> filter(fn: (r) => r._field != \"total\")\n |> filter(fn: (r) => r.client == \"${client}\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean)",
# + "refId": "A"
# + }
# + ],
# + "title": "Query Statuses",
# + "type": "timeseries"
# + },
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "description": "",
# + "fieldConfig": {
# + "defaults": {
# + "color": {
# + "mode": "palette-classic"
# + },
# + "custom": {
# + "axisCenteredZero": false,
# + "axisColorMode": "text",
# + "axisLabel": "",
# + "axisPlacement": "auto",
# + "barAlignment": 0,
# + "drawStyle": "line",
# + "fillOpacity": 0,
# + "gradientMode": "none",
# + "hideFrom": {
# + "legend": false,
# + "tooltip": false,
# + "viz": false
# + },
# + "lineInterpolation": "linear",
# + "lineWidth": 1,
# + "pointSize": 5,
# + "scaleDistribution": {
# + "type": "linear"
# + },
# + "showPoints": "auto",
# + "spanNulls": true,
# + "stacking": {
# + "group": "A",
# + "mode": "none"
# + },
# + "thresholdsStyle": {
# + "mode": "off"
# + }
# + },
# + "mappings": [],
# + "thresholds": {
# + "mode": "absolute",
# + "steps": [
# + {
# + "color": "green",
# + "value": null
# + },
# + {
# + "color": "red",
# + "value": 80
# + }
# + ]
# + }
# + },
# + "overrides": []
# + },
# + "gridPos": {
# + "h": 8,
# + "w": 12,
# + "x": 0,
# + "y": 21
# + },
# + "id": 13,
# + "options": {
# + "legend": {
# + "calcs": [],
# + "displayMode": "list",
# + "placement": "bottom",
# + "showLegend": true
# + },
# + "tooltip": {
# + "mode": "single",
# + "sort": "none"
# + }
# + },
# + "targets": [
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "query": "from(bucket: \"telegraf/autogen\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"pihole_clients\")\n |> filter(fn: (r) => r._field == \"blocklisted\")\n |> filter(fn: (r) => r.client == \"${client}\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean)",
# + "refId": "A"
# + }
# + ],
# + "title": "Blocked DNS Queries",
# + "type": "timeseries"
# + },
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "fieldConfig": {
# + "defaults": {
# + "color": {
# + "mode": "palette-classic"
# + },
# + "custom": {
# + "axisCenteredZero": false,
# + "axisColorMode": "text",
# + "axisLabel": "",
# + "axisPlacement": "auto",
# + "barAlignment": 0,
# + "drawStyle": "line",
# + "fillOpacity": 0,
# + "gradientMode": "none",
# + "hideFrom": {
# + "legend": false,
# + "tooltip": false,
# + "viz": false
# + },
# + "lineInterpolation": "linear",
# + "lineWidth": 1,
# + "pointSize": 5,
# + "scaleDistribution": {
# + "type": "linear"
# + },
# + "showPoints": "auto",
# + "spanNulls": true,
# + "stacking": {
# + "group": "A",
# + "mode": "none"
# + },
# + "thresholdsStyle": {
# + "mode": "off"
# + }
# + },
# + "mappings": [],
# + "thresholds": {
# + "mode": "absolute",
# + "steps": [
# + {
# + "color": "green",
# + "value": null
# + },
# + {
# + "color": "red",
# + "value": 80
# + }
# + ]
# + },
# + "unit": "q/ps"
# + },
# + "overrides": []
# + },
# + "gridPos": {
# + "h": 8,
# + "w": 12,
# + "x": 12,
# + "y": 21
# + },
# + "id": 14,
# + "options": {
# + "legend": {
# + "calcs": [],
# + "displayMode": "list",
# + "placement": "bottom",
# + "showLegend": true
# + },
# + "tooltip": {
# + "mode": "single",
# + "sort": "none"
# + }
# + },
# + "targets": [
# + {
# + "datasource": {
# + "type": "influxdb",
# + "uid": "${DS_INFLUXDB}"
# + },
# + "query": "from(bucket: \"telegraf/autogen\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"pihole_clients\")\n |> filter(fn: (r) => r._field == \"blocklisted\")\n |> filter(fn: (r) => r.client == \"${client}\")\n |> derivative(nonNegative: true)\n |> aggregateWindow(every: v.windowPeriod, fn: mean)",
# + "refId": "A"
# + }
# + ],
# + "title": "Blocked DNS Queries Per Second",
# + "type": "timeseries"
# + }
# + ],
# + "schemaVersion": 37,
# + "style": "dark",
# + "tags": [
# + "pihole",
# + "dns",
# + "system"
# + ],
# + "templating": {
# + "list": [
# + {
# + "current": {},
# + "definition": "from(bucket: \"telegraf\")\n|> range(start: v.timeRangeStart)\n|> filter(fn: (r) => r._measurement == \"pihole_clients\")\n|> keyValues(keyColumns: [\"client\"])\n|> group()\n|> keep(columns: [\"_value\"])\n",
# + "hide": 0,
# + "includeAll": false,
# + "multi": false,
# + "name": "client",
# + "options": [],
# + "query": "from(bucket: \"telegraf\")\n|> range(start: v.timeRangeStart)\n|> filter(fn: (r) => r._measurement == \"pihole_clients\")\n|> keyValues(keyColumns: [\"client\"])\n|> group()\n|> keep(columns: [\"_value\"])\n",
# + "refresh": 1,
# + "regex": "",
# + "skipUrlSync": false,
# + "sort": 5,
# + "type": "query"
# + }
# + ]
# + },
# + "time": {
# + "from": "now-12h",
# + "to": "now"
# + },
# + "timepicker": {},
# + "timezone": "",
# + "title": "Pi-Hole Per Client",
# + "uid": "gJTfg8L4z",
# + "version": 8,
# + "weekStart": ""
# +}
# \ No newline at end of file
#
diff --git a/pihole-granular-stats/grafana/screenshot.png b/pihole-granular-stats/grafana/screenshot.png
--- a/pihole-granular-stats/grafana/screenshot.png
+++ b/pihole-granular-stats/grafana/screenshot.png
# Binary files /dev/null and b/pihole-granular-stats/grafana/screenshot.png differ
#
diff --git a/pihole-granular-stats/pihole-granular-stats.py b/pihole-granular-stats/pihole-granular-stats.py
--- a/pihole-granular-stats/pihole-granular-stats.py
+++ b/pihole-granular-stats/pihole-granular-stats.py
# @@ -0,0 +1,156 @@
# +#!/usr/bin/env python3
# +#
# +# Telegraf plugin to collect logs from
# +# pihole and generate per-client stats
# +#
# +# Copyright (c) 2023 B Tasker
# +# Released under GNU GPL v3 - https://www.gnu.org/licenses/gpl-3.0.txt
# +#
# +
# +import requests
# +import sys
# +from datetime import datetime, timedelta
# +
# +# Pihole connection info
# +PIHOLE_ADDRESS="http://127.0.0.1:8080"
# +
# +# Auth token - get this from settings
# +PIHOLE_TOKEN=""
# +
# +# How many minutes of logs to query on each
# +# iteration
# +QUERY_TIME_RANGE=15
# +
# +# The measurement name to use
# +MEASUREMENT="pihole_clients"
# +
# +
# +
# +def getQueryLogs(pihole_addr, token, time_range):
# + ''' Build a request to the Pihole API to get query logs for the
# + specified time range
# + '''
# + # Calculate the time bounds
# + now = datetime.now()
# + end_s = now.strftime('%s')
# +
# + # Calculate the startt time and round it to the
# + # nearest full minute
# + start = now - timedelta(minutes = time_range)
# + start_str = start.strftime('%s')
# + start_s = int(int(start_str)//60 * 60)
# +
# + # Build the request
# + args = {
# + "from" : start_s,
# + "until" : end_s,
# + "auth" : token
# + }
# + url = f"{pihole_addr}/admin/api.php?getAllQueries"
# +
# + # Place and return
# + r = requests.get(url=url, params=args)
# + return r.json()
# +
# +
# +def queryLogToStats(queries):
# + ''' Iterate over the query log results and build a stats object
# +
# + '''
# + # Define the structures that we'll collect stats into
# + answer_types = {
# + "blocklisted" : 0,
# + "forwarded" : 0,
# + "cachedresponse" : 0,
# + "wildcardblock" : 0,
# + "total" : 0
# + }
# +
# + counters = {
# + "clients" : {}
# + }
# +
# + results = {}
# +
# + # Iterate over the result set
# + for row in queries['data']:
# + # Round the timestamp to the nearest minute
# + ts = int(int(row[0])//60 * 60)
# +
# + # Client etc
# + client = row[3]
# + resp_type = int(row[4])
# +
# + # Translate the response type into a string
# + if resp_type == 1:
# + answer_type = "blocklisted"
# + elif resp_type == 2:
# + answer_type = "forwarded"
# + elif resp_type == 3:
# + answer_type = "cachedresponse"
# + elif resp_type == 4:
# + answer_type = "wildcardblock"
# +
# + # Populate the stats objects if not already there
# + ts_key = ts
# + if ts_key not in results:
# + results[ts_key] = counters.copy()
# +
# + if client not in results[ts_key]["clients"]:
# + results[ts_key]["clients"][client] = answer_types.copy()
# +
# + # Increment the relevant counters
# + results[ts_key]["clients"][client][answer_type] += 1
# + results[ts_key]["clients"][client]["total"] += 1
# +
# + return results
# +
# +
# +def statsBlockToLP(measurement, block, client, timestamp):
# + ''' Convert an "answer_types" stats block to line protocol
# + '''
# + lps = [
# + measurement,
# + f"client={client}"
# + ]
# +
# + lp1 = ','.join(lps)
# +
# + fields = []
# +
# + for answer_type in block:
# + fields.append(f"{answer_type}={block[answer_type]}i")
# +
# + lp2 = ','.join(fields)
# + lp = " ".join([lp1, lp2, str(timestamp * 1000000000)])
# + return lp
# +
# +
# +def statsToLP(measurement, stats):
# + ''' Iterate over the stats object creating line protocol for
# + each client and timestamp pair
# + '''
# + lines = []
# + # Iterate over each timestamp grouping
# + for timestamp in stats:
# + # Iterate over the clients
# + for client in stats[timestamp]["clients"]:
# + lp = statsBlockToLP(measurement, stats[timestamp]["clients"][client], client, timestamp)
# + lines.append(lp)
# +
# + return lines
# +
# +
# +if __name__ == "__main__":
# + # Get a list of queries
# + queries = getQueryLogs(PIHOLE_ADDRESS, PIHOLE_TOKEN, QUERY_TIME_RANGE)
# +
# + if "data" not in queries:
# + # Request failed
# + sys.exit(1)
# +
# +
# + stats = queryLogToStats(queries)
# +
# + lp_lines = statsToLP(MEASUREMENT, stats)
# + print('\n'.join(lp_lines))
#