Skip to content

Commit c1c4fdf

Browse files
authored
agent: add per-endpoint proxy metrics (#220)
Fixes #216, where if the agent runs multiple endpoints, it attempts to register the same metrics multiple times so panics. This extends the proxy metrics to include an endpoint label, such as: ``` piko_agent_requests_total{endpoint="endpoint1",method="GET",status="200"} 1034 piko_agent_requests_total{endpoint="endpoint2",method="GET",status="200"} 1234 ``` It also registers the metrics only once to avoid panicing.
1 parent 6ace8bf commit c1c4fdf

File tree

3 files changed

+142
-8
lines changed

3 files changed

+142
-8
lines changed

agent/reverseproxy/metrics.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package reverseproxy
2+
3+
import (
4+
"net/http"
5+
"strconv"
6+
"time"
7+
8+
"github.com/gin-gonic/gin"
9+
"github.com/prometheus/client_golang/prometheus"
10+
)
11+
12+
type Metrics struct {
13+
RequestsInFlight *prometheus.GaugeVec
14+
RequestsTotal *prometheus.CounterVec
15+
RequestLatency *prometheus.HistogramVec
16+
RequestSize *prometheus.HistogramVec
17+
ResponseSize *prometheus.HistogramVec
18+
}
19+
20+
func NewMetrics(subsystem string) *Metrics {
21+
sizeBuckets := prometheus.ExponentialBuckets(256, 4, 8)
22+
return &Metrics{
23+
RequestsInFlight: prometheus.NewGaugeVec(
24+
prometheus.GaugeOpts{
25+
Namespace: "piko",
26+
Subsystem: subsystem,
27+
Name: "requests_in_flight",
28+
Help: "Number of requests currently handled by this server.",
29+
},
30+
[]string{"endpoint"},
31+
),
32+
RequestsTotal: prometheus.NewCounterVec(
33+
prometheus.CounterOpts{
34+
Namespace: "piko",
35+
Subsystem: subsystem,
36+
Name: "requests_total",
37+
Help: "Total requests.",
38+
},
39+
[]string{"status", "method", "endpoint"},
40+
),
41+
RequestLatency: prometheus.NewHistogramVec(
42+
prometheus.HistogramOpts{
43+
Namespace: "piko",
44+
Subsystem: subsystem,
45+
Name: "request_latency_seconds",
46+
Help: "Request latency.",
47+
Buckets: prometheus.DefBuckets,
48+
},
49+
[]string{"status", "method", "endpoint"},
50+
),
51+
RequestSize: prometheus.NewHistogramVec(
52+
prometheus.HistogramOpts{
53+
Namespace: "piko",
54+
Subsystem: subsystem,
55+
Name: "request_size_bytes",
56+
Help: "Request size",
57+
Buckets: sizeBuckets,
58+
},
59+
[]string{"endpoint"},
60+
),
61+
ResponseSize: prometheus.NewHistogramVec(
62+
prometheus.HistogramOpts{
63+
Namespace: "piko",
64+
Subsystem: subsystem,
65+
Name: "response_size_bytes",
66+
Help: "Response size",
67+
Buckets: sizeBuckets,
68+
},
69+
[]string{"endpoint"},
70+
),
71+
}
72+
}
73+
74+
func (m *Metrics) Handler(endpoint string) gin.HandlerFunc {
75+
return func(c *gin.Context) {
76+
m.RequestsInFlight.With(prometheus.Labels{
77+
"endpoint": endpoint,
78+
}).Inc()
79+
defer m.RequestsInFlight.With(prometheus.Labels{
80+
"endpoint": endpoint,
81+
}).Dec()
82+
83+
start := time.Now()
84+
85+
// Process request.
86+
c.Next()
87+
88+
m.RequestsTotal.With(prometheus.Labels{
89+
"status": strconv.Itoa(c.Writer.Status()),
90+
"method": c.Request.Method,
91+
"endpoint": endpoint,
92+
}).Inc()
93+
m.RequestLatency.With(prometheus.Labels{
94+
"status": strconv.Itoa(c.Writer.Status()),
95+
"method": c.Request.Method,
96+
"endpoint": endpoint,
97+
}).Observe(float64(time.Since(start).Milliseconds()) / 1000)
98+
m.RequestSize.With(prometheus.Labels{
99+
"endpoint": endpoint,
100+
}).Observe(float64(computeApproximateRequestSize(c.Request)))
101+
m.ResponseSize.With(prometheus.Labels{
102+
"endpoint": endpoint,
103+
}).Observe(float64(c.Writer.Size()))
104+
}
105+
}
106+
107+
func (m *Metrics) Register(registry *prometheus.Registry) {
108+
registry.MustRegister(
109+
m.RequestsInFlight,
110+
m.RequestsTotal,
111+
m.RequestLatency,
112+
m.RequestSize,
113+
m.ResponseSize,
114+
)
115+
}
116+
117+
func computeApproximateRequestSize(r *http.Request) int {
118+
s := 0
119+
if r.URL != nil {
120+
s += len(r.URL.String())
121+
}
122+
123+
s += len(r.Method)
124+
s += len(r.Proto)
125+
for name, values := range r.Header {
126+
s += len(name)
127+
for _, value := range values {
128+
s += len(value)
129+
}
130+
}
131+
s += len(r.Host)
132+
133+
if r.ContentLength != -1 {
134+
s += int(r.ContentLength)
135+
}
136+
return s
137+
}

agent/reverseproxy/server.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"net/http"
88

99
"github.com/gin-gonic/gin"
10-
"github.com/prometheus/client_golang/prometheus"
1110
"go.uber.org/zap"
1211
"go.uber.org/zap/zapcore"
1312

@@ -28,7 +27,7 @@ type Server struct {
2827

2928
func NewServer(
3029
conf config.ListenerConfig,
31-
registry *prometheus.Registry,
30+
metrics *Metrics,
3231
logger log.Logger,
3332
) *Server {
3433
logger = logger.WithSubsystem("proxy.http")
@@ -50,11 +49,7 @@ func NewServer(
5049

5150
s.router.Use(middleware.NewLogger(conf.AccessLog, logger))
5251

53-
metrics := middleware.NewMetrics("agent")
54-
if registry != nil {
55-
metrics.Register(registry)
56-
}
57-
router.Use(metrics.Handler())
52+
router.Use(metrics.Handler(conf.EndpointID))
5853

5954
s.router.NoRoute(s.proxyRoute)
6055

cli/agent/command.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ func runAgent(conf *config.Config, logger log.Logger) error {
121121
}
122122

123123
registry := prometheus.NewRegistry()
124+
proxyMetrics := reverseproxy.NewMetrics("agent")
125+
proxyMetrics.Register(registry)
124126

125127
var group rungroup.Group
126128

@@ -138,7 +140,7 @@ func runAgent(conf *config.Config, logger log.Logger) error {
138140
defer ln.Close()
139141

140142
if listenerConfig.Protocol == config.ListenerProtocolHTTP {
141-
server := reverseproxy.NewServer(listenerConfig, registry, logger)
143+
server := reverseproxy.NewServer(listenerConfig, proxyMetrics, logger)
142144

143145
// Listener handler.
144146
group.Add(func() error {

0 commit comments

Comments
 (0)