Compare commits
10 commits
b4719ce9a0
...
44b1448080
Author | SHA1 | Date | |
---|---|---|---|
44b1448080 | |||
e3caa1dcf7 | |||
08fab8ede7 | |||
3e12c69d33 | |||
5e0344ddec | |||
297a498632 | |||
6d9ae78e7d | |||
9df65941a0 | |||
ba2c117d6a | |||
3fa623b162 |
30
.github/workflows/go.yml
vendored
Normal file
30
.github/workflows/go.yml
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Ensure all files were formatted as per gofmt
|
||||
run: |
|
||||
[ "$(gofmt -l $(find . -name '*.go') 2>&1)" = "" ]
|
||||
|
||||
- name: install binaries
|
||||
run: go install github.com/google/dnsmasq_exporter
|
||||
|
||||
- name: build tests
|
||||
run: go test -c ./collector
|
||||
|
||||
- name: docker build
|
||||
run: docker build --pull --no-cache --rm -t=dns -f travis/Dockerfile .
|
||||
|
||||
- name: run tests in docker
|
||||
run: docker run -v $PWD:/usr/src:ro -e TESTDATA_FILE_PATH=/usr/src/collector/testdata/dnsmasq.leases dns /bin/sh -c './collector.test -test.v'
|
22
.travis.yml
22
.travis.yml
|
@ -1,22 +0,0 @@
|
|||
# Use the (faster) container-based infrastructure, see also
|
||||
# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
|
||||
sudo: false
|
||||
dist: trusty
|
||||
services:
|
||||
- docker
|
||||
|
||||
language: go
|
||||
go:
|
||||
- "1.13"
|
||||
go_import_path: github.com/stapelberg/dnsmasq_exporter
|
||||
|
||||
|
||||
script:
|
||||
# Check whether files are syntactically correct.
|
||||
- "gofmt -l $(find . -name '*.go' | tr '\\n' ' ') >/dev/null"
|
||||
# Check whether files were not gofmt'ed.
|
||||
- "gosrc=$(find . -name '*.go' | tr '\\n' ' '); [ $(gofmt -l $gosrc 2>&- | wc -l) -eq 0 ] || (echo 'gofmt was not run on these files:'; gofmt -l $gosrc 2>&-; false)"
|
||||
- go vet .
|
||||
- go test -c
|
||||
- docker build --pull --no-cache --rm -t=dns -f travis/Dockerfile .
|
||||
- docker run -v $PWD:/usr/src:ro dns /bin/sh -c './dnsmasq_exporter.test -test.v'
|
|
@ -1,5 +1,5 @@
|
|||
# build stage
|
||||
FROM golang:1.12.6-stretch AS build-env
|
||||
FROM golang:1.17.2-stretch AS build-env
|
||||
ADD . /src
|
||||
ENV CGO_ENABLED=0
|
||||
WORKDIR /src
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# dnsmasq exporter
|
||||
|
||||
[![Build Status](https://travis-ci.org/google/dnsmasq_exporter.svg?branch=master)](https://travis-ci.org/google/dnsmasq_exporter)
|
||||
[![Build Status](https://github.com/google/dnsmasq_exporter/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/google/dnsmasq_exporter/actions)
|
||||
|
||||
dnsmasq_exporter is an exporter for [Prometheus](https://prometheus.io/),
|
||||
allowing you to monitor/alert on the number of DHCP leases and various DNS
|
||||
|
@ -32,6 +32,7 @@ systemctl enable --now dnsmasq_exporter
|
|||
```
|
||||
|
||||
### Alternative usage
|
||||
|
||||
```shell
|
||||
docker build -t dnsmasq_exporter .
|
||||
docker run --restart=unless-stopped --net=host dnsmasq_exporter
|
||||
|
|
301
collector/collector.go
Normal file
301
collector/collector.go
Normal file
|
@ -0,0 +1,301 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package collector collects dnsmasq statistics as a Prometheus collector.
|
||||
package collector
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var (
|
||||
// floatMetrics contains prometheus Gauges, keyed by the stats DNS record
|
||||
// they correspond to.
|
||||
floatMetrics = map[string]*prometheus.Desc{
|
||||
"cachesize.bind.": prometheus.NewDesc(
|
||||
"dnsmasq_cachesize",
|
||||
"configured size of the DNS cache",
|
||||
nil, nil,
|
||||
),
|
||||
|
||||
"insertions.bind.": prometheus.NewDesc(
|
||||
"dnsmasq_insertions",
|
||||
"DNS cache insertions",
|
||||
nil, nil,
|
||||
),
|
||||
|
||||
"evictions.bind.": prometheus.NewDesc(
|
||||
"dnsmasq_evictions",
|
||||
"DNS cache exictions: numbers of entries which replaced an unexpired cache entry",
|
||||
nil, nil,
|
||||
),
|
||||
|
||||
"misses.bind.": prometheus.NewDesc(
|
||||
"dnsmasq_misses",
|
||||
"DNS cache misses: queries which had to be forwarded",
|
||||
nil, nil,
|
||||
),
|
||||
|
||||
"hits.bind.": prometheus.NewDesc(
|
||||
"dnsmasq_hits",
|
||||
"DNS queries answered locally (cache hits)",
|
||||
nil, nil,
|
||||
),
|
||||
|
||||
"auth.bind.": prometheus.NewDesc(
|
||||
"dnsmasq_auth",
|
||||
"DNS queries for authoritative zones",
|
||||
nil, nil,
|
||||
),
|
||||
}
|
||||
|
||||
serversMetrics = map[string]*prometheus.Desc{
|
||||
"queries": prometheus.NewDesc(
|
||||
"dnsmasq_servers_queries",
|
||||
"DNS queries on upstream server",
|
||||
[]string{"server"}, nil,
|
||||
),
|
||||
"queries_failed": prometheus.NewDesc(
|
||||
"dnsmasq_servers_queries_failed",
|
||||
"DNS queries failed on upstream server",
|
||||
[]string{"server"}, nil,
|
||||
),
|
||||
}
|
||||
|
||||
// individual lease metrics have high cardinality and are thus disabled by
|
||||
// default, unless enabled with the -expose_leases flag
|
||||
leaseMetrics = prometheus.NewDesc(
|
||||
"dnsmasq_lease_expiry",
|
||||
"Expiry time for active DHCP leases",
|
||||
[]string{"mac_addr", "ip_addr", "computer_name", "client_id"},
|
||||
nil,
|
||||
)
|
||||
|
||||
leases = prometheus.NewDesc(
|
||||
"dnsmasq_leases",
|
||||
"Number of DHCP leases handed out",
|
||||
nil, nil,
|
||||
)
|
||||
)
|
||||
|
||||
// From https://manpages.debian.org/stretch/dnsmasq-base/dnsmasq.8.en.html:
|
||||
// The cache statistics are also available in the DNS as answers to queries of
|
||||
// class CHAOS and type TXT in domain bind. The domain names are cachesize.bind,
|
||||
// insertions.bind, evictions.bind, misses.bind, hits.bind, auth.bind and
|
||||
// servers.bind. An example command to query this, using the dig utility would
|
||||
// be:
|
||||
// dig +short chaos txt cachesize.bind
|
||||
|
||||
// Config contains the configuration for the collector.
|
||||
type Config struct {
|
||||
DnsClient *dns.Client
|
||||
DnsmasqAddr string
|
||||
LeasesPath string
|
||||
ExposeLeases bool
|
||||
}
|
||||
|
||||
// Collector implements prometheus.Collector and exposes dnsmasq metrics.
|
||||
type Collector struct {
|
||||
cfg Config
|
||||
}
|
||||
|
||||
type lease struct {
|
||||
expiry uint64
|
||||
macAddress string
|
||||
ipAddress string
|
||||
computerName string
|
||||
clientId string
|
||||
}
|
||||
|
||||
// New creates a new Collector.
|
||||
func New(cfg Config) *Collector {
|
||||
return &Collector{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Collector) Describe(ch chan<- *prometheus.Desc) {
|
||||
for _, d := range floatMetrics {
|
||||
ch <- d
|
||||
}
|
||||
for _, d := range serversMetrics {
|
||||
ch <- d
|
||||
}
|
||||
ch <- leases
|
||||
ch <- leaseMetrics
|
||||
}
|
||||
|
||||
func (c *Collector) Collect(ch chan<- prometheus.Metric) {
|
||||
var eg errgroup.Group
|
||||
|
||||
eg.Go(func() error {
|
||||
msg := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
question("cachesize.bind."),
|
||||
question("insertions.bind."),
|
||||
question("evictions.bind."),
|
||||
question("misses.bind."),
|
||||
question("hits.bind."),
|
||||
question("auth.bind."),
|
||||
question("servers.bind."),
|
||||
},
|
||||
}
|
||||
in, _, err := c.cfg.DnsClient.Exchange(msg, c.cfg.DnsmasqAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, a := range in.Answer {
|
||||
txt, ok := a.(*dns.TXT)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch txt.Hdr.Name {
|
||||
case "servers.bind.":
|
||||
for _, str := range txt.Txt {
|
||||
arr := strings.Fields(str)
|
||||
if got, want := len(arr), 3; got != want {
|
||||
return fmt.Errorf("stats DNS record servers.bind.: unexpeced number of argument in record: got %d, want %d", got, want)
|
||||
}
|
||||
queries, err := strconv.ParseFloat(arr[1], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
failedQueries, err := strconv.ParseFloat(arr[2], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch <- prometheus.MustNewConstMetric(serversMetrics["queries"], prometheus.GaugeValue, queries, arr[0])
|
||||
ch <- prometheus.MustNewConstMetric(serversMetrics["queries_failed"], prometheus.GaugeValue, failedQueries, arr[0])
|
||||
}
|
||||
default:
|
||||
g, ok := floatMetrics[txt.Hdr.Name]
|
||||
if !ok {
|
||||
continue // ignore unexpected answer from dnsmasq
|
||||
}
|
||||
if got, want := len(txt.Txt), 1; got != want {
|
||||
return fmt.Errorf("stats DNS record %q: unexpected number of replies: got %d, want %d", txt.Hdr.Name, got, want)
|
||||
}
|
||||
f, err := strconv.ParseFloat(txt.Txt[0], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch <- prometheus.MustNewConstMetric(g, prometheus.GaugeValue, f)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
activeLeases, err := readLeaseFile(c.cfg.LeasesPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch <- prometheus.MustNewConstMetric(leases, prometheus.GaugeValue, float64(len(activeLeases)))
|
||||
|
||||
if c.cfg.ExposeLeases {
|
||||
for _, activeLease := range activeLeases {
|
||||
ch <- prometheus.MustNewConstMetric(leaseMetrics, prometheus.GaugeValue, float64(activeLease.expiry),
|
||||
activeLease.macAddress, activeLease.ipAddress, activeLease.computerName, activeLease.clientId)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
log.Printf("could not complete scrape: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func question(name string) dns.Question {
|
||||
return dns.Question{
|
||||
Name: name,
|
||||
Qtype: dns.TypeTXT,
|
||||
Qclass: dns.ClassCHAOS,
|
||||
}
|
||||
}
|
||||
|
||||
func parseLease(line string) (*lease, error) {
|
||||
arr := strings.Fields(line)
|
||||
if got, want := len(arr), 5; got != want {
|
||||
return nil, fmt.Errorf("illegal lease: expected %d fields, got %d", want, got)
|
||||
}
|
||||
|
||||
expires, err := strconv.ParseUint(arr[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &lease{
|
||||
expiry: expires,
|
||||
macAddress: arr[1],
|
||||
ipAddress: arr[2],
|
||||
computerName: arr[3],
|
||||
clientId: arr[4],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read the DHCP lease file with the given path and return a list of leases.
|
||||
//
|
||||
// The format of the DHCP lease file written by dnsmasq is not formally
|
||||
// documented in the dnsmasq manual but the format has been described in the
|
||||
// mailing list:
|
||||
//
|
||||
// - https://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2006q2/000733.html
|
||||
// - https://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2016q2/010595.html
|
||||
//
|
||||
// The DHCP lease file is written to by lease_update_file() in
|
||||
// src/lease.c, and is read by lease_init().
|
||||
func readLeaseFile(path string) ([]lease, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// ignore
|
||||
return []lease{}, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
activeLeases := []lease{}
|
||||
for scanner.Scan() {
|
||||
activeLease, err := parseLease(scanner.Text())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
activeLeases = append(activeLeases, *activeLease)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return activeLeases, nil
|
||||
}
|
|
@ -1,4 +1,18 @@
|
|||
package main
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package collector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -13,6 +27,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
|
@ -60,17 +75,24 @@ func TestDnsmasqExporter(t *testing.T) {
|
|||
time.Sleep(10 * time.Millisecond) // do not hog the CPU
|
||||
}
|
||||
|
||||
s := &server{
|
||||
promHandler: promhttp.Handler(),
|
||||
dnsClient: &dns.Client{
|
||||
SingleInflight: true,
|
||||
},
|
||||
dnsmasqAddr: "localhost:" + port,
|
||||
leasesPath: "testdata/dnsmasq.leases",
|
||||
testDataFilePath := os.Getenv("TESTDATA_FILE_PATH")
|
||||
if testDataFilePath == "" {
|
||||
testDataFilePath = "./testdata/dnsmasq.leases"
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
DnsClient: &dns.Client{
|
||||
SingleInflight: true,
|
||||
},
|
||||
DnsmasqAddr: "localhost:" + port,
|
||||
LeasesPath: testDataFilePath,
|
||||
ExposeLeases: false,
|
||||
}
|
||||
|
||||
c := New(cfg)
|
||||
|
||||
t.Run("first", func(t *testing.T) {
|
||||
metrics := fetchMetrics(t, s)
|
||||
metrics := fetchMetrics(t, c)
|
||||
want := map[string]string{
|
||||
"dnsmasq_leases": "2",
|
||||
"dnsmasq_cachesize": "666",
|
||||
|
@ -85,7 +107,7 @@ func TestDnsmasqExporter(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("second", func(t *testing.T) {
|
||||
metrics := fetchMetrics(t, s)
|
||||
metrics := fetchMetrics(t, c)
|
||||
want := map[string]string{
|
||||
"dnsmasq_leases": "2",
|
||||
"dnsmasq_cachesize": "666",
|
||||
|
@ -107,7 +129,7 @@ func TestDnsmasqExporter(t *testing.T) {
|
|||
}
|
||||
|
||||
t.Run("after query", func(t *testing.T) {
|
||||
metrics := fetchMetrics(t, s)
|
||||
metrics := fetchMetrics(t, c)
|
||||
want := map[string]string{
|
||||
"dnsmasq_leases": "2",
|
||||
"dnsmasq_cachesize": "666",
|
||||
|
@ -121,14 +143,42 @@ func TestDnsmasqExporter(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
s.leasesPath = "testdata/dnsmasq.leases.does.not.exists"
|
||||
t.Run("should not expose lease information when disabled", func(t *testing.T) {
|
||||
metrics := fetchMetrics(t, c)
|
||||
for key, _ := range metrics {
|
||||
if strings.Contains(key, "dnsmasq_lease_expiry") {
|
||||
t.Errorf("lease information should not be exposed when disabled: %v", key)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
c.cfg.ExposeLeases = true
|
||||
|
||||
t.Run("with high-cardinality lease metrics enabled", func(t *testing.T) {
|
||||
metrics := fetchMetrics(t, c)
|
||||
want := map[string]string{
|
||||
"dnsmasq_leases": "2",
|
||||
"dnsmasq_cachesize": "666",
|
||||
"dnsmasq_hits": "5",
|
||||
"dnsmasq_misses": "1",
|
||||
"dnsmasq_lease_expiry{client_id=\"00:00:00:00:00:00\",computer_name=\"host-1\",ip_addr=\"10.10.10.10\",mac_addr=\"00:00:00:00:00:00\"}": "1.625595932e+09",
|
||||
"dnsmasq_lease_expiry{client_id=\"00:00:00:00:00:01\",computer_name=\"host-2\",ip_addr=\"10.10.10.11\",mac_addr=\"00:00:00:00:00:01\"}": "0",
|
||||
}
|
||||
for key, val := range want {
|
||||
if got, want := metrics[key], val; got != want {
|
||||
t.Errorf("metric %q: got %q, want %q", key, got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
c.cfg.LeasesPath = "testdata/dnsmasq.leases.does.not.exists"
|
||||
|
||||
t.Run("without leases file", func(t *testing.T) {
|
||||
metrics := fetchMetrics(t, s)
|
||||
metrics := fetchMetrics(t, c)
|
||||
want := map[string]string{
|
||||
"dnsmasq_leases": "0",
|
||||
"dnsmasq_cachesize": "666",
|
||||
"dnsmasq_hits": "4",
|
||||
"dnsmasq_hits": "6",
|
||||
"dnsmasq_misses": "1",
|
||||
}
|
||||
for key, val := range want {
|
||||
|
@ -140,9 +190,13 @@ func TestDnsmasqExporter(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func fetchMetrics(t *testing.T, s *server) map[string]string {
|
||||
func fetchMetrics(t *testing.T, c *Collector) map[string]string {
|
||||
reg := prometheus.NewRegistry()
|
||||
reg.MustRegister(c)
|
||||
handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
s.metrics(rec, httptest.NewRequest("GET", "/metrics", nil))
|
||||
handler.ServeHTTP(rec, httptest.NewRequest("GET", "/metrics", nil))
|
||||
resp := rec.Result()
|
||||
if got, want := resp.StatusCode, http.StatusOK; got != want {
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
2
collector/testdata/dnsmasq.leases
vendored
Normal file
2
collector/testdata/dnsmasq.leases
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
1625595932 00:00:00:00:00:00 10.10.10.10 host-1 00:00:00:00:00:00
|
||||
0 00:00:00:00:00:01 10.10.10.11 host-2 00:00:00:00:00:01
|
|
@ -3,25 +3,37 @@
|
|||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"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,
|
||||
"gnetId": null,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": 34,
|
||||
"iteration": 1602437170658,
|
||||
"id": 94,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": null,
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
|
@ -30,14 +42,24 @@
|
|||
},
|
||||
"id": 15,
|
||||
"panels": [],
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "DNS Stats",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": "$datasource",
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
|
@ -73,11 +95,15 @@
|
|||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
}
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"pluginVersion": "9.3.2-67a213dc85",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"expr": "sum(dnsmasq_leases{job=~\"$job\", instance=~\"$instance\"})",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
|
@ -85,16 +111,15 @@
|
|||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Leases",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "$datasource",
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"noValue": "0",
|
||||
"thresholds": {
|
||||
|
@ -131,11 +156,15 @@
|
|||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
}
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"pluginVersion": "9.3.2-67a213dc85",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"expr": "sum(dnsmasq_servers_queries{job=~\"$job\", instance=~\"$instance\"})",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
|
@ -143,17 +172,18 @@
|
|||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Upstream Queries",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "$datasource",
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"max": 1,
|
||||
"min": 0,
|
||||
"noValue": "0%",
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
|
@ -190,11 +220,15 @@
|
|||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
}
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"pluginVersion": "9.3.2-67a213dc85",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"expr": "sum(dnsmasq_servers_queries_failed{job=~\"$job\", instance=~\"$instance\"}) / sum(dnsmasq_servers_queries{job=~\"$job\", instance=~\"$instance\"})",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
|
@ -202,14 +236,15 @@
|
|||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Upstream Failed Queries",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": null,
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
|
@ -218,15 +253,27 @@
|
|||
},
|
||||
"id": 13,
|
||||
"panels": [],
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Build Info",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": "prometheus",
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"align": null
|
||||
"displayMode": "auto",
|
||||
"inspect": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
|
@ -253,11 +300,21 @@
|
|||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"footer": {
|
||||
"fields": "",
|
||||
"reducer": [
|
||||
"sum"
|
||||
],
|
||||
"show": false
|
||||
},
|
||||
"showHeader": true
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"pluginVersion": "9.3.2-67a213dc85",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"expr": "dnsmasq_exporter_build_info{job=~\"$job\", instance=~\"$instance\"}",
|
||||
"format": "time_series",
|
||||
"instant": true,
|
||||
|
@ -266,14 +323,16 @@
|
|||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Build Info",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "labelsToFields",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "merge",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
|
@ -301,7 +360,10 @@
|
|||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"datasource": null,
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
|
@ -310,14 +372,24 @@
|
|||
},
|
||||
"id": 11,
|
||||
"panels": [],
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Cache Stats",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": "$datasource",
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
|
@ -353,11 +425,15 @@
|
|||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
}
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"pluginVersion": "9.3.2-67a213dc85",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"expr": "sum(dnsmasq_cachesize{job=~\"$job\", instance=~\"$instance\"})",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
|
@ -365,16 +441,15 @@
|
|||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Cache Size",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "$datasource",
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
|
@ -410,11 +485,15 @@
|
|||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
}
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"pluginVersion": "9.3.2-67a213dc85",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"expr": "sum(dnsmasq_hits{job=~\"$job\", instance=~\"$instance\"})",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
|
@ -422,16 +501,15 @@
|
|||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Cache Hits",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "$datasource",
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
|
@ -467,11 +545,15 @@
|
|||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
}
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"pluginVersion": "9.3.2-67a213dc85",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"expr": "sum(dnsmasq_insertions{job=~\"$job\", instance=~\"$instance\"})",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
|
@ -479,26 +561,24 @@
|
|||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Cache Insertions",
|
||||
"type": "stat"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 25,
|
||||
"schemaVersion": 37,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"selected": true,
|
||||
"text": "prometheus",
|
||||
"value": "prometheus"
|
||||
"selected": false,
|
||||
"text": "default",
|
||||
"value": "default"
|
||||
},
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": null,
|
||||
"label": "Data Source",
|
||||
"multi": false,
|
||||
"name": "datasource",
|
||||
"options": [],
|
||||
|
@ -510,79 +590,55 @@
|
|||
"type": "datasource"
|
||||
},
|
||||
{
|
||||
"allValue": null,
|
||||
"allValue": ".+",
|
||||
"current": {
|
||||
"selected": true,
|
||||
"text": "dnsmasq",
|
||||
"value": [
|
||||
"dnsmasq"
|
||||
]
|
||||
"selected": false,
|
||||
"text": "All",
|
||||
"value": "$__all"
|
||||
},
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"definition": "label_values(dnsmasq_exporter_build_info, job)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": null,
|
||||
"label": "job",
|
||||
"multi": true,
|
||||
"name": "job",
|
||||
"options": [
|
||||
{
|
||||
"selected": false,
|
||||
"text": "All",
|
||||
"value": "$__all"
|
||||
},
|
||||
{
|
||||
"selected": true,
|
||||
"text": "dnsmasq",
|
||||
"value": "dnsmasq"
|
||||
}
|
||||
],
|
||||
"options": [],
|
||||
"query": "label_values(dnsmasq_exporter_build_info, job)",
|
||||
"refresh": 0,
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tags": [],
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"allValue": null,
|
||||
"allValue": ".+",
|
||||
"current": {
|
||||
"selected": true,
|
||||
"text": "router.lan",
|
||||
"value": [
|
||||
"router.lan"
|
||||
]
|
||||
"selected": false,
|
||||
"text": "All",
|
||||
"value": "$__all"
|
||||
},
|
||||
"datasource": {
|
||||
"uid": "$datasource"
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"definition": "label_values(dnsmasq_exporter_build_info, instance)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": null,
|
||||
"label": "instance",
|
||||
"multi": true,
|
||||
"name": "instance",
|
||||
"options": [
|
||||
{
|
||||
"selected": false,
|
||||
"text": "All",
|
||||
"value": "$__all"
|
||||
},
|
||||
{
|
||||
"selected": true,
|
||||
"text": "router.lan",
|
||||
"value": "router.lan"
|
||||
}
|
||||
],
|
||||
"options": [],
|
||||
"query": "label_values(dnsmasq_exporter_build_info, instance)",
|
||||
"refresh": 0,
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tags": [],
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
|
@ -609,5 +665,6 @@
|
|||
"timezone": "",
|
||||
"title": "dnsmasq",
|
||||
"uid": "ySypWTcMk",
|
||||
"version": 2
|
||||
}
|
||||
"version": 2,
|
||||
"weekStart": ""
|
||||
}
|
224
dnsmasq.go
224
dnsmasq.go
|
@ -16,17 +16,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/google/dnsmasq_exporter/collector"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
@ -38,6 +32,9 @@ var (
|
|||
"localhost:9153",
|
||||
"listen address")
|
||||
|
||||
exposeLeases = flag.Bool("expose_leases",
|
||||
false,
|
||||
"expose dnsmasq leases as metrics (high cardinality)")
|
||||
leasesPath = flag.String("leases_path",
|
||||
"/var/lib/misc/dnsmasq.leases",
|
||||
"path to the dnsmasq leases file")
|
||||
|
@ -50,205 +47,33 @@ var (
|
|||
"path under which metrics are served")
|
||||
)
|
||||
|
||||
var (
|
||||
// floatMetrics contains prometheus Gauges, keyed by the stats DNS record
|
||||
// they correspond to.
|
||||
floatMetrics = map[string]prometheus.Gauge{
|
||||
"cachesize.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "dnsmasq_cachesize",
|
||||
Help: "configured size of the DNS cache",
|
||||
}),
|
||||
|
||||
"insertions.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "dnsmasq_insertions",
|
||||
Help: "DNS cache insertions",
|
||||
}),
|
||||
|
||||
"evictions.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "dnsmasq_evictions",
|
||||
Help: "DNS cache exictions: numbers of entries which replaced an unexpired cache entry",
|
||||
}),
|
||||
|
||||
"misses.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "dnsmasq_misses",
|
||||
Help: "DNS cache misses: queries which had to be forwarded",
|
||||
}),
|
||||
|
||||
"hits.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "dnsmasq_hits",
|
||||
Help: "DNS queries answered locally (cache hits)",
|
||||
}),
|
||||
|
||||
"auth.bind.": prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "dnsmasq_auth",
|
||||
Help: "DNS queries for authoritative zones",
|
||||
}),
|
||||
}
|
||||
|
||||
serversMetrics = map[string]*prometheus.GaugeVec{
|
||||
"queries": prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "dnsmasq_servers_queries",
|
||||
Help: "DNS queries on upstream server",
|
||||
},
|
||||
[]string{"server"},
|
||||
),
|
||||
"queries_failed": prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "dnsmasq_servers_queries_failed",
|
||||
Help: "DNS queries failed on upstream server",
|
||||
},
|
||||
[]string{"server"},
|
||||
),
|
||||
}
|
||||
|
||||
leases = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "dnsmasq_leases",
|
||||
Help: "Number of DHCP leases handed out",
|
||||
})
|
||||
)
|
||||
|
||||
func init() {
|
||||
for _, g := range floatMetrics {
|
||||
prometheus.MustRegister(g)
|
||||
}
|
||||
for _, g := range serversMetrics {
|
||||
prometheus.MustRegister(g)
|
||||
}
|
||||
prometheus.MustRegister(leases)
|
||||
prometheus.MustRegister(version.NewCollector("dnsmasq_exporter"))
|
||||
}
|
||||
|
||||
// From https://manpages.debian.org/stretch/dnsmasq-base/dnsmasq.8.en.html:
|
||||
// The cache statistics are also available in the DNS as answers to queries of
|
||||
// class CHAOS and type TXT in domain bind. The domain names are cachesize.bind,
|
||||
// insertions.bind, evictions.bind, misses.bind, hits.bind, auth.bind and
|
||||
// servers.bind. An example command to query this, using the dig utility would
|
||||
// be:
|
||||
// dig +short chaos txt cachesize.bind
|
||||
|
||||
type server struct {
|
||||
promHandler http.Handler
|
||||
dnsClient *dns.Client
|
||||
dnsmasqAddr string
|
||||
leasesPath string
|
||||
}
|
||||
|
||||
func question(name string) dns.Question {
|
||||
return dns.Question{
|
||||
Name: name,
|
||||
Qtype: dns.TypeTXT,
|
||||
Qclass: dns.ClassCHAOS,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) metrics(w http.ResponseWriter, r *http.Request) {
|
||||
var eg errgroup.Group
|
||||
|
||||
eg.Go(func() error {
|
||||
msg := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: dns.Id(),
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []dns.Question{
|
||||
question("cachesize.bind."),
|
||||
question("insertions.bind."),
|
||||
question("evictions.bind."),
|
||||
question("misses.bind."),
|
||||
question("hits.bind."),
|
||||
question("auth.bind."),
|
||||
question("servers.bind."),
|
||||
},
|
||||
}
|
||||
in, _, err := s.dnsClient.Exchange(msg, s.dnsmasqAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, a := range in.Answer {
|
||||
txt, ok := a.(*dns.TXT)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch txt.Hdr.Name {
|
||||
case "servers.bind.":
|
||||
for _, str := range txt.Txt {
|
||||
arr := strings.Fields(str)
|
||||
if got, want := len(arr), 3; got != want {
|
||||
return fmt.Errorf("stats DNS record servers.bind.: unexpeced number of argument in record: got %d, want %d", got, want)
|
||||
}
|
||||
queries, err := strconv.ParseFloat(arr[1], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
failedQueries, err := strconv.ParseFloat(arr[2], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serversMetrics["queries"].WithLabelValues(arr[0]).Set(queries)
|
||||
serversMetrics["queries_failed"].WithLabelValues(arr[0]).Set(failedQueries)
|
||||
}
|
||||
default:
|
||||
g, ok := floatMetrics[txt.Hdr.Name]
|
||||
if !ok {
|
||||
continue // ignore unexpected answer from dnsmasq
|
||||
}
|
||||
if got, want := len(txt.Txt), 1; got != want {
|
||||
return fmt.Errorf("stats DNS record %q: unexpected number of replies: got %d, want %d", txt.Hdr.Name, got, want)
|
||||
}
|
||||
f, err := strconv.ParseFloat(txt.Txt[0], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.Set(f)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
f, err := os.Open(s.leasesPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// ignore
|
||||
leases.Set(0)
|
||||
return nil
|
||||
}
|
||||
log.Println("warn: could not open leases file:", err)
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
var lines float64
|
||||
for scanner.Scan() {
|
||||
lines++
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
leases.Set(lines)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
s.promHandler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
s := &server{
|
||||
promHandler: promhttp.Handler(),
|
||||
dnsClient: &dns.Client{
|
||||
|
||||
var (
|
||||
dnsClient = &dns.Client{
|
||||
SingleInflight: true,
|
||||
},
|
||||
dnsmasqAddr: *dnsmasqAddr,
|
||||
leasesPath: *leasesPath,
|
||||
}
|
||||
http.HandleFunc(*metricsPath, s.metrics)
|
||||
}
|
||||
cfg = collector.Config{
|
||||
DnsClient: dnsClient,
|
||||
DnsmasqAddr: *dnsmasqAddr,
|
||||
LeasesPath: *leasesPath,
|
||||
ExposeLeases: *exposeLeases,
|
||||
}
|
||||
collector = collector.New(cfg)
|
||||
reg = prometheus.NewRegistry()
|
||||
)
|
||||
|
||||
reg.MustRegister(collector)
|
||||
|
||||
http.Handle(*metricsPath, promhttp.HandlerFor(
|
||||
prometheus.Gatherers{prometheus.DefaultGatherer, reg},
|
||||
promhttp.HandlerOpts{},
|
||||
))
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`<html>
|
||||
<head><title>Dnsmasq Exporter</title></head>
|
||||
|
@ -257,7 +82,6 @@ func main() {
|
|||
<p><a href="` + *metricsPath + `">Metrics</a></p>
|
||||
</body></html>`))
|
||||
})
|
||||
|
||||
log.Println("Listening on", *listen)
|
||||
log.Println("Service metrics under", *metricsPath)
|
||||
log.Fatal(http.ListenAndServe(*listen, nil))
|
||||
|
|
4
go.mod
4
go.mod
|
@ -3,8 +3,8 @@ module github.com/google/dnsmasq_exporter
|
|||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/miekg/dns v1.1.14
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/miekg/dns v1.1.25
|
||||
github.com/prometheus/client_golang v1.11.1
|
||||
github.com/prometheus/common v0.31.1
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
)
|
||||
|
|
12
go.sum
12
go.sum
|
@ -138,8 +138,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.14 h1:wkQWn9wIp4mZbwW8XV6Km6owkvRPbOiV004ZM2CkGvA=
|
||||
github.com/miekg/dns v1.1.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg=
|
||||
github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
|
@ -153,8 +153,9 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s=
|
||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
|
@ -191,6 +192,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
|
@ -238,6 +240,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -282,6 +285,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -329,6 +334,7 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
|
|||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
|
2
testdata/dnsmasq.leases
vendored
2
testdata/dnsmasq.leases
vendored
|
@ -1,2 +0,0 @@
|
|||
lease1
|
||||
lease2
|
Loading…
Reference in a new issue