Files
quic-go/metrics/tracer.go
2024-08-03 19:32:11 -07:00

148 lines
4.2 KiB
Go

package metrics
import (
"errors"
"fmt"
"net"
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/qerr"
"github.com/quic-go/quic-go/logging"
"github.com/prometheus/client_golang/prometheus"
)
const metricNamespace = "quicgo"
func getIPVersion(addr net.Addr) string {
udpAddr, ok := addr.(*net.UDPAddr)
if !ok {
return ""
}
if udpAddr.IP.To4() != nil {
return "ipv4"
}
return "ipv6"
}
var (
connsRejected = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: metricNamespace,
Name: "server_connections_rejected_total",
Help: "Connections Rejected",
},
[]string{"ip_version", "reason"},
)
packetDropped = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: metricNamespace,
Name: "server_received_packets_dropped_total",
Help: "packets dropped",
},
[]string{"ip_version", "reason"},
)
)
// NewTracer creates a new tracer using the default Prometheus registerer.
// The Tracer returned from this function can be used to collect metrics for
// events happening before the establishment of a QUIC connection.
// It can be set on the Tracer field of quic.Transport.
func NewTracer() *logging.Tracer {
return NewTracerWithRegisterer(prometheus.DefaultRegisterer)
}
// NewTracerWithRegisterer creates a new tracer using a given Prometheus registerer.
func NewTracerWithRegisterer(registerer prometheus.Registerer) *logging.Tracer {
for _, c := range [...]prometheus.Collector{
connsRejected,
packetDropped,
} {
if err := registerer.Register(c); err != nil {
if ok := errors.As(err, &prometheus.AlreadyRegisteredError{}); !ok {
panic(err)
}
}
}
return &logging.Tracer{
SentPacket: func(addr net.Addr, hdr *logging.Header, _ logging.ByteCount, frames []logging.Frame) {
tags := getStringSlice()
defer putStringSlice(tags)
var reason string
switch {
case hdr.Type == protocol.PacketTypeRetry:
reason = "retry"
case hdr.Type == protocol.PacketTypeInitial:
var ccf *logging.ConnectionCloseFrame
for _, f := range frames {
cc, ok := f.(*logging.ConnectionCloseFrame)
if ok {
ccf = cc
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) {
case qerr.ConnectionRefused:
reason = "connection_refused"
case qerr.InvalidToken:
reason = "invalid_token"
default:
// This shouldn't happen, the server doesn't send CONNECTION_CLOSE frames with different errors.
reason = fmt.Sprintf("transport_error: %d", ccf.ErrorCode)
}
} else {
// This shouldn't happen, the server doesn't send application-level CONNECTION_CLOSE frames.
reason = "application_error"
}
}
*tags = append(*tags, getIPVersion(addr))
*tags = append(*tags, reason)
connsRejected.WithLabelValues(*tags...).Inc()
},
SentVersionNegotiationPacket: func(addr net.Addr, _, _ logging.ArbitraryLenConnectionID, _ []logging.Version) {
tags := getStringSlice()
defer putStringSlice(tags)
*tags = append(*tags, getIPVersion(addr))
*tags = append(*tags, "version_negotiation")
connsRejected.WithLabelValues(*tags...).Inc()
},
DroppedPacket: func(addr net.Addr, pt logging.PacketType, _ logging.ByteCount, reason logging.PacketDropReason) {
tags := getStringSlice()
defer putStringSlice(tags)
var dropReason string
//nolint:exhaustive // Only a few drop reasons applicable.
switch reason {
case logging.PacketDropDOSPrevention:
if pt == logging.PacketType0RTT {
dropReason = "0rtt_dos_prevention"
} else {
dropReason = "dos_prevention"
}
case logging.PacketDropHeaderParseError:
dropReason = "header_parsing"
case logging.PacketDropPayloadDecryptError:
dropReason = "payload_decrypt"
case logging.PacketDropUnexpectedPacket:
dropReason = "unexpected_packet"
default:
dropReason = "unknown"
}
*tags = append(*tags, getIPVersion(addr))
*tags = append(*tags, dropReason)
packetDropped.WithLabelValues(*tags...).Inc()
},
}
}