aboutsummaryrefslogtreecommitdiff
path: root/plugin/metrics/metrics.go
blob: 6a9e65272d94ffa3baec3d5da63631c8c7a0baf6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Package metrics implement a handler and plugin that provides Prometheus metrics.
package metrics

import (
	"context"
	"net"
	"net/http"
	"sync"
	"time"

	"github.com/coredns/caddy"
	"github.com/coredns/coredns/plugin"
	"github.com/coredns/coredns/plugin/pkg/reuseport"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// Metrics holds the prometheus configuration. The metrics' path is fixed to be /metrics .
type Metrics struct {
	Next plugin.Handler
	Addr string
	Reg  *prometheus.Registry

	ln      net.Listener
	lnSetup bool

	mux *http.ServeMux
	srv *http.Server

	zoneNames []string
	zoneMap   map[string]struct{}
	zoneMu    sync.RWMutex

	plugins map[string]struct{} // all available plugins, used to determine which plugin made the client write
}

// New returns a new instance of Metrics with the given address.
func New(addr string) *Metrics {
	met := &Metrics{
		Addr:    addr,
		Reg:     prometheus.DefaultRegisterer.(*prometheus.Registry),
		zoneMap: make(map[string]struct{}),
		plugins: pluginList(caddy.ListPlugins()),
	}

	return met
}

// MustRegister wraps m.Reg.MustRegister.
func (m *Metrics) MustRegister(c prometheus.Collector) {
	err := m.Reg.Register(c)
	if err != nil {
		// ignore any duplicate error, but fatal on any other kind of error
		if _, ok := err.(prometheus.AlreadyRegisteredError); !ok {
			log.Fatalf("Cannot register metrics collector: %s", err)
		}
	}
}

// AddZone adds zone z to m.
func (m *Metrics) AddZone(z string) {
	m.zoneMu.Lock()
	m.zoneMap[z] = struct{}{}
	m.zoneNames = keys(m.zoneMap)
	m.zoneMu.Unlock()
}

// RemoveZone remove zone z from m.
func (m *Metrics) RemoveZone(z string) {
	m.zoneMu.Lock()
	delete(m.zoneMap, z)
	m.zoneNames = keys(m.zoneMap)
	m.zoneMu.Unlock()
}

// ZoneNames returns the zones of m.
func (m *Metrics) ZoneNames() []string {
	m.zoneMu.RLock()
	s := m.zoneNames
	m.zoneMu.RUnlock()
	return s
}

// OnStartup sets up the metrics on startup.
func (m *Metrics) OnStartup() error {
	ln, err := reuseport.Listen("tcp", m.Addr)
	if err != nil {
		log.Errorf("Failed to start metrics handler: %s", err)
		return err
	}

	m.ln = ln
	m.lnSetup = true

	m.mux = http.NewServeMux()
	m.mux.Handle("/metrics", promhttp.HandlerFor(m.Reg, promhttp.HandlerOpts{}))

	// creating some helper variables to avoid data races on m.srv and m.ln
	server := &http.Server{Handler: m.mux}
	m.srv = server

	go func() {
		server.Serve(ln)
	}()

	ListenAddr = ln.Addr().String() // For tests.
	return nil
}

// OnRestart stops the listener on reload.
func (m *Metrics) OnRestart() error {
	if !m.lnSetup {
		return nil
	}
	u.Unset(m.Addr)
	return m.stopServer()
}

func (m *Metrics) stopServer() error {
	if !m.lnSetup {
		return nil
	}
	ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
	defer cancel()
	if err := m.srv.Shutdown(ctx); err != nil {
		log.Infof("Failed to stop prometheus http server: %s", err)
		return err
	}
	m.lnSetup = false
	m.ln.Close()
	return nil
}

// OnFinalShutdown tears down the metrics listener on shutdown and restart.
func (m *Metrics) OnFinalShutdown() error { return m.stopServer() }

func keys(m map[string]struct{}) []string {
	sx := []string{}
	for k := range m {
		sx = append(sx, k)
	}
	return sx
}

// pluginList iterates over the returned plugin map from caddy and removes the "dns." prefix from them.
func pluginList(m map[string][]string) map[string]struct{} {
	pm := map[string]struct{}{}
	for _, p := range m["others"] {
		// only add 'dns.' plugins
		if len(p) > 3 {
			pm[p[4:]] = struct{}{}
			continue
		}
	}
	return pm
}

// ListenAddr is assigned the address of the prometheus listener. Its use is mainly in tests where
// we listen on "localhost:0" and need to retrieve the actual address.
var ListenAddr string

// shutdownTimeout is the maximum amount of time the metrics plugin will wait
// before erroring when it tries to close the metrics server
const shutdownTimeout time.Duration = time.Second * 5

var buildInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
	Namespace: plugin.Namespace,
	Name:      "build_info",
	Help:      "A metric with a constant '1' value labeled by version, revision, and goversion from which CoreDNS was built.",
}, []string{"version", "revision", "goversion"})
branch nameGravatar Fred K. Schott 1-0/+2 2022-02-28Make smoke tests more deterministic (#2618)Gravatar Fred K. Schott 336-147/+34315 2022-02-28[ci] yarn formatGravatar natemoo-re 1-20/+20 2022-02-28[ci] release (#2683)astro@0.23.3Gravatar github-actions[bot] 31-54/+55 2022-02-28Fix typo (#2674)Gravatar Robin Millette 1-1/+1 2022-02-28[ci] update lockfile (#2676)Gravatar Fred K. Schott 1-6/+6 2022-02-28fix(runtime): do not render empty Fragment (#2667)Gravatar Mateus Esdras 1-0/+3 2022-02-28fix(hmr): HMR regression related to .astro updates (#2681)Gravatar Nate Moore 6-7/+24 2022-02-28Fix HTMLElement expression warning (#2675)Gravatar Jonathan Neal 1-1/+1 2022-02-28[ci] collect statsGravatar FredKSchott 1-0/+1 2022-02-27[ci] update lockfile (#2668)Gravatar Fred K. Schott 1-80/+80 2022-02-27[ci] collect statsGravatar FredKSchott 1-0/+1 2022-02-26[ci] collect statsGravatar FredKSchott 1-0/+1 2022-02-25[ci] yarn formatGravatar natemoo-re 1-20/+20 2022-02-25[ci] release (#2666)astro@0.23.2Gravatar github-actions[bot] 32-59/+57 2022-02-25[ci] yarn formatGravatar natemoo-re 2-12/+6 2022-02-25fix astro scoping of "@import" inside of style tags (#2656)Gravatar Fred K. Schott 3-6/+35 2022-02-25[ci] update lockfile (#2659)Gravatar Fred K. Schott 1-20/+20 2022-02-25feat: improve third-party Astro package compatability (#2665)Gravatar Nate Moore 3-6/+100 2022-02-25get new example working during buildGravatar Fred K. Schott 4-16/+21 2022-02-25[ci] yarn formatGravatar FredKSchott 1-7/+6 2022-02-25Add Non-HTML Pages example (#2637)Gravatar Joel Kuzmarski 11-0/+136 2022-02-25[ci] collect statsGravatar FredKSchott 1-0/+1 2022-02-24[ci] yarn formatGravatar natemoo-re 2-24/+24 2022-02-24[ci] release (#2641)astro@0.23.1@astrojs/markdown-remark@0.6.2Gravatar github-actions[bot] 38-90/+81 2022-02-24ensure utf8 encoding when serving html (#2654)Gravatar Fred K. Schott 3-4/+9 2022-02-24fix(core): Issue #2625. error with process.env.LANG larger than 5 (#2645)Gravatar Javier Cortés 2-1/+6 2022-02-24[ci] update lockfile (#2646)Gravatar Fred K. Schott 1-130/+124 2022-02-24chore: upgrade compiler (#2653)Gravatar Nate Moore 3-11/+11 2022-02-24[ci] yarn formatGravatar natemoo-re 2-5/+5 2022-02-24Add fine-grained HMR support (#2649)Gravatar Nate Moore 7-36/+37 2022-02-24[ci] collect statsGravatar FredKSchott 1-0/+1 2022-02-23Fixed incorrect types and imports (#2630)Gravatar Juan Martín Seery 27-35/+37 2022-02-23Add sass dev dep to blog-multiple-authors example (#2643)Gravatar Joel Kuzmarski 1-1/+2 2022-02-23Fix(component): align starting position in Markdown slot (#2631)Gravatar Shinobu Hayashi 4-6/+61 2022-02-23[ci] yarn formatGravatar matthewp 1-1/+1 2022-02-23Run all smoke tests with the static build (#2609)Gravatar Matthew Phillips 2-26/+32 2022-02-23[ci] collect statsGravatar FredKSchott 1-0/+1 2022-02-22[ci] update lockfile (#2624)Gravatar Fred K. Schott 1-171/+201 2022-02-22Fixed shiki import to work with "type": "module" (#2628)Gravatar Juan Martín Seery 3-5/+13