Added latest metrics available

This commit is contained in:
Vincent Composieux 2019-05-09 21:11:31 +02:00
parent 021de28520
commit 68f0032961
No known key found for this signature in database
GPG key ID: 7306974ABA6382AC
7 changed files with 265 additions and 82 deletions

View file

@ -12,20 +12,6 @@ This is a Prometheus exporter for [PI-Hole](https://pi-hole.net/)'s Raspberry PI
## Installation
### From sources
First, retrieve the project:
```bash
$ go get -u github.com/eko/pihole-exporter
# or
$ git clone https://github.com/eko/pihole-exporter.git
```
Then, build the binary (here, an example to run on Raspberry PI ARM architecture):
```bash
$ GOOS=linux GOARCH=arm GOARM=7 go build -o pihole_exporter .
```
### Download binary
You can also download the latest version of the binary built for your architecture here:
@ -44,6 +30,20 @@ You can also download the latest version of the binary built for your architectu
[Linux](https://github.com/eko/pihole-exporter/releases/latest/download/pihole_exporter-linux-arm)
]
### From sources
First, you have to retrieve the project sources by using one of the following way:
```bash
$ go get -u github.com/eko/pihole-exporter
# or
$ git clone https://github.com/eko/pihole-exporter.git
```
Then, build the binary (here, an example to run on Raspberry PI ARM architecture):
```bash
$ GOOS=linux GOARCH=arm GOARM=7 go build -o pihole_exporter .
```
## Usage
In order to run the exporter, type the following command (arguments are optional):
@ -51,29 +51,37 @@ In order to run the exporter, type the following command (arguments are optional
```bash
$ ./pihole_exporter -pihole_hostname 192.168.1.10 -pihole_password azerty
2019/05/09 09:32:19 ------------------------------------
2019/05/09 09:32:19 - PI-Hole exporter configuration -
2019/05/09 09:32:19 ------------------------------------
2019/05/09 09:32:19 PIHoleHostname : 192.168.1.10
2019/05/09 09:32:19 PIHolePassword : azerty
2019/05/09 09:32:19 Port : 9311
2019/05/09 09:32:19 Interval : 10s
2019/05/09 09:32:19 ------------------------------------
2019/05/09 09:32:19 New prometheus metric registered: Desc{fqName: "pihole_domains_being_blocked", help: "This represent the number of domains being blocked", constLabels: {}, variableLabels: []}
2019/05/09 09:32:19 New prometheus metric registered: Desc{fqName: "pihole_dns_queries_today", help: "This represent the number of DNS queries made over the current day", constLabels: {}, variableLabels: []}
2019/05/09 09:32:19 New prometheus metric registered: Desc{fqName: "pihole_ads_blocked_today", help: "This represent the number of ads blocked over the current day", constLabels: {}, variableLabels: []}
2019/05/09 09:32:19 New prometheus metric registered: Desc{fqName: "pihole_ads_percentage_today", help: "This represent the percentage of ads blocked over the current day", constLabels: {}, variableLabels: []}
2019/05/09 09:32:19 New prometheus metric registered: Desc{fqName: "pihole_unique_domains", help: "This represent the number of unique domains seen", constLabels: {}, variableLabels: []}
2019/05/09 09:32:19 New prometheus metric registered: Desc{fqName: "pihole_queries_forwarded", help: "This represent the number of queries forwarded", constLabels: {}, variableLabels: []}
2019/05/09 09:32:19 New prometheus metric registered: Desc{fqName: "pihole_queries_cached", help: "This represent the number of queries cached", constLabels: {}, variableLabels: []}
2019/05/09 09:32:19 New prometheus metric registered: Desc{fqName: "pihole_clients_ever_seen", help: "This represent the number of clients ever seen", constLabels: {}, variableLabels: []}
2019/05/09 09:32:19 New prometheus metric registered: Desc{fqName: "pihole_unique_clients", help: "This represent the number of unique clients seen", constLabels: {}, variableLabels: []}
2019/05/09 09:32:19 New prometheus metric registered: Desc{fqName: "pihole_dns_queries_all_types", help: "This represent the number of DNS queries made for all types", constLabels: {}, variableLabels: []}
2019/05/09 09:32:19 Starting HTTP server
2019/05/09 20:19:52 ------------------------------------
2019/05/09 20:19:52 - PI-Hole exporter configuration -
2019/05/09 20:19:52 ------------------------------------
2019/05/09 20:19:52 PIHoleHostname : 192.168.1.10
2019/05/09 20:19:52 PIHolePassword : azerty
2019/05/09 20:19:52 Port : 9311
2019/05/09 20:19:52 Interval : 10s
2019/05/09 20:19:52 ------------------------------------
2019/05/09 20:19:52 New Prometheus metric registered: domains_blocked
2019/05/09 20:19:52 New Prometheus metric registered: dns_queries_today
2019/05/09 20:19:52 New Prometheus metric registered: ads_blocked_today
2019/05/09 20:19:52 New Prometheus metric registered: ads_percentag_today
2019/05/09 20:19:52 New Prometheus metric registered: unique_domains
2019/05/09 20:19:52 New Prometheus metric registered: queries_forwarded
2019/05/09 20:19:52 New Prometheus metric registered: queries_cached
2019/05/09 20:19:52 New Prometheus metric registered: clients_ever_seen
2019/05/09 20:19:52 New Prometheus metric registered: unique_clients
2019/05/09 20:19:52 New Prometheus metric registered: dns_queries_all_types
2019/05/09 20:19:52 New Prometheus metric registered: reply
2019/05/09 20:19:52 New Prometheus metric registered: top_queries
2019/05/09 20:19:52 New Prometheus metric registered: top_ads
2019/05/09 20:19:52 New Prometheus metric registered: top_sources
2019/05/09 20:19:52 New Prometheus metric registered: forward_destinations
2019/05/09 20:19:52 New Prometheus metric registered: querytypes
2019/05/09 20:19:52 New Prometheus metric registered: status
2019/05/09 20:19:52 Starting HTTP server
2019/05/09 20:19:54 New tick of statistics: 648 ads blocked / 66796 total DNS querie
...
```
## Available options
## Available CLI options
```bash
# Interval of time the exporter will fetch data from PI-Hole
-interval duration (optional) (default 10s)
@ -87,3 +95,25 @@ $ ./pihole_exporter -pihole_hostname 192.168.1.10 -pihole_password azerty
# Port to be used for the exporter
-port string (optional) (default "9311")
```
## Available Prometheus metrics
| Metric name | Description |
|:----------------------------:|-------------------------------------------------------------------------------------------|
| pihole_domains_being_blocked | This represent the number of domains being blocked |
| pihole_dns_queries_today | This represent the number of DNS queries made over the current day |
| pihole_ads_blocked_today | This represent the number of ads blocked over the current day |
| pihole_ads_percentage_today | This represent the percentage of ads blocked over the current day |
| pihole_unique_domains | This represent the number of unique domains seen |
| pihole_queries_forwarded | This represent the number of queries forwarded |
| pihole_queries_cached | This represent the number of queries cached |
| pihole_clients_ever_seen | This represent the number of clients ever seen |
| pihole_unique_clients | This represent the number of unique clients seen |
| pihole_dns_queries_all_types | This represent the number of DNS queries made for all types |
| pihole_reply | This represent the number of replies made for all types |
| pihole_top_queries | This represent the number of top queries made by PI-Hole by domain |
| pihole_top_ads | This represent the number of top ads made by PI-Hole by domain |
| pihole_top_sources | This represent the number of top sources requests made by PI-Hole by source host |
| pihole_forward_destinations | This represent the number of forward destinations requests made by PI-Hole by destination |
| pihole_querytypes | This represent the number of queries made by PI-Hole by type |
| pihole_status | This represent if PI-Hole is enabled |

View file

@ -13,6 +13,7 @@ import (
"github.com/heetch/confita/backend/flags"
)
// Config is the exporter CLI configuration.
type Config struct {
PIHoleHostname string `config:"pihole_hostname"`
PIHolePassword string `config:"pihole_password"`
@ -31,6 +32,7 @@ func getDefaultConfig() *Config {
}
}
// Load method loads the configuration by using both flag or environment variables.
func Load() *Config {
loaders := []backend.Backend{
env.NewBackend(),

View file

@ -8,111 +8,198 @@ import (
var (
// DomainsBlocked - The number of domains being blocked by PI-Hole.
DomainsBlocked = prometheus.NewGauge(
DomainsBlocked = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "domains_being_blocked",
Namespace: "pihole",
Help: "This represent the number of domains being blocked",
},
[]string{"hostname"},
)
// DNSQueriesToday - The number of DNS requests made over PI-Hole over the current day.
DNSQueriesToday = prometheus.NewGauge(
DNSQueriesToday = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "dns_queries_today",
Namespace: "pihole",
Help: "This represent the number of DNS queries made over the current day",
},
[]string{"hostname"},
)
// AdsBlockedToday - The number of ads blocked by PI-Hole over the current day.
AdsBlockedToday = prometheus.NewGauge(
AdsBlockedToday = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "ads_blocked_today",
Namespace: "pihole",
Help: "This represent the number of ads blocked over the current day",
},
[]string{"hostname"},
)
// AdsPercentageToday - The percentage of ads blocked by PI-Hole over the current day.
AdsPercentageToday = prometheus.NewGauge(
AdsPercentageToday = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "ads_percentage_today",
Namespace: "pihole",
Help: "This represent the percentage of ads blocked over the current day",
},
[]string{"hostname"},
)
// UniqueDomains - The number of unique domains seen by PI-Hole.
UniqueDomains = prometheus.NewGauge(
UniqueDomains = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "unique_domains",
Namespace: "pihole",
Help: "This represent the number of unique domains seen",
},
[]string{"hostname"},
)
// QueriesForwarded - The number of queries forwarded by PI-Hole.
QueriesForwarded = prometheus.NewGauge(
QueriesForwarded = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "queries_forwarded",
Namespace: "pihole",
Help: "This represent the number of queries forwarded",
},
[]string{"hostname"},
)
// QueriesCached - The number of queries cached by PI-Hole.
QueriesCached = prometheus.NewGauge(
QueriesCached = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "queries_cached",
Namespace: "pihole",
Help: "This represent the number of queries cached",
},
[]string{"hostname"},
)
// ClientsEverSeen - The number of clients ever seen by PI-Hole.
ClientsEverSeen = prometheus.NewGauge(
ClientsEverSeen = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "clients_ever_seen",
Namespace: "pihole",
Help: "This represent the number of clients ever seen",
},
[]string{"hostname"},
)
// UniqueClients - The number of unique clients seen by PI-Hole.
UniqueClients = prometheus.NewGauge(
UniqueClients = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "unique_clients",
Namespace: "pihole",
Help: "This represent the number of unique clients seen",
},
[]string{"hostname"},
)
// DnsQueriesAllTypes - The number of DNS queries made for all types by PI-Hole.
DnsQueriesAllTypes = prometheus.NewGauge(
// DNSQueriesAllTypes - The number of DNS queries made for all types by PI-Hole.
DNSQueriesAllTypes = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "dns_queries_all_types",
Namespace: "pihole",
Help: "This represent the number of DNS queries made for all types",
},
[]string{"hostname"},
)
// Reply - The number of replies made for every types by PI-Hole.
Reply = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "reply",
Namespace: "pihole",
Help: "This represent the number of replies made for all types",
},
[]string{"hostname", "type"},
)
// TopQueries - The number of top queries made by PI-Hole by domain.
TopQueries = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_queries",
Namespace: "pihole",
Help: "This represent the number of top queries made by PI-Hole by domain",
},
[]string{"hostname", "domain"},
)
// TopAds - The number of top ads made by PI-Hole by domain.
TopAds = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_ads",
Namespace: "pihole",
Help: "This represent the number of top ads made by PI-Hole by domain",
},
[]string{"hostname", "domain"},
)
// TopSources - The number of top sources requests made by PI-Hole by source host.
TopSources = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_sources",
Namespace: "pihole",
Help: "This represent the number of top sources requests made by PI-Hole by source host",
},
[]string{"hostname", "source"},
)
// ForwardDestinations - The number of forward destinations requests made by PI-Hole by destination.
ForwardDestinations = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "forward_destinations",
Namespace: "pihole",
Help: "This represent the number of forward destinations requests made by PI-Hole by destination",
},
[]string{"hostname", "destination"},
)
// QueryTypes - The number of queries made by PI-Hole by type.
QueryTypes = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "querytypes",
Namespace: "pihole",
Help: "This represent the number of queries made by PI-Hole by type",
},
[]string{"hostname", "type"},
)
// Status - Is PI-Hole enabled?
Status = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "status",
Namespace: "pihole",
Help: "This if PI-Hole is enabled",
},
[]string{"hostname"},
)
)
// Init initializes Prometheus metrics
// Init initializes all Prometheus metrics made available by PI-Hole exporter.
func Init() {
initMetric(DomainsBlocked)
initMetric(DNSQueriesToday)
initMetric(AdsBlockedToday)
initMetric(AdsPercentageToday)
initMetric(UniqueDomains)
initMetric(QueriesForwarded)
initMetric(QueriesCached)
initMetric(ClientsEverSeen)
initMetric(UniqueClients)
initMetric(DnsQueriesAllTypes)
initMetric("domains_blocked", DomainsBlocked)
initMetric("dns_queries_today", DNSQueriesToday)
initMetric("ads_blocked_today", AdsBlockedToday)
initMetric("ads_percentag_today", AdsPercentageToday)
initMetric("unique_domains", UniqueDomains)
initMetric("queries_forwarded", QueriesForwarded)
initMetric("queries_cached", QueriesCached)
initMetric("clients_ever_seen", ClientsEverSeen)
initMetric("unique_clients", UniqueClients)
initMetric("dns_queries_all_types", DNSQueriesAllTypes)
initMetric("reply", Reply)
initMetric("top_queries", TopQueries)
initMetric("top_ads", TopAds)
initMetric("top_sources", TopSources)
initMetric("forward_destinations", ForwardDestinations)
initMetric("querytypes", QueryTypes)
initMetric("status", Status)
}
func initMetric(metric prometheus.Gauge) {
func initMetric(name string, metric *prometheus.GaugeVec) {
prometheus.MustRegister(metric)
log.Printf("New prometheus metric registered: %s", metric.Desc().String())
log.Printf("New Prometheus metric registered: %s", name)
}

View file

@ -20,6 +20,7 @@ var (
statsURLPattern = "http://%s/admin/api.php?summaryRaw&overTimeData&topItems&recentItems&getQueryTypes&getForwardDestinations&getQuerySources&jsonForceObject"
)
// Client struct is a PI-Hole client to request an instance of a PI-Hole ad blocker.
type Client struct {
hostname string
password string
@ -27,6 +28,7 @@ type Client struct {
httpClient http.Client
}
// NewClient method initializes a new PI-Hole client.
func NewClient(hostname, password string, interval time.Duration) *Client {
return &Client{
hostname: hostname,
@ -40,7 +42,9 @@ func NewClient(hostname, password string, interval time.Duration) *Client {
}
}
func (c *Client) Fetch() {
// Scrape method logins and retrieves statistics from PI-Hole JSON API
// and then pass them as Prometheus metrics.
func (c *Client) Scrape() {
for range time.Tick(c.interval) {
sessionID := c.getPHPSessionID()
if sessionID == nil {
@ -49,19 +53,53 @@ func (c *Client) Fetch() {
}
stats := c.getStatistics(*sessionID)
c.setMetrics(stats)
log.Println("New tick of statistics", stats)
log.Printf("New tick of statistics: %s", stats.ToString())
}
}
metrics.DomainsBlocked.Set(float64(stats.DomainsBeingBlocked))
metrics.DNSQueriesToday.Set(float64(stats.DNSQueriesToday))
metrics.AdsBlockedToday.Set(float64(stats.AdsBlockedToday))
metrics.AdsPercentageToday.Set(float64(stats.AdsPercentageToday))
metrics.UniqueDomains.Set(float64(stats.UniqueDomains))
metrics.QueriesForwarded.Set(float64(stats.QueriesForwarded))
metrics.QueriesCached.Set(float64(stats.QueriesCached))
metrics.ClientsEverSeen.Set(float64(stats.ClientsEverSeen))
metrics.UniqueClients.Set(float64(stats.UniqueClients))
metrics.DnsQueriesAllTypes.Set(float64(stats.DnsQueriesAllTypes))
func (c *Client) setMetrics(stats *Stats) {
metrics.DomainsBlocked.WithLabelValues(c.hostname).Set(float64(stats.DomainsBeingBlocked))
metrics.DNSQueriesToday.WithLabelValues(c.hostname).Set(float64(stats.DNSQueriesToday))
metrics.AdsBlockedToday.WithLabelValues(c.hostname).Set(float64(stats.AdsBlockedToday))
metrics.AdsPercentageToday.WithLabelValues(c.hostname).Set(float64(stats.AdsPercentageToday))
metrics.UniqueDomains.WithLabelValues(c.hostname).Set(float64(stats.UniqueDomains))
metrics.QueriesForwarded.WithLabelValues(c.hostname).Set(float64(stats.QueriesForwarded))
metrics.QueriesCached.WithLabelValues(c.hostname).Set(float64(stats.QueriesCached))
metrics.ClientsEverSeen.WithLabelValues(c.hostname).Set(float64(stats.ClientsEverSeen))
metrics.UniqueClients.WithLabelValues(c.hostname).Set(float64(stats.UniqueClients))
metrics.DNSQueriesAllTypes.WithLabelValues(c.hostname).Set(float64(stats.DNSQueriesAllTypes))
metrics.Reply.WithLabelValues(c.hostname, "no_data").Set(float64(stats.ReplyNoData))
metrics.Reply.WithLabelValues(c.hostname, "nx_domain").Set(float64(stats.ReplyNxDomain))
metrics.Reply.WithLabelValues(c.hostname, "cname").Set(float64(stats.ReplyCname))
metrics.Reply.WithLabelValues(c.hostname, "ip").Set(float64(stats.ReplyIP))
var isEnabled int = 0
if stats.Status == enabledStatus {
isEnabled = 1
}
metrics.Status.WithLabelValues(c.hostname).Set(float64(isEnabled))
for domain, value := range stats.TopQueries {
metrics.TopQueries.WithLabelValues(c.hostname, domain).Set(float64(value))
}
for domain, value := range stats.TopAds {
metrics.TopAds.WithLabelValues(c.hostname, domain).Set(float64(value))
}
for source, value := range stats.TopSources {
metrics.TopSources.WithLabelValues(c.hostname, source).Set(float64(value))
}
for destination, value := range stats.ForwardDestinations {
metrics.ForwardDestinations.WithLabelValues(c.hostname, destination).Set(value)
}
for queryType, value := range stats.QueryTypes {
metrics.QueryTypes.WithLabelValues(c.hostname, queryType).Set(value)
}
}

View file

@ -1,15 +1,36 @@
package pihole
// Stats is the PI-Hole statistics JSON API corresponding model
import "fmt"
const (
enabledStatus = "enabled"
)
// Stats struct is the PI-Hole statistics JSON API corresponding model.
type Stats struct {
DomainsBeingBlocked int `json:"domains_being_blocked"`
DNSQueriesToday int `json:"dns_queries_today"`
AdsBlockedToday int `json:"ads_blocked_today"`
AdsPercentageToday float64 `json:"ads_percentage_today"`
UniqueDomains int `json:"unique_domains"`
QueriesForwarded int `json:"queries_forwarded"`
QueriesCached int `json:"queries_cached"`
ClientsEverSeen int `json:"clients_ever_seen"`
UniqueClients int `json:"unique_clients"`
DnsQueriesAllTypes int `json:"dns_queries_all_types"`
DomainsBeingBlocked int `json:"domains_being_blocked"`
DNSQueriesToday int `json:"dns_queries_today"`
AdsBlockedToday int `json:"ads_blocked_today"`
AdsPercentageToday float64 `json:"ads_percentage_today"`
UniqueDomains int `json:"unique_domains"`
QueriesForwarded int `json:"queries_forwarded"`
QueriesCached int `json:"queries_cached"`
ClientsEverSeen int `json:"clients_ever_seen"`
UniqueClients int `json:"unique_clients"`
DNSQueriesAllTypes int `json:"dns_queries_all_types"`
ReplyNoData int `json:"reply_NODATA"`
ReplyNxDomain int `json:"reply_NXDOMAIN"`
ReplyCname int `json:"reply_CNAME"`
ReplyIP int `json:"reply_IP"`
TopQueries map[string]int `json:"top_queries"`
TopAds map[string]int `json:"top_ads"`
TopSources map[string]int `json:"top_sources"`
ForwardDestinations map[string]float64 `json:"forward_destinations"`
QueryTypes map[string]float64 `json:"querytypes"`
Status string `json:"status"`
}
// ToString method returns a string of the current statistics struct.
func (s *Stats) ToString() string {
return fmt.Sprintf("%d ads blocked / %d total DNS queries", s.AdsBlockedToday, s.DNSQueriesAllTypes)
}

View file

@ -9,10 +9,13 @@ import (
"golang.org/x/net/context"
)
// Server is the struct for the HTTP server.
type Server struct {
httpServer *http.Server
}
// NewServer method initializes a new HTTP server instance and associates
// the different routes that will be used by Prometheus (metrics) or for monitoring (readiness, liveness).
func NewServer(port string) *Server {
mux := http.NewServeMux()
httpServer := &http.Server{Addr: ":" + port, Handler: mux}
@ -28,6 +31,7 @@ func NewServer(port string) *Server {
return s
}
// ListenAndServe method serves HTTP requests.
func (s *Server) ListenAndServe() {
log.Println("Starting HTTP server")
@ -37,6 +41,7 @@ func (s *Server) ListenAndServe() {
}
}
// Stop method stops the HTTP server (so the exporter become unavailable).
func (s *Server) Stop() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

View file

@ -34,7 +34,7 @@ func main() {
func initPiholeClient(hostname, password string, interval time.Duration) {
client := pihole.NewClient(hostname, password, interval)
go client.Fetch()
go client.Scrape()
}
func initHttpServer(port string) {