uquic/quictrace/tracer.go
2020-10-05 13:47:57 +07:00

203 lines
5.3 KiB
Go

package quictrace
import (
"fmt"
"time"
"google.golang.org/protobuf/proto"
"github.com/lucas-clemente/quic-go/internal/protocol"
"github.com/lucas-clemente/quic-go/internal/wire"
"github.com/lucas-clemente/quic-go/quictrace/pb"
)
type traceEvent struct {
connID protocol.ConnectionID
ev Event
}
// A tracer is used to trace a QUIC connection
type tracer struct {
eventQueue chan traceEvent
events map[string] /* conn ID */ []traceEvent
}
var _ Tracer = &tracer{}
// NewTracer creates a new Tracer
func NewTracer() Tracer {
qt := &tracer{
eventQueue: make(chan traceEvent, 1<<10),
events: make(map[string][]traceEvent),
}
go qt.run()
return qt
}
// Trace traces an event
func (t *tracer) Trace(connID protocol.ConnectionID, ev Event) {
t.eventQueue <- traceEvent{connID: connID, ev: ev}
}
func (t *tracer) run() {
for tev := range t.eventQueue {
key := string(tev.connID)
if _, ok := t.events[key]; !ok {
t.events[key] = make([]traceEvent, 0, 10*1<<10)
}
t.events[key] = append(t.events[key], tev)
}
}
func (t *tracer) GetAllTraces() map[string][]byte {
traces := make(map[string][]byte)
for connID := range t.events {
data, err := t.emitByConnIDAsString(connID)
if err != nil {
panic(err)
}
traces[connID] = data
}
return traces
}
// Emit emits the serialized protobuf that will be consumed by quic-trace
func (t *tracer) Emit(connID protocol.ConnectionID) ([]byte, error) {
return t.emitByConnIDAsString(string(connID))
}
func (t *tracer) emitByConnIDAsString(connID string) ([]byte, error) {
events, ok := t.events[connID]
if !ok {
return nil, fmt.Errorf("no trace found for connection ID %s", connID)
}
trace := &pb.Trace{
DestinationConnectionId: []byte{1, 2, 3, 4, 5, 6, 7, 8},
SourceConnectionId: []byte{1, 2, 3, 4, 5, 6, 7, 8},
ProtocolVersion: []byte{0xff, 0, 0, 19},
Events: make([]*pb.Event, len(events)),
}
var startTime time.Time
for i, ev := range events {
event := ev.ev
if i == 0 {
startTime = event.Time
}
packetNumber := uint64(event.PacketNumber)
packetSize := uint64(event.PacketSize)
trace.Events[i] = &pb.Event{
TimeUs: durationToUs(event.Time.Sub(startTime)),
EventType: getEventType(event.EventType),
PacketSize: &packetSize,
PacketNumber: &packetNumber,
TransportState: getTransportState(event.TransportState),
EncryptionLevel: getEncryptionLevel(event.EncryptionLevel),
Frames: getFrames(event.Frames),
}
}
delete(t.events, connID)
return proto.Marshal(trace)
}
func getEventType(evType EventType) *pb.EventType {
var t pb.EventType
switch evType {
case PacketSent:
t = pb.EventType_PACKET_SENT
case PacketReceived:
t = pb.EventType_PACKET_RECEIVED
case PacketLost:
t = pb.EventType_PACKET_LOST
default:
panic("unknown event type")
}
return &t
}
func getEncryptionLevel(encLevel protocol.EncryptionLevel) *pb.EncryptionLevel {
enc := pb.EncryptionLevel_ENCRYPTION_UNKNOWN
switch encLevel {
case protocol.EncryptionInitial:
enc = pb.EncryptionLevel_ENCRYPTION_INITIAL
case protocol.EncryptionHandshake:
enc = pb.EncryptionLevel_ENCRYPTION_HANDSHAKE
case protocol.Encryption0RTT:
enc = pb.EncryptionLevel_ENCRYPTION_0RTT
case protocol.Encryption1RTT:
enc = pb.EncryptionLevel_ENCRYPTION_1RTT
}
return &enc
}
func getFrames(wframes []wire.Frame) []*pb.Frame {
streamFrameType := pb.FrameType_STREAM
cryptoFrameType := pb.FrameType_CRYPTO
ackFrameType := pb.FrameType_ACK
var frames []*pb.Frame
for _, frame := range wframes {
switch f := frame.(type) {
case *wire.CryptoFrame:
offset := uint64(f.Offset)
length := uint64(len(f.Data))
frames = append(frames, &pb.Frame{
FrameType: &cryptoFrameType,
CryptoFrameInfo: &pb.CryptoFrameInfo{
Offset: &offset,
Length: &length,
},
})
case *wire.StreamFrame:
streamID := uint64(f.StreamID)
offset := uint64(f.Offset)
length := uint64(f.DataLen())
frames = append(frames, &pb.Frame{
FrameType: &streamFrameType,
StreamFrameInfo: &pb.StreamFrameInfo{
StreamId: &streamID,
Offset: &offset,
Length: &length,
},
})
case *wire.AckFrame:
var ackedPackets []*pb.AckBlock
for _, ackBlock := range f.AckRanges {
firstPacket := uint64(ackBlock.Smallest)
lastPacket := uint64(ackBlock.Largest)
ackedPackets = append(ackedPackets, &pb.AckBlock{
FirstPacket: &firstPacket,
LastPacket: &lastPacket,
})
}
frames = append(frames, &pb.Frame{
FrameType: &ackFrameType,
AckInfo: &pb.AckInfo{
AckDelayUs: durationToUs(f.DelayTime),
AckedPackets: ackedPackets,
},
})
}
}
return frames
}
func getTransportState(state *TransportState) *pb.TransportState {
bytesInFlight := uint64(state.BytesInFlight)
congestionWindow := uint64(state.CongestionWindow)
ccs := fmt.Sprintf("InSlowStart: %t, InRecovery: %t", state.InSlowStart, state.InRecovery)
return &pb.TransportState{
MinRttUs: durationToUs(state.MinRTT),
SmoothedRttUs: durationToUs(state.SmoothedRTT),
LastRttUs: durationToUs(state.LatestRTT),
InFlightBytes: &bytesInFlight,
CwndBytes: &congestionWindow,
CongestionControlState: &ccs,
}
}
func durationToUs(d time.Duration) *uint64 {
dur := uint64(d / 1000)
return &dur
}