@@ -9,16 +9,29 @@ import (
99 "connectrpc.com/connect"
1010 "github.com/go-kit/log/level"
1111 "github.com/grafana/dskit/middleware"
12+ "github.com/grafana/pyroscope/pkg/tenant"
1213 "github.com/grafana/pyroscope/pkg/util"
14+ "github.com/prometheus/client_golang/prometheus"
1315 "google.golang.org/grpc"
1416 "google.golang.org/grpc/metadata"
1517)
1618
1719const (
18- // Capability names - update parseClientCapabilities below when new capabilities added
20+ // Capability names - update parseClientCapabilities and trackEnabledCapabilities
21+ // below when new capabilities added
1922 allowUtf8LabelNamesCapabilityName string = "allow-utf8-labelnames"
2023)
2124
25+ var (
26+ usage = prometheus .NewCounterVec (
27+ prometheus.CounterOpts {
28+ Name : "client_capability_enabled_total" ,
29+ Help : "Total number of requests with client capabilities enabled" ,
30+ },
31+ []string {"tenant" , "capability_name" },
32+ )
33+ )
34+
2235// Define a custom context key type to avoid collisions
2336type contextKey struct {}
2437
@@ -35,7 +48,35 @@ func GetClientCapabilities(ctx context.Context) (ClientCapabilities, bool) {
3548 return value , ok
3649}
3750
38- func ClientCapabilitiesGRPCMiddleware () grpc.UnaryServerInterceptor {
51+ type ClientCapabilityMiddleware struct {
52+ usage * prometheus.CounterVec
53+ }
54+
55+ func NewClientCapabilityMiddleware (reg prometheus.Registerer ) * ClientCapabilityMiddleware {
56+ usage = util .RegisterOrGet (reg , usage )
57+
58+ return & ClientCapabilityMiddleware {
59+ usage : usage ,
60+ }
61+ }
62+
63+ // trackEnabledCapabilities records metrics for each enabled capability
64+ func (m * ClientCapabilityMiddleware ) trackEnabledCapabilities (ctx context.Context , capabilities ClientCapabilities ) {
65+ tenantID , err := tenant .ExtractTenantIDFromContext (ctx )
66+ if err != nil {
67+ // Fall back if tenant cannot be extracted
68+ tenantID = tenant .DefaultTenantID
69+ }
70+
71+ // Track each enabled capability
72+ if capabilities .AllowUtf8LabelNames {
73+ m .usage .WithLabelValues (tenantID , allowUtf8LabelNamesCapabilityName ).Inc ()
74+ }
75+ }
76+
77+ // CreateGRPC creates gRPC middleware that extracts and parses the
78+ // `Accept` header for capabilities the client supports
79+ func (m * ClientCapabilityMiddleware ) CreateGRPC () grpc.UnaryServerInterceptor {
3980 return func (
4081 ctx context.Context ,
4182 req interface {},
@@ -62,13 +103,17 @@ func ClientCapabilitiesGRPCMiddleware() grpc.UnaryServerInterceptor {
62103 }
63104
64105 enhancedCtx := WithClientCapabilities (ctx , clientCapabilities )
106+
107+ // Track enabled capabilities for metrics
108+ m .trackEnabledCapabilities (enhancedCtx , clientCapabilities )
109+
65110 return handler (enhancedCtx , req )
66111 }
67112}
68113
69- // ClientCapabilitiesHttpMiddleware creates middleware that extracts and parses the
114+ // CreateHttp creates HTTP middleware that extracts and parses the
70115// `Accept` header for capabilities the client supports
71- func ClientCapabilitiesHttpMiddleware () middleware.Interface {
116+ func ( m * ClientCapabilityMiddleware ) CreateHttp () middleware.Interface {
72117 return middleware .Func (func (next http.Handler ) http.Handler {
73118 return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
74119 clientCapabilities , err := parseClientCapabilities (r .Header )
@@ -78,6 +123,10 @@ func ClientCapabilitiesHttpMiddleware() middleware.Interface {
78123 }
79124
80125 ctx := WithClientCapabilities (r .Context (), clientCapabilities )
126+
127+ // Track enabled capabilities for metrics
128+ m .trackEnabledCapabilities (ctx , clientCapabilities )
129+
81130 next .ServeHTTP (w , r .WithContext (ctx ))
82131 })
83132 })
0 commit comments