Skip to content

Commit

Permalink
add prom metrics to frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
Katherine Lin committed Apr 16, 2024
1 parent 3382078 commit 356e7d2
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 3 deletions.
21 changes: 20 additions & 1 deletion frontend/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/Azure/ARO-HCP/internal/api"
"github.com/Azure/ARO-HCP/internal/api/arm"
"github.com/Azure/ARO-HCP/internal/api/subscription"
"github.com/prometheus/client_golang/prometheus/promhttp"
)

const (
Expand All @@ -36,6 +37,7 @@ type Frontend struct {
cache cache
ready atomic.Value
done chan struct{}
metrics *PrometheusEmitter
}

// MuxPattern forms a URL pattern suitable for passing to http.ServeMux.
Expand All @@ -46,9 +48,11 @@ func MuxPattern(method string, segments ...string) string {
}

func NewFrontend(logger *slog.Logger, listener net.Listener) *Frontend {
prometheusEmitter := NewPrometheusEmitter()
f := &Frontend{
logger: logger,
listener: listener,
metrics: prometheusEmitter,
server: http.Server{
ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
BaseContext: func(net.Listener) context.Context {
Expand All @@ -59,17 +63,24 @@ func NewFrontend(logger *slog.Logger, listener net.Listener) *Frontend {
done: make(chan struct{}),
}

// Setup metrics middleware
metricsMiddleware := MetricsMiddleware{Emitter: prometheusEmitter}

mux := NewMiddlewareMux(
MiddlewarePanic,
MiddlewareLogging,
MiddlewareBody,
MiddlewareLowercase,
MiddlewareSystemData)
MiddlewareSystemData,
metricsMiddleware.Metrics())

// Unauthenticated routes
mux.HandleFunc("/", f.NotFound)
mux.HandleFunc(MuxPattern(http.MethodGet, "healthz", "ready"), f.HealthzReady)

// Expose Prometheus metrics endpoint
mux.Handle("/metrics", promhttp.Handler())

// Authenticated routes
postMuxMiddleware := NewMiddleware(
MiddlewareLoggingPostMux,
Expand Down Expand Up @@ -99,6 +110,7 @@ func NewFrontend(logger *slog.Logger, listener net.Listener) *Frontend {
MuxPattern(http.MethodPost, PatternSubscriptions, PatternResourceGroups, PatternProviders, PatternResourceName, PatternActionName),
postMuxMiddleware.HandlerFunc(f.ArmResourceAction))
mux.Handle(MuxPattern(http.MethodPut, PatternSubscriptions), postMuxMiddleware.HandlerFunc(f.ArmSubscriptionAction))

f.server.Handler = mux

return f
Expand Down Expand Up @@ -142,11 +154,18 @@ func (f *Frontend) NotFound(writer http.ResponseWriter, request *http.Request) {
}

func (f *Frontend) HealthzReady(writer http.ResponseWriter, request *http.Request) {
var healthStatus float64
if f.CheckReady() {
writer.WriteHeader(http.StatusOK)
healthStatus = 1.0
} else {
writer.WriteHeader(http.StatusInternalServerError)
healthStatus = 0.0
}

(*PrometheusEmitter).EmitGauge(f.metrics, "frontend.health", healthStatus, map[string]string{
"endpoint": "/healthz/ready",
})
}

func (f *Frontend) ArmResourceListBySubscription(writer http.ResponseWriter, request *http.Request) {
Expand Down
11 changes: 10 additions & 1 deletion frontend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,13 @@ module github.com/Azure/ARO-HCP/frontend

go 1.22

require github.com/google/go-cmp v0.6.0 // indirect
require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect
golang.org/x/sys v0.16.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
)
14 changes: 14 additions & 0 deletions frontend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,17 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
87 changes: 87 additions & 0 deletions frontend/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package main

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.

import (
"net/http"
"strconv"
"time"

"golang.org/x/exp/maps"

"github.com/Azure/ARO-HCP/internal/metrics"
"github.com/prometheus/client_golang/prometheus"
)

type PrometheusEmitter struct {
gauges map[string]*prometheus.GaugeVec
counters map[string]*prometheus.CounterVec
}

func NewPrometheusEmitter() *PrometheusEmitter {
return &PrometheusEmitter{
gauges: make(map[string]*prometheus.GaugeVec),
counters: make(map[string]*prometheus.CounterVec),
}
}

func (pe *PrometheusEmitter) EmitGauge(name string, value float64, labels map[string]string) {
vec, exists := pe.gauges[name]
if !exists {
labelKeys := maps.Keys(labels)
vec = prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: name}, labelKeys)
prometheus.MustRegister(vec)
pe.gauges[name] = vec
}
vec.With(labels).Set(value)
}

func (pe *PrometheusEmitter) EmitCounter(name string, value float64, labels map[string]string) {
vec, exists := pe.counters[name]
if !exists {
labelKeys := maps.Keys(labels)
vec = prometheus.NewCounterVec(prometheus.CounterOpts{Name: name}, labelKeys)
prometheus.MustRegister(vec)
pe.counters[name] = vec
}
vec.With(labels).Add(value)
}

type MetricsMiddleware struct {
metrics.Emitter
}

type logResponseWriter struct {
http.ResponseWriter
statusCode int
}

// Metric records request metrics for tracking
func (mm MetricsMiddleware) Metrics() MiddlewareFunc {
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
apiVersion := r.URL.Query().Get(APIVersionKey)
t := time.Now()

w = &logResponseWriter{ResponseWriter: w, statusCode: http.StatusOK}

next.ServeHTTP(w, r)

//get the route pattern that matched
routePattern := r.URL.Path
mm.Emitter.EmitCounter("frontend.count", 1.0, map[string]string{
"verb": r.Method,
"api-version": apiVersion,
"code": strconv.Itoa(w.(*logResponseWriter).statusCode),
"route": routePattern,
})

mm.Emitter.EmitGauge("frontend.duration", float64(time.Since(t).Milliseconds()), map[string]string{
"verb": r.Method,
"api-version": apiVersion,
"code": strconv.Itoa(w.(*logResponseWriter).statusCode),
"route": routePattern,
})

}
}
28 changes: 27 additions & 1 deletion go.work.sum
Original file line number Diff line number Diff line change
@@ -1,19 +1,45 @@
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=

0 comments on commit 356e7d2

Please sign in to comment.