2018-05-01 23:45:06 +00:00
|
|
|
package metrics
|
|
|
|
|
|
|
|
import (
|
2019-01-10 20:55:44 +00:00
|
|
|
"context"
|
2019-11-12 22:55:40 +00:00
|
|
|
"fmt"
|
2018-05-01 23:45:06 +00:00
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
_ "net/http/pprof"
|
|
|
|
"runtime"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2021-01-19 11:53:54 +00:00
|
|
|
"github.com/gorilla/mux"
|
2018-05-01 23:45:06 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
2020-11-25 06:55:13 +00:00
|
|
|
"github.com/rs/zerolog"
|
|
|
|
"golang.org/x/net/trace"
|
2018-05-01 23:45:06 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
shutdownTimeout = time.Second * 15
|
|
|
|
startupTime = time.Millisecond * 500
|
|
|
|
)
|
|
|
|
|
2021-01-19 11:53:54 +00:00
|
|
|
func newMetricsHandler(readyServer *ReadyServer) *mux.Router {
|
|
|
|
router := mux.NewRouter()
|
|
|
|
router.PathPrefix("/debug/").Handler(http.DefaultServeMux)
|
|
|
|
|
|
|
|
router.Handle("/metrics", promhttp.Handler())
|
|
|
|
router.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
|
2020-11-25 06:55:13 +00:00
|
|
|
_, _ = fmt.Fprintf(w, "OK\n")
|
2020-11-30 20:05:37 +00:00
|
|
|
})
|
2021-01-14 22:33:36 +00:00
|
|
|
if readyServer != nil {
|
2021-01-19 11:53:54 +00:00
|
|
|
router.Handle("/ready", readyServer)
|
2021-01-14 22:33:36 +00:00
|
|
|
}
|
2021-01-19 11:53:54 +00:00
|
|
|
|
|
|
|
return router
|
2020-11-30 20:05:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func ServeMetrics(
|
|
|
|
l net.Listener,
|
|
|
|
shutdownC <-chan struct{},
|
2021-01-14 22:33:36 +00:00
|
|
|
readyServer *ReadyServer,
|
2020-11-25 06:55:13 +00:00
|
|
|
log *zerolog.Logger,
|
2020-11-30 20:05:37 +00:00
|
|
|
) (err error) {
|
2018-05-01 23:45:06 +00:00
|
|
|
var wg sync.WaitGroup
|
|
|
|
// Metrics port is privileged, so no need for further access control
|
|
|
|
trace.AuthRequest = func(*http.Request) (bool, bool) { return true, true }
|
|
|
|
// TODO: parameterize ReadTimeout and WriteTimeout. The maximum time we can
|
|
|
|
// profile CPU usage depends on WriteTimeout
|
2021-01-14 22:33:36 +00:00
|
|
|
h := newMetricsHandler(readyServer)
|
2018-05-01 23:45:06 +00:00
|
|
|
server := &http.Server{
|
|
|
|
ReadTimeout: 10 * time.Second,
|
|
|
|
WriteTimeout: 10 * time.Second,
|
2020-11-30 20:05:37 +00:00
|
|
|
Handler: h,
|
2018-05-01 23:45:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
err = server.Serve(l)
|
|
|
|
}()
|
2020-11-25 06:55:13 +00:00
|
|
|
log.Info().Msgf("Starting metrics server on %s", fmt.Sprintf("%v/metrics", l.Addr()))
|
2018-05-01 23:45:06 +00:00
|
|
|
// server.Serve will hang if server.Shutdown is called before the server is
|
|
|
|
// fully started up. So add artificial delay.
|
|
|
|
time.Sleep(startupTime)
|
|
|
|
|
|
|
|
<-shutdownC
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
2020-11-25 06:55:13 +00:00
|
|
|
_ = server.Shutdown(ctx)
|
2018-05-01 23:45:06 +00:00
|
|
|
cancel()
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
if err == http.ErrServerClosed {
|
2020-11-25 06:55:13 +00:00
|
|
|
log.Info().Msg("Metrics server stopped")
|
2018-05-01 23:45:06 +00:00
|
|
|
return nil
|
|
|
|
}
|
2020-12-28 18:10:01 +00:00
|
|
|
log.Err(err).Msg("Metrics server failed")
|
2018-05-01 23:45:06 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func RegisterBuildInfo(buildTime string, version string) {
|
|
|
|
buildInfo := prometheus.NewGaugeVec(
|
|
|
|
prometheus.GaugeOpts{
|
|
|
|
// Don't namespace build_info, since we want it to be consistent across all Cloudflare services
|
|
|
|
Name: "build_info",
|
|
|
|
Help: "Build and version information",
|
|
|
|
},
|
|
|
|
[]string{"goversion", "revision", "version"},
|
|
|
|
)
|
|
|
|
prometheus.MustRegister(buildInfo)
|
|
|
|
buildInfo.WithLabelValues(runtime.Version(), buildTime, version).Set(1)
|
|
|
|
}
|