Skip to content

Commit edb114d

Browse files
committed
Add metrics pkg
enables metrics server deployment for whereabouts to collect prom metrics and enable pprof Signed-off-by: Benjamin Pickard <[email protected]>
1 parent 9bdc601 commit edb114d

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed

pkg/metrics/metrics.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package metrics
2+
3+
import (
4+
"context"
5+
"crypto/tls"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"net/http/pprof"
10+
"sync"
11+
"time"
12+
13+
"github.com/prometheus/client_golang/prometheus/promhttp"
14+
15+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
16+
utilwait "k8s.io/apimachinery/pkg/util/wait"
17+
"k8s.io/klog/v2"
18+
)
19+
20+
// inspired by metrics server implementation @https://github.com/ovn-kubernetes/ovn-kubernetes/go-controller/pk/metrics/metrics.go
21+
22+
// stringFlagSetterFunc is a func used for setting string type flag.
23+
type stringFlagSetterFunc func(string) (string, error)
24+
25+
// klogSetter is a setter to set klog level.
26+
func klogSetter(val string) (string, error) {
27+
var level klog.Level
28+
if err := level.Set(val); err != nil {
29+
return "", fmt.Errorf("failed set klog.logging.verbosity %s: %v", val, err)
30+
}
31+
return fmt.Sprintf("successfully set klog.logging.verbosity to %s", val), nil
32+
}
33+
34+
// stringFlagPutHandler wraps an http Handler to set string type flag.
35+
func stringFlagPutHandler(setter stringFlagSetterFunc) http.HandlerFunc {
36+
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
37+
switch {
38+
case req.Method == "PUT":
39+
body, err := io.ReadAll(req.Body)
40+
if err != nil {
41+
writePlainText(http.StatusBadRequest, "error reading request body: "+err.Error(), w)
42+
return
43+
}
44+
defer req.Body.Close()
45+
response, err := setter(string(body))
46+
if err != nil {
47+
writePlainText(http.StatusBadRequest, err.Error(), w)
48+
return
49+
}
50+
writePlainText(http.StatusOK, response, w)
51+
return
52+
default:
53+
writePlainText(http.StatusNotAcceptable, "unsupported http method", w)
54+
return
55+
}
56+
})
57+
}
58+
59+
// writePlainText renders a simple string response.
60+
func writePlainText(statusCode int, text string, w http.ResponseWriter) {
61+
w.Header().Set("Content-Type", "text/plain")
62+
w.Header().Set("X-Content-Type-Options", "nosniff")
63+
w.WriteHeader(statusCode)
64+
fmt.Fprintln(w, text)
65+
}
66+
67+
// using the cyrpto/tls module's GetCertificate() callback function helps in picking up
68+
// the latest certificate (due to cert rotation on cert expiry)
69+
func getTLSServer(addr, certFile, privKeyFile string, handler http.Handler) *http.Server {
70+
tlsConfig := &tls.Config{
71+
GetCertificate: func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
72+
cert, err := tls.LoadX509KeyPair(certFile, privKeyFile)
73+
if err != nil {
74+
return nil, fmt.Errorf("error generating x509 certs for metrics TLS endpoint: %v", err)
75+
}
76+
return &cert, nil
77+
},
78+
}
79+
server := &http.Server{
80+
Addr: addr,
81+
Handler: handler,
82+
TLSConfig: tlsConfig,
83+
}
84+
return server
85+
}
86+
87+
func StartMetricsServer(bindAddress string, certFile string, keyfile string, stopChan <-chan struct{}, wg *sync.WaitGroup) {
88+
mux := http.NewServeMux()
89+
mux.Handle("/metrics", promhttp.Handler())
90+
91+
mux.HandleFunc("/debug/pprof/", pprof.Index)
92+
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
93+
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
94+
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
95+
// Allow changes to log level at runtime
96+
mux.HandleFunc("/debug/flags/v", stringFlagPutHandler(klogSetter))
97+
98+
startMetricsServer(bindAddress, certFile, keyfile, mux, stopChan, wg)
99+
}
100+
101+
func startMetricsServer(bindAddress, certFile, keyFile string, handler http.Handler, stopChan <-chan struct{}, wg *sync.WaitGroup) {
102+
var server *http.Server
103+
wg.Add(1)
104+
go func() {
105+
defer wg.Done()
106+
utilwait.Until(func() {
107+
klog.Infof("Starting metrics server at address %q", bindAddress)
108+
var listenAndServe func() error
109+
if certFile != "" && keyFile != "" {
110+
server = getTLSServer(bindAddress, certFile, keyFile, handler)
111+
listenAndServe = func() error { return server.ListenAndServeTLS("", "") }
112+
} else {
113+
server = &http.Server{Addr: bindAddress, Handler: handler}
114+
listenAndServe = func() error { return server.ListenAndServe() }
115+
}
116+
117+
errCh := make(chan error)
118+
go func() {
119+
errCh <- listenAndServe()
120+
}()
121+
var err error
122+
select {
123+
case err = <-errCh:
124+
err = fmt.Errorf("failed while running metrics server at address %q: %w", bindAddress, err)
125+
utilruntime.HandleError(err)
126+
case <-stopChan:
127+
klog.Infof("Stopping metrics server at address %q", bindAddress)
128+
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
129+
defer cancel()
130+
if err := server.Shutdown(shutdownCtx); err != nil {
131+
klog.Errorf("Error stopping metrics server at address %q: %v", bindAddress, err)
132+
}
133+
}
134+
}, 5*time.Second, stopChan)
135+
}()
136+
}

0 commit comments

Comments
 (0)