Added more metrics. HTTP protocol only supported

This commit is contained in:
Eldwan Brianne 2020-11-02 18:18:08 +01:00
parent 7cbc8b8893
commit 86c12b3921
4 changed files with 217 additions and 108 deletions

View File

@ -1,113 +1,123 @@
package adguard
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"time"
b64 "encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"time"
"github.com/ebrianne/adguard-exporter/internal/metrics"
"github.com/ebrianne/adguard-exporter/internal/metrics"
)
var (
statsURLPattern = "%s://%s:%d/control/stats"
statsURLPattern = "%s://%s:%d/control/stats"
)
// Client struct is a AdGuard client to request an instance of a AdGuard ad blocker.
type Client struct {
httpClient http.Client
interval time.Duration
protocol string
hostname string
port uint16
username string
password string
sessionID string
httpClient http.Client
interval time.Duration
protocol string
hostname string
port uint16
b64password string
}
// NewClient method initializes a new AdGuard client.
func NewClient(protocol, hostname string, port uint16, username, password string, interval time.Duration) *Client {
if protocol != "http" && protocol != "https" {
log.Printf("protocol %s is invalid. Must be http or https.", protocol)
os.Exit(1)
}
func NewClient(protocol, hostname string, port uint16, b64password string, interval time.Duration) *Client {
if protocol != "http" {
log.Printf("protocol %s is invalid. Must be http.", protocol)
os.Exit(1)
}
return &Client{
protocol: protocol,
hostname: hostname,
port: port,
username: username,
password: password,
interval: interval,
httpClient: http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
},
}
return &Client{
protocol: protocol,
hostname: hostname,
port: port,
b64password: b64password,
interval: interval,
httpClient: http.Client{},
}
}
// Scrape method authenticates and retrieves statistics from AdGuard JSON API
// and then pass them as Prometheus metrics.
func (c *Client) Scrape() {
for range time.Tick(c.interval) {
stats := c.getStatistics()
for range time.Tick(c.interval) {
stats := c.getStatistics()
c.setMetrics(stats)
c.setMetrics(stats)
log.Printf("New tick of statistics: %s", stats.ToString())
}
log.Printf("New tick of statistics: %s", stats.ToString())
}
}
func (c *Client) setMetrics(stats *Stats) {
metrics.AvgProcessingTime.WithLabelValues(c.hostname).Set(float64(stats.AvgProcessingTime))
metrics.AvgProcessingTime.WithLabelValues(c.hostname).Set(float64(stats.AvgProcessingTime))
metrics.DnsQueries.WithLabelValues(c.hostname).Set(float64(stats.DnsQueries))
metrics.BlockedFiltering.WithLabelValues(c.hostname).Set(float64(stats.BlockedFiltering))
metrics.ParentalFiltering.WithLabelValues(c.hostname).Set(float64(stats.ParentalFiltering))
metrics.SafeBrowsingFiltering.WithLabelValues(c.hostname).Set(float64(stats.SafeBrowsingFiltering))
metrics.SafeSearchFiltering.WithLabelValues(c.hostname).Set(float64(stats.SafeSearchFiltering))
for l := range stats.TopQueries {
for domain, value := range stats.TopQueries[l] {
metrics.TopQueries.WithLabelValues(c.hostname, domain).Set(float64(value))
}
}
for l := range stats.TopBlocked {
for domain, value := range stats.TopBlocked[l] {
metrics.TopBlocked.WithLabelValues(c.hostname, domain).Set(float64(value))
}
}
for l := range stats.TopClients {
for source, value := range stats.TopClients[l] {
metrics.TopClients.WithLabelValues(c.hostname, source).Set(float64(value))
}
}
}
func (c *Client) getStatistics() *Stats {
var stats Stats
var stats Stats
statsURL := fmt.Sprintf(statsURLPattern, c.protocol, c.hostname, c.port)
statsURL := fmt.Sprintf(statsURLPattern, c.protocol, c.hostname, c.port)
req, err := http.NewRequest("GET", statsURL, nil)
if err != nil {
log.Fatal("An error has occured when creating HTTP statistics request", err)
}
req, err := http.NewRequest("GET", statsURL, nil)
if err != nil {
log.Fatal("An error has occured when creating HTTP statistics request ", err)
}
if c.isUsingPassword() {
c.authenticateRequest(req)
}
if c.isUsingPassword() {
c.authenticateRequest(req)
}
resp, err := c.httpClient.Do(req)
if err != nil {
log.Printf("An error has occured during login to Adguard: %v", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("Unable to read Adguard statistics HTTP response", err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("Unable to read Adguard statistics HTTP response", err)
}
err = json.Unmarshal(body, &stats)
if err != nil {
log.Println("Unable to unmarshal Adguard statistics to statistics struct model", err)
}
err = json.Unmarshal(body, &stats)
if err != nil {
log.Println("Unable to unmarshal Adguard statistics to statistics struct model", err)
}
return &stats
}
func (c *Client) basicAuth() string {
auth := c.username + ":" + c.password
return base64.StdEncoding.EncodeToString([]byte(auth))
return &stats
}
func (c *Client) isUsingPassword() bool {
return len(c.password) > 0
return len(c.b64password) > 0
}
func (c *Client) authenticateRequest(req *http.Request) {
req.Header.Add("Authorization", "Basic " + c.basicAuth())
req.Header.Add("Authorization", "Basic " + c.b64password)
}

View File

@ -3,14 +3,22 @@ package adguard
import "fmt"
const (
enabledStatus = "enabled"
enabledStatus = "enabled"
)
type Stats struct {
AvgProcessingTime float64 `json:"avg_processing_time"`
AvgProcessingTime float64 `json:"avg_processing_time"`
DnsQueries int `json:"num_dns_queries"`
BlockedFiltering int `json:"num_blocked_filtering"`
ParentalFiltering int `json:"num_replaced_parental"`
SafeBrowsingFiltering int `json:"num_replaced_safebrowsing"`
SafeSearchFiltering int `json:"num_replaced_safesearch"`
TopQueries []map[string]int `json:"top_queried_domains"`
TopBlocked []map[string]int `json:"top_blocked_domains"`
TopClients []map[string]int `json:"top_clients"`
}
// ToString method returns a string of the current statistics struct.
func (s *Stats) ToString() string {
return fmt.Sprintf("Average Processing Time %d s", s.AvgProcessingTime)
return fmt.Sprintf("%d ads blocked / %d total DNS queries", s.BlockedFiltering, s.DnsQueries)
}

View File

@ -1,28 +1,108 @@
package metrics
import (
"log"
"log"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus"
)
var (
AvgProcessingTime = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "avg_processing_time",
Namespace: "adguard",
Help: "This represent the average processing time for a DNS query in s",
},
[]string{"hostname"},
)
AvgProcessingTime = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "avg_processing_time",
Namespace: "adguard",
Help: "This represent the average processing time for a DNS query in s",
},
[]string{"hostname"},
)
DnsQueries = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_dns_queries",
Namespace: "adguard",
Help: "Number of DNS queries",
},
[]string{"hostname"},
)
BlockedFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_blocked_filtering",
Namespace: "adguard",
Help: "This represent the number of domains blocked",
},
[]string{"hostname"},
)
ParentalFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_replaced_parental",
Namespace: "adguard",
Help: "This represent the number of domains blocked (parental)",
},
[]string{"hostname"},
)
SafeBrowsingFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_replaced_safebrowsing",
Namespace: "adguard",
Help: "This represent the number of domains blocked (safe browsing)",
},
[]string{"hostname"},
)
SafeSearchFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_replaced_safesearch",
Namespace: "adguard",
Help: "This represent the number of domains blocked (safe search)",
},
[]string{"hostname"},
)
TopQueries = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_queried_domains",
Namespace: "adguard",
Help: "This represent the top queried domains",
},
[]string{"hostname", "domain"},
)
TopBlocked = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_blocked_domains",
Namespace: "adguard",
Help: "This represent the top bloacked domains",
},
[]string{"hostname", "domain"},
)
TopClients = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_clients",
Namespace: "adguard",
Help: "This represent the top clients",
},
[]string{"hostname", "client"},
)
)
// Init initializes all Prometheus metrics made available by AdGuard exporter.
func Init() {
initMetric("avg_processing_time", AvgProcessingTime)
initMetric("avg_processing_time", AvgProcessingTime)
initMetric("num_dns_queries", DnsQueries)
initMetric("num_blocked_filtering", BlockedFiltering)
initMetric("num_replaced_parental", ParentalFiltering)
initMetric("num_replaced_safebrowsing", SafeBrowsingFiltering)
initMetric("num_replaced_safesearch", SafeSearchFiltering)
initMetric("top_queried_domains", TopQueries)
initMetric("top_blocked_domains", TopBlocked)
initMetric("top_clients", TopClients)
}
func initMetric(name string, metric *prometheus.GaugeVec) {
prometheus.MustRegister(metric)
log.Printf("New Prometheus metric registered: %s", name)
prometheus.MustRegister(metric)
log.Printf("New Prometheus metric registered: %s", name)
}

61
main.go
View File

@ -1,53 +1,64 @@
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"encoding/base64"
"github.com/ebrianne/adguard-exporter/config"
"github.com/ebrianne/adguard-exporter/internal/metrics"
"github.com/ebrianne/adguard-exporter/internal/adguard"
"github.com/ebrianne/adguard-exporter/internal/server"
"github.com/ebrianne/adguard-exporter/config"
"github.com/ebrianne/adguard-exporter/internal/metrics"
"github.com/ebrianne/adguard-exporter/internal/adguard"
"github.com/ebrianne/adguard-exporter/internal/server"
)
const (
name = "adguard-exporter"
name = "adguard-exporter"
)
var (
s *server.Server
s *server.Server
)
func main() {
conf := config.Load()
conf := config.Load()
metrics.Init()
metrics.Init()
initAdguardClient(conf.AdguardProtocol, conf.AdguardHostname, conf.AdguardPort, conf.AdguardUsername, conf.AdguardPassword, conf.Interval)
initHttpServer(conf.Port)
initAdguardClient(conf.AdguardProtocol, conf.AdguardHostname, conf.AdguardPort, conf.AdguardUsername, conf.AdguardPassword, conf.Interval)
initHttpServer(conf.Port)
handleExitSignal()
handleExitSignal()
}
func basicAuth(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}
func initAdguardClient(protocol, hostname string, port uint16, username, password string, interval time.Duration) {
client := adguard.NewClient(protocol, hostname, port, username, password, interval)
go client.Scrape()
b64password := ""
if len(username) > 0 && len(password) > 0 {
b64password = basicAuth(username, password)
}
client := adguard.NewClient(protocol, hostname, port, b64password, interval)
go client.Scrape()
}
func initHttpServer(port string) {
s = server.NewServer(port)
go s.ListenAndServe()
s = server.NewServer(port)
go s.ListenAndServe()
}
func handleExitSignal() {
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
<-stop
<-stop
s.Stop()
fmt.Println(fmt.Sprintf("\n%s HTTP server stopped", name))
s.Stop()
fmt.Println(fmt.Sprintf("\n%s HTTP server stopped", name))
}