Skip to content

Commit 11fba59

Browse files
chore: polish metrics
1 parent 12567e6 commit 11fba59

File tree

3 files changed

+119
-40
lines changed

3 files changed

+119
-40
lines changed

metrics/httpmetrics/httpmetrics.go

+78-26
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,101 @@
11
package httpmetrics
22

33
import (
4+
"cmp"
45
"net/http"
56

67
"github.com/felixge/httpsnoop"
8+
"go.inout.gg/foundations/debug"
79
"go.inout.gg/foundations/http/httpmiddleware"
10+
"go.inout.gg/foundations/metrics"
811
"go.inout.gg/foundations/must"
912
"go.opentelemetry.io/otel"
1013
"go.opentelemetry.io/otel/attribute"
1114
"go.opentelemetry.io/otel/metric"
1215
)
1316

14-
var (
15-
provider = otel.GetMeterProvider()
16-
meter = provider.Meter("foundations:httpmetrics")
17-
18-
requestDurationHisto = must.Must(meter.Int64Histogram(
19-
"request_duration_ms",
20-
metric.WithDescription("The incoming request duration in milliseconds."),
21-
metric.WithUnit("ms"),
22-
metric.WithExplicitBucketBoundaries(1, 5, 10, 25, 50, 100, 200, 500, 1_000, 5_000, 10_000, 30_000, 60_000),
23-
))
24-
responseBodySizeHisto = must.Must(meter.Int64Histogram(
25-
"response_body_size_bytes",
26-
metric.WithDescription("The outgoing response body size in bytes."),
27-
metric.WithUnit("bytes"),
28-
metric.WithExplicitBucketBoundaries(1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000),
29-
))
17+
const (
18+
name = "go.inout.gg/metrics/httpmetrics"
3019
)
3120

21+
type stats struct {
22+
inflightRequests metric.Int64UpDownCounter
23+
requestsDuration metric.Int64Histogram
24+
responseBodySize metric.Int64Histogram
25+
}
26+
27+
func newStats(m metric.Meter) *stats {
28+
return &stats{
29+
inflightRequests: must.Must(m.Int64UpDownCounter(
30+
metrics.FormatMetricName("inflight_requests"),
31+
metric.WithDescription("The number of inflight requests."),
32+
metric.WithUnit("{request}"),
33+
)),
34+
requestsDuration: must.Must(m.Int64Histogram(
35+
metrics.FormatMetricName("request_duration_ms"),
36+
metric.WithDescription("The incoming request duration in milliseconds."),
37+
metric.WithUnit("ms"),
38+
metric.WithExplicitBucketBoundaries(1, 5, 10, 25, 50, 100, 200, 500, 1_000, 5_000, 10_000, 30_000, 60_000),
39+
)),
40+
responseBodySize: must.Must(m.Int64Histogram(
41+
metrics.FormatMetricName("request_body_size_bytes"),
42+
metric.WithDescription("The incoming request body size in bytes."),
43+
metric.WithUnit("bytes"),
44+
metric.WithExplicitBucketBoundaries(1, 5, 10, 25, 50, 100, 200, 500, 1_000, 5_000, 10_000, 30_000, 60_000),
45+
)),
46+
}
47+
}
48+
49+
func (s *stats) RecordInflightRequest(r *http.Request) func() {
50+
attrs := attribute.NewSet(
51+
attribute.String("method", r.Method),
52+
attribute.String("path", r.URL.Path),
53+
)
54+
55+
s.inflightRequests.Add(r.Context(), 1, metric.WithAttributeSet(attrs))
56+
57+
return func() {
58+
s.inflightRequests.Add(r.Context(), -1, metric.WithAttributeSet(attrs))
59+
}
60+
}
61+
62+
func (s *stats) RecordHandledRequest(r *http.Request, metrics httpsnoop.Metrics) {
63+
ctx := r.Context()
64+
attrs := attribute.NewSet(
65+
attribute.Int("code", metrics.Code),
66+
attribute.String("method", r.Method),
67+
attribute.String("path", r.URL.Path),
68+
)
69+
70+
s.requestsDuration.Record(ctx, metrics.Duration.Milliseconds(), metric.WithAttributeSet(attrs))
71+
s.responseBodySize.Record(ctx, metrics.Written, metric.WithAttributeSet(attrs))
72+
}
73+
74+
type Config struct {
75+
Provider metric.MeterProvider
76+
}
77+
78+
func (c *Config) defaults() {
79+
c.Provider = cmp.Or(c.Provider, otel.GetMeterProvider())
80+
}
81+
3282
// Middleware returns a middleware that captures metrics for incoming HTTP requests.
33-
func Middleware() httpmiddleware.Middleware {
83+
func Middleware(cfg *Config) httpmiddleware.MiddlewareFunc {
84+
cfg.defaults()
85+
debug.Assert(cfg.Provider != nil, "provider is nil")
86+
87+
var (
88+
meter = cfg.Provider.Meter(name)
89+
stats = newStats(meter)
90+
)
91+
3492
return httpmiddleware.MiddlewareFunc(func(next http.Handler) http.Handler {
3593
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
36-
ctx := r.Context()
94+
finishInflightRequest := stats.RecordInflightRequest(r)
95+
defer finishInflightRequest()
3796

3897
metrics := httpsnoop.CaptureMetrics(next, w, r)
39-
defaultAttributes := attribute.NewSet(
40-
attribute.Int("code", metrics.Code),
41-
attribute.String("method", r.Method),
42-
attribute.String("path", r.URL.Path),
43-
)
44-
45-
requestDurationHisto.Record(ctx, metrics.Duration.Milliseconds(), metric.WithAttributeSet(defaultAttributes))
46-
responseBodySizeHisto.Record(ctx, metrics.Written, metric.WithAttributeSet(defaultAttributes))
98+
stats.RecordHandledRequest(r, metrics)
4799
})
48100
})
49101
}

metrics/metrics.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package metrics
2+
3+
const (
4+
prefix = "foundations."
5+
)
6+
7+
func FormatMetricName(name string) string {
8+
return prefix + name
9+
}

metrics/sqldbmetrics/sqldbmetrics.go

+32-14
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package sqldbmetrics
22

33
import (
4+
"cmp"
45
"context"
56

67
"github.com/jackc/pgx/v5/pgxpool"
8+
"go.inout.gg/foundations/debug"
9+
"go.inout.gg/foundations/metrics"
710
"go.inout.gg/foundations/must"
811
"go.opentelemetry.io/otel"
912
"go.opentelemetry.io/otel/metric"
1013
)
1114

15+
const (
16+
name = "go.inout.gg/foundations/metrics/sqldbmetrics"
17+
)
18+
1219
type stats struct {
1320
meter metric.Meter
1421
acquireCount metric.Int64ObservableCounter
@@ -28,18 +35,18 @@ type stats struct {
2835
func newStats(meter metric.Meter) *stats {
2936
return &stats{
3037
meter: meter,
31-
acquireCount: must.Must(meter.Int64ObservableCounter("acquire_count", metric.WithDescription("Number of acquire operations"))),
32-
acquireDuration: must.Must(meter.Int64Histogram("acquire_duration", metric.WithDescription("Duration of acquire operations"))),
33-
acquiredConns: must.Must(meter.Int64ObservableCounter("acquired_conns", metric.WithDescription("Number of acquired connections"))),
34-
canceledAcquireCount: must.Must(meter.Int64ObservableCounter("canceled_acquire_count", metric.WithDescription("Number of canceled acquire operations"))),
35-
constructingConns: must.Must(meter.Int64ObservableCounter("constructing_conns", metric.WithDescription("Number of connections being constructed"))),
36-
emptyAcquireCount: must.Must(meter.Int64ObservableCounter("empty_acquire_count", metric.WithDescription("Number of empty acquire operations"))),
37-
idleConns: must.Must(meter.Int64ObservableCounter("idle_conns", metric.WithDescription("Number of idle connections"))),
38-
maxConns: must.Must(meter.Int64ObservableCounter("max_conns", metric.WithDescription("Maximum number of connections"))),
39-
maxIdleDestroyCount: must.Must(meter.Int64ObservableCounter("max_idle_destroy_count", metric.WithDescription("Number of connections destroyed due to max idle"))),
40-
maxLifetimeDestroyCount: must.Must(meter.Int64ObservableCounter("max_lifetime_destroy_count", metric.WithDescription("Number of connections destroyed due to max lifetime"))),
41-
newConnsCount: must.Must(meter.Int64ObservableCounter("new_conns_count", metric.WithDescription("Number of new connections"))),
42-
totalConns: must.Must(meter.Int64ObservableCounter("total_conns", metric.WithDescription("Total number of connections"))),
38+
acquireCount: must.Must(meter.Int64ObservableCounter(metrics.FormatMetricName("acquire_count"), metric.WithDescription("Number of acquire operations"))),
39+
acquireDuration: must.Must(meter.Int64Histogram(metrics.FormatMetricName("acquire_duration"), metric.WithDescription("Duration of acquire operations"))),
40+
acquiredConns: must.Must(meter.Int64ObservableCounter(metrics.FormatMetricName("acquired_conns"), metric.WithDescription("Number of acquired connections"))),
41+
canceledAcquireCount: must.Must(meter.Int64ObservableCounter(metrics.FormatMetricName("canceled_acquire_count"), metric.WithDescription("Number of canceled acquire operations"))),
42+
constructingConns: must.Must(meter.Int64ObservableCounter(metrics.FormatMetricName("constructing_conns"), metric.WithDescription("Number of connections being constructed"))),
43+
emptyAcquireCount: must.Must(meter.Int64ObservableCounter(metrics.FormatMetricName("empty_acquire_count"), metric.WithDescription("Number of empty acquire operations"))),
44+
idleConns: must.Must(meter.Int64ObservableCounter(metrics.FormatMetricName("idle_conns"), metric.WithDescription("Number of idle connections"))),
45+
maxConns: must.Must(meter.Int64ObservableCounter(metrics.FormatMetricName("max_conns"), metric.WithDescription("Maximum number of connections"))),
46+
maxIdleDestroyCount: must.Must(meter.Int64ObservableCounter(metrics.FormatMetricName("max_idle_destroy_count"), metric.WithDescription("Number of connections destroyed due to max idle"))),
47+
maxLifetimeDestroyCount: must.Must(meter.Int64ObservableCounter(metrics.FormatMetricName("max_lifetime_destroy_count"), metric.WithDescription("Number of connections destroyed due to max lifetime"))),
48+
newConnsCount: must.Must(meter.Int64ObservableCounter(metrics.FormatMetricName("new_conns_count"), metric.WithDescription("Number of new connections"))),
49+
totalConns: must.Must(meter.Int64ObservableCounter(metrics.FormatMetricName("total_conns_count"), metric.WithDescription("Total number of connections"))),
4350
}
4451
}
4552

@@ -66,10 +73,21 @@ func (s *stats) register(p *pgxpool.Pool) error {
6673
return err
6774
}
6875

69-
func MustRegister(p *pgxpool.Pool) {
76+
type Config struct {
77+
Provider metric.MeterProvider
78+
}
79+
80+
func (c *Config) defaults() {
81+
c.Provider = cmp.Or(c.Provider, otel.GetMeterProvider())
82+
}
83+
84+
func MustRegister(p *pgxpool.Pool, cfg *Config) {
85+
cfg.defaults()
86+
debug.Assert(cfg.Provider != nil, "provider is nil")
87+
7088
var (
7189
provider = otel.GetMeterProvider()
72-
meter = provider.Meter("foundations:sqldbmetrics")
90+
meter = provider.Meter(name)
7391
)
7492

7593
must.Must1(newStats(meter).register(p))

0 commit comments

Comments
 (0)