forked from quic-go/quic-go
228 lines
7.2 KiB
Go
228 lines
7.2 KiB
Go
package qlog
|
|
|
|
import (
|
|
"net"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/lucas-clemente/quic-go/internal/protocol"
|
|
|
|
"github.com/francoispqt/gojay"
|
|
)
|
|
|
|
var eventFields = [4]string{"time", "category", "event", "data"}
|
|
|
|
type events []event
|
|
|
|
var _ sort.Interface = &events{}
|
|
var _ gojay.MarshalerJSONArray = events{}
|
|
|
|
func (e events) IsNil() bool { return e == nil }
|
|
func (e events) Len() int { return len(e) }
|
|
func (e events) Less(i, j int) bool {
|
|
// Don't use time.Before() here.
|
|
// Before() uses monotonic time.
|
|
// We need to make sure that the actual exported timestamp are sorted.
|
|
return e[i].Time.UnixNano() < e[j].Time.UnixNano()
|
|
}
|
|
func (e events) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
|
|
|
func (e events) MarshalJSONArray(enc *gojay.Encoder) {
|
|
// Event timestamps are taken from multiple go routines.
|
|
// For example, the receiving go routine sets the receive time of the packet.
|
|
// Therefore, events can end up being slightly out of order.
|
|
// It turns out that Go's stable sort implementation is a lot faster in that case.
|
|
// See https://gist.github.com/marten-seemann/30251742b378318097e5400ea170c00f for benchmarking code.
|
|
sort.Stable(e)
|
|
for _, ev := range e {
|
|
enc.Array(ev)
|
|
}
|
|
}
|
|
|
|
type eventDetails interface {
|
|
Category() category
|
|
Name() string
|
|
gojay.MarshalerJSONObject
|
|
}
|
|
|
|
type event struct {
|
|
Time time.Time
|
|
eventDetails
|
|
}
|
|
|
|
var _ gojay.MarshalerJSONArray = event{}
|
|
|
|
func (e event) IsNil() bool { return false }
|
|
func (e event) MarshalJSONArray(enc *gojay.Encoder) {
|
|
enc.Float64(float64(e.Time.UnixNano()) / 1e6)
|
|
enc.String(e.Category().String())
|
|
enc.String(e.Name())
|
|
enc.Object(e.eventDetails)
|
|
}
|
|
|
|
type eventConnectionStarted struct {
|
|
SrcAddr *net.UDPAddr
|
|
DestAddr *net.UDPAddr
|
|
|
|
Version protocol.VersionNumber
|
|
SrcConnectionID protocol.ConnectionID
|
|
DestConnectionID protocol.ConnectionID
|
|
|
|
// TODO: add ALPN
|
|
}
|
|
|
|
var _ eventDetails = &eventConnectionStarted{}
|
|
|
|
func (e eventConnectionStarted) Category() category { return categoryTransport }
|
|
func (e eventConnectionStarted) Name() string { return "connection_started" }
|
|
func (e eventConnectionStarted) IsNil() bool { return false }
|
|
|
|
func (e eventConnectionStarted) MarshalJSONObject(enc *gojay.Encoder) {
|
|
// If ip is not an IPv4 address, To4 returns nil.
|
|
// Note that there might be some corner cases, where this is not correct.
|
|
// See https://stackoverflow.com/questions/22751035/golang-distinguish-ipv4-ipv6.
|
|
isIPv6 := e.SrcAddr.IP.To4() == nil
|
|
if isIPv6 {
|
|
enc.StringKey("ip_version", "ipv6")
|
|
} else {
|
|
enc.StringKey("ip_version", "ipv4")
|
|
}
|
|
enc.StringKey("src_ip", e.SrcAddr.IP.String())
|
|
enc.IntKey("src_port", e.SrcAddr.Port)
|
|
enc.StringKey("dst_ip", e.DestAddr.IP.String())
|
|
enc.IntKey("dst_port", e.DestAddr.Port)
|
|
enc.StringKey("quic_version", versionNumber(e.Version).String())
|
|
enc.StringKey("src_cid", connectionID(e.SrcConnectionID).String())
|
|
enc.StringKey("dst_cid", connectionID(e.DestConnectionID).String())
|
|
}
|
|
|
|
type eventPacketSent struct {
|
|
PacketType packetType
|
|
Header packetHeader
|
|
Frames frames
|
|
IsCoalesced bool
|
|
Trigger string
|
|
}
|
|
|
|
var _ eventDetails = eventPacketSent{}
|
|
|
|
func (e eventPacketSent) Category() category { return categoryTransport }
|
|
func (e eventPacketSent) Name() string { return "packet_sent" }
|
|
func (e eventPacketSent) IsNil() bool { return false }
|
|
|
|
func (e eventPacketSent) MarshalJSONObject(enc *gojay.Encoder) {
|
|
enc.StringKey("packet_type", e.PacketType.String())
|
|
enc.ObjectKey("header", e.Header)
|
|
enc.ArrayKeyOmitEmpty("frames", e.Frames)
|
|
enc.BoolKeyOmitEmpty("is_coalesced", e.IsCoalesced)
|
|
enc.StringKeyOmitEmpty("trigger", e.Trigger)
|
|
}
|
|
|
|
type eventPacketReceived struct {
|
|
PacketType packetType
|
|
Header packetHeader
|
|
Frames frames
|
|
IsCoalesced bool
|
|
Trigger string
|
|
}
|
|
|
|
var _ eventDetails = eventPacketReceived{}
|
|
|
|
func (e eventPacketReceived) Category() category { return categoryTransport }
|
|
func (e eventPacketReceived) Name() string { return "packet_received" }
|
|
func (e eventPacketReceived) IsNil() bool { return false }
|
|
|
|
func (e eventPacketReceived) MarshalJSONObject(enc *gojay.Encoder) {
|
|
enc.StringKey("packet_type", e.PacketType.String())
|
|
enc.ObjectKey("header", e.Header)
|
|
enc.ArrayKeyOmitEmpty("frames", e.Frames)
|
|
enc.BoolKeyOmitEmpty("is_coalesced", e.IsCoalesced)
|
|
enc.StringKeyOmitEmpty("trigger", e.Trigger)
|
|
}
|
|
|
|
type eventRetryReceived struct {
|
|
Header packetHeader
|
|
}
|
|
|
|
func (e eventRetryReceived) Category() category { return categoryTransport }
|
|
func (e eventRetryReceived) Name() string { return "packet_received" }
|
|
func (e eventRetryReceived) IsNil() bool { return false }
|
|
|
|
func (e eventRetryReceived) MarshalJSONObject(enc *gojay.Encoder) {
|
|
enc.StringKey("packet_type", packetTypeRetry.String())
|
|
enc.ObjectKey("header", e.Header)
|
|
}
|
|
|
|
func milliseconds(dur time.Duration) float64 { return float64(dur.Nanoseconds()) / 1e6 }
|
|
|
|
type eventMetricsUpdated struct {
|
|
MinRTT time.Duration
|
|
SmoothedRTT time.Duration
|
|
LatestRTT time.Duration
|
|
RTTVariance time.Duration
|
|
|
|
CongestionWindow protocol.ByteCount
|
|
BytesInFlight protocol.ByteCount
|
|
PacketsInFlight int
|
|
}
|
|
|
|
func (e eventMetricsUpdated) Category() category { return categoryRecovery }
|
|
func (e eventMetricsUpdated) Name() string { return "metrics_updated" }
|
|
func (e eventMetricsUpdated) IsNil() bool { return false }
|
|
|
|
func (e eventMetricsUpdated) MarshalJSONObject(enc *gojay.Encoder) {
|
|
enc.FloatKey("min_rtt", milliseconds(e.MinRTT))
|
|
enc.FloatKey("smoothed_rtt", milliseconds(e.SmoothedRTT))
|
|
enc.FloatKey("latest_rtt", milliseconds(e.LatestRTT))
|
|
enc.FloatKey("rtt_variance", milliseconds(e.RTTVariance))
|
|
|
|
enc.Uint64Key("congestion_window", uint64(e.CongestionWindow))
|
|
enc.Uint64Key("bytes_in_flight", uint64(e.BytesInFlight))
|
|
enc.Uint64KeyOmitEmpty("packets_in_flight", uint64(e.PacketsInFlight))
|
|
}
|
|
|
|
type eventUpdatedPTO struct {
|
|
Value uint32
|
|
}
|
|
|
|
func (e eventUpdatedPTO) Category() category { return categoryRecovery }
|
|
func (e eventUpdatedPTO) Name() string { return "metrics_updated" }
|
|
func (e eventUpdatedPTO) IsNil() bool { return false }
|
|
|
|
func (e eventUpdatedPTO) MarshalJSONObject(enc *gojay.Encoder) {
|
|
enc.Uint32Key("pto_count", e.Value)
|
|
}
|
|
|
|
type eventPacketLost struct {
|
|
PacketType packetType
|
|
PacketNumber protocol.PacketNumber
|
|
Trigger PacketLossReason
|
|
}
|
|
|
|
func (e eventPacketLost) Category() category { return categoryRecovery }
|
|
func (e eventPacketLost) Name() string { return "packet_lost" }
|
|
func (e eventPacketLost) IsNil() bool { return false }
|
|
|
|
func (e eventPacketLost) MarshalJSONObject(enc *gojay.Encoder) {
|
|
enc.StringKey("packet_type", e.PacketType.String())
|
|
enc.StringKey("packet_number", toString(int64(e.PacketNumber)))
|
|
enc.StringKey("trigger", e.Trigger.String())
|
|
}
|
|
|
|
type eventKeyUpdated struct {
|
|
Trigger keyUpdateTrigger
|
|
KeyType keyType
|
|
Generation protocol.KeyPhase
|
|
// we don't log the keys here, so we don't need `old` and `new`.
|
|
}
|
|
|
|
func (e eventKeyUpdated) Category() category { return categorySecurity }
|
|
func (e eventKeyUpdated) Name() string { return "key_updated" }
|
|
func (e eventKeyUpdated) IsNil() bool { return false }
|
|
|
|
func (e eventKeyUpdated) MarshalJSONObject(enc *gojay.Encoder) {
|
|
enc.StringKey("trigger", e.Trigger.String())
|
|
enc.StringKey("key_type", e.KeyType.String())
|
|
enc.Uint64KeyOmitEmpty("generation", uint64(e.Generation))
|
|
}
|