diff --git a/cmd/hwtd/main.go b/cmd/hwtd/main.go index 7dc1da3..cbea177 100644 --- a/cmd/hwtd/main.go +++ b/cmd/hwtd/main.go @@ -6,7 +6,9 @@ import ( "os" "github.com/prometheus/client_golang/prometheus/promhttp" + "mcquay.me/hwt" + "mcquay.me/hwt/metrics" pb "mcquay.me/hwt/rpc/hwt" ) @@ -15,8 +17,14 @@ func main() { if err != nil { log.Fatalf("cannot get hostname: %v", err) } + + if err := metrics.RegisterPromMetrics(); err != nil { + log.Fatalf("registering prom metrics: %v", err) + } + s := &hwt.Server{hn} - th := pb.NewHelloWorldServer(s, nil) + hs := hwt.NewMetricsHooks(metrics.HTTPLatency) + th := pb.NewHelloWorldServer(s, hs) sm := http.NewServeMux() sm.Handle("/", th) sm.Handle("/metrics", promhttp.Handler()) diff --git a/hooks.go b/hooks.go new file mode 100644 index 0000000..06ba8a0 --- /dev/null +++ b/hooks.go @@ -0,0 +1,46 @@ +package hwt + +import ( + "context" + "time" + + "github.com/twitchtv/twirp" +) + +var reqStartKey = new(int) + +type Timer func(path string, dur time.Duration) + +func NewMetricsHooks(timer Timer) *twirp.ServerHooks { + hs := &twirp.ServerHooks{} + + hs.RequestReceived = func(ctx context.Context) (context.Context, error) { + return markReqStart(ctx), nil + } + + hs.ResponseSent = func(ctx context.Context) { + name, ok := twirp.MethodName(ctx) + if !ok { + // XXX (sm) : something else? + panic("missing name") + } + start, ok := getReqStart(ctx) + if !ok { + // XXX (sm) : something else? + panic("missing start") + } + dur := time.Now().Sub(start) + timer(name, dur) + } + + return hs +} + +func markReqStart(ctx context.Context) context.Context { + return context.WithValue(ctx, reqStartKey, time.Now()) +} + +func getReqStart(ctx context.Context) (time.Time, bool) { + t, ok := ctx.Value(reqStartKey).(time.Time) + return t, ok +} diff --git a/metrics/metrics.go b/metrics/metrics.go new file mode 100644 index 0000000..6dae5da --- /dev/null +++ b/metrics/metrics.go @@ -0,0 +1,9 @@ +package metrics + +import ( + "time" +) + +func HTTPLatency(path string, dur time.Duration) { + httpReqLat.WithLabelValues(path).Observe(float64(dur) / float64(time.Millisecond)) +} diff --git a/metrics/prom.go b/metrics/prom.go new file mode 100644 index 0000000..0714535 --- /dev/null +++ b/metrics/prom.go @@ -0,0 +1,23 @@ +package metrics + +import ( + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" +) + +var ( + httpReqLat = prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Name: "hwt_request_latency_ms", + Help: "Latency in ms of http requests grouped by req path", + }, + []string{"path"}, + ) +) + +func RegisterPromMetrics() error { + if err := prometheus.Register(httpReqLat); err != nil { + return errors.Wrap(err, "registering http request latency") + } + return nil +}