metrics: add a very basic ConnectionTracer

This commit is contained in:
Marten Seemann
2024-01-13 15:27:40 +07:00
parent a555a14ae3
commit 2968b93ea8
3 changed files with 131 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
package metrics
import (
"context"
"errors"
"net"
"time"
"github.com/quic-go/quic-go/logging"
"github.com/prometheus/client_golang/prometheus"
)
var (
connStarted = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: metricNamespace,
Name: "connections_started_total",
Help: "Connections Started",
},
[]string{"dir"},
)
connClosed = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: metricNamespace,
Name: "connections_closed_total",
Help: "Connections Closed",
},
[]string{"dir"},
)
connDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: metricNamespace,
Name: "connection_duration_seconds",
Help: "Duration of a Connection",
Buckets: prometheus.ExponentialBuckets(1.0/16, 2, 25), // up to 24 days
},
[]string{"dir"},
)
)
// DefaultTracer returns a callback that creates a metrics ConnectionTracer.
// The ConnectionTracer returned can be set on the quic.Config for a new connection.
// It should be reused across QUIC connections.
func DefaultTracer() func(_ context.Context, p logging.Perspective, _ logging.ConnectionID) *logging.ConnectionTracer {
return DefaultTracerWithRegisterer(prometheus.DefaultRegisterer)
}
// DefaultTracerWithRegisterer returns a callback that creates a metrics ConnectionTracer
// using a given Prometheus registerer.
func DefaultTracerWithRegisterer(registerer prometheus.Registerer) func(_ context.Context, p logging.Perspective, _ logging.ConnectionID) *logging.ConnectionTracer {
return func(_ context.Context, p logging.Perspective, _ logging.ConnectionID) *logging.ConnectionTracer {
switch p {
case logging.PerspectiveClient:
return NewClientConnectionTracerWithRegisterer(registerer)
case logging.PerspectiveServer:
return NewServerConnectionTracerWithRegisterer(registerer)
default:
panic("invalid perspective")
}
}
}
// NewClientConnectionTracerWithRegisterer creates a new connection tracer for a connection
// dialed on the client side with a given Prometheus registerer.
func NewClientConnectionTracerWithRegisterer(registerer prometheus.Registerer) *logging.ConnectionTracer {
return newConnectionTracerWithRegisterer(registerer, true)
}
// NewServerConnectionTracerWithRegisterer creates a new connection tracer for a connection
// accepted on the server side with a given Prometheus registerer.
func NewServerConnectionTracerWithRegisterer(registerer prometheus.Registerer) *logging.ConnectionTracer {
return newConnectionTracerWithRegisterer(registerer, false)
}
func newConnectionTracerWithRegisterer(registerer prometheus.Registerer, isClient bool) *logging.ConnectionTracer {
for _, c := range [...]prometheus.Collector{
connStarted,
connClosed,
connDuration,
} {
if err := registerer.Register(c); err != nil {
if ok := errors.As(err, &prometheus.AlreadyRegisteredError{}); !ok {
panic(err)
}
}
}
direction := "incoming"
if isClient {
direction = "outgoing"
}
var startTime time.Time
return &logging.ConnectionTracer{
StartedConnection: func(_, _ net.Addr, _, _ logging.ConnectionID) {
tags := getStringSlice()
defer putStringSlice(tags)
startTime = time.Now()
*tags = append(*tags, direction)
connStarted.WithLabelValues(*tags...).Inc()
},
ClosedConnection: func(_ error) {
tags := getStringSlice()
defer putStringSlice(tags)
*tags = append(*tags, direction)
connDuration.WithLabelValues(*tags...).Observe(time.Since(startTime).Seconds())
connClosed.WithLabelValues(*tags...).Inc()
},
}
}

View File

@@ -19,6 +19,18 @@ quic.Transport{
When using multiple `Transport`s, it is recommended to use the metrics tracer struct for all of them.
Set a metrics connection tracer on the `Config`:
```go
tracer := metrics.DefaultTracer()
quic.Config{
Tracer: tracer,
}
```
It is recommended to use the same connection tracer returned by `DefaultTracer` on the `Config`s for all connections.
Running:
```shell
docker-compose up

View File

@@ -83,6 +83,11 @@ func NewTracerWithRegisterer(registerer prometheus.Registerer) *logging.Tracer {
break
}
}
// This should never happen. We only send Initials before creating the connection in order to
// reject a connection attempt.
if ccf == nil {
return
}
if ccf.IsApplicationError {
//nolint:exhaustive // Only a few error codes applicable.
switch qerr.TransportErrorCode(ccf.ErrorCode) {