forked from quic-go/quic-go
qlogwriter: implement the draft-12 trace header (#5360)
This commit is contained in:
@@ -38,108 +38,65 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
type topLevel struct {
|
||||
trace trace
|
||||
type traceHeader struct {
|
||||
VantagePointType string
|
||||
GroupID *ConnectionID
|
||||
ReferenceTime time.Time
|
||||
}
|
||||
|
||||
func (l topLevel) Encode(enc *jsontext.Encoder) error {
|
||||
func (l traceHeader) Encode(enc *jsontext.Encoder) error {
|
||||
h := encoderHelper{enc: enc}
|
||||
h.WriteToken(jsontext.BeginObject)
|
||||
h.WriteToken(jsontext.String("file_schema"))
|
||||
h.WriteToken(jsontext.String("urn:ietf:params:qlog:file:sequential"))
|
||||
h.WriteToken(jsontext.String("serialization_format"))
|
||||
h.WriteToken(jsontext.String("application/qlog+json-seq"))
|
||||
h.WriteToken(jsontext.String("title"))
|
||||
h.WriteToken(jsontext.String("quic-go qlog"))
|
||||
h.WriteToken(jsontext.String("code_version"))
|
||||
h.WriteToken(jsontext.String(quicGoVersion))
|
||||
|
||||
h.WriteToken(jsontext.String("trace"))
|
||||
// trace
|
||||
h.WriteToken(jsontext.BeginObject)
|
||||
h.WriteToken(jsontext.String("vantage_point"))
|
||||
// -- vantage_point
|
||||
h.WriteToken(jsontext.BeginObject)
|
||||
h.WriteToken(jsontext.String("type"))
|
||||
h.WriteToken(jsontext.String(l.VantagePointType))
|
||||
// -- end vantage_point
|
||||
h.WriteToken(jsontext.EndObject)
|
||||
|
||||
h.WriteToken(jsontext.String("common_fields"))
|
||||
// -- common_fields
|
||||
h.WriteToken(jsontext.BeginObject)
|
||||
if l.GroupID != nil {
|
||||
h.WriteToken(jsontext.String("group_id"))
|
||||
h.WriteToken(jsontext.String(l.GroupID.String()))
|
||||
}
|
||||
h.WriteToken(jsontext.String("reference_time"))
|
||||
// ---- reference_time
|
||||
h.WriteToken(jsontext.BeginObject)
|
||||
h.WriteToken(jsontext.String("clock_type"))
|
||||
h.WriteToken(jsontext.String("monotonic"))
|
||||
h.WriteToken(jsontext.String("epoch"))
|
||||
h.WriteToken(jsontext.String("unknown"))
|
||||
h.WriteToken(jsontext.String("wall_clock_time"))
|
||||
h.WriteToken(jsontext.String(l.ReferenceTime.Format(time.RFC3339Nano)))
|
||||
// ---- end reference_time
|
||||
h.WriteToken(jsontext.EndObject)
|
||||
// -- end common_fields
|
||||
h.WriteToken(jsontext.EndObject)
|
||||
// end trace
|
||||
h.WriteToken(jsontext.EndObject)
|
||||
|
||||
// The following fields are not required by the qlog draft anymore,
|
||||
// but qvis still requires them to be present.
|
||||
h.WriteToken(jsontext.String("qlog_format"))
|
||||
h.WriteToken(jsontext.String("JSON-SEQ"))
|
||||
h.WriteToken(jsontext.String("qlog_version"))
|
||||
h.WriteToken(jsontext.String("0.3"))
|
||||
h.WriteToken(jsontext.String("title"))
|
||||
h.WriteToken(jsontext.String("quic-go qlog"))
|
||||
h.WriteToken(jsontext.String("configuration"))
|
||||
if err := (configuration{Version: quicGoVersion}).Encode(enc); err != nil {
|
||||
return err
|
||||
}
|
||||
h.WriteToken(jsontext.String("trace"))
|
||||
if err := l.trace.Encode(enc); err != nil {
|
||||
return err
|
||||
}
|
||||
h.WriteToken(jsontext.EndObject)
|
||||
return h.err
|
||||
}
|
||||
|
||||
type configuration struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
func (c configuration) Encode(enc *jsontext.Encoder) error {
|
||||
h := encoderHelper{enc: enc}
|
||||
h.WriteToken(jsontext.BeginObject)
|
||||
h.WriteToken(jsontext.String("code_version"))
|
||||
h.WriteToken(jsontext.String(c.Version))
|
||||
h.WriteToken(jsontext.EndObject)
|
||||
return h.err
|
||||
}
|
||||
|
||||
type vantagePoint struct {
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
|
||||
func (p vantagePoint) Encode(enc *jsontext.Encoder) error {
|
||||
h := encoderHelper{enc: enc}
|
||||
h.WriteToken(jsontext.BeginObject)
|
||||
if p.Name != "" {
|
||||
h.WriteToken(jsontext.String("name"))
|
||||
h.WriteToken(jsontext.String(p.Name))
|
||||
}
|
||||
if p.Type != "" {
|
||||
h.WriteToken(jsontext.String("type"))
|
||||
h.WriteToken(jsontext.String(p.Type))
|
||||
}
|
||||
h.WriteToken(jsontext.EndObject)
|
||||
return h.err
|
||||
}
|
||||
|
||||
type commonFields struct {
|
||||
ODCID *ConnectionID
|
||||
GroupID *ConnectionID
|
||||
ProtocolType string
|
||||
ReferenceTime time.Time
|
||||
}
|
||||
|
||||
func (f commonFields) Encode(enc *jsontext.Encoder) error {
|
||||
h := encoderHelper{enc: enc}
|
||||
h.WriteToken(jsontext.BeginObject)
|
||||
if f.ODCID != nil {
|
||||
h.WriteToken(jsontext.String("ODCID"))
|
||||
h.WriteToken(jsontext.String(f.ODCID.String()))
|
||||
h.WriteToken(jsontext.String("group_id"))
|
||||
h.WriteToken(jsontext.String(f.ODCID.String()))
|
||||
}
|
||||
if f.ProtocolType != "" {
|
||||
h.WriteToken(jsontext.String("protocol_type"))
|
||||
h.WriteToken(jsontext.String(f.ProtocolType))
|
||||
}
|
||||
h.WriteToken(jsontext.String("reference_time"))
|
||||
h.WriteToken(jsontext.Float(float64(f.ReferenceTime.UnixNano()) / 1e6))
|
||||
h.WriteToken(jsontext.String("time_format"))
|
||||
h.WriteToken(jsontext.String("relative"))
|
||||
h.WriteToken(jsontext.EndObject)
|
||||
return h.err
|
||||
}
|
||||
|
||||
type trace struct {
|
||||
VantagePoint vantagePoint
|
||||
CommonFields commonFields
|
||||
}
|
||||
|
||||
func (t trace) Encode(enc *jsontext.Encoder) error {
|
||||
h := encoderHelper{enc: enc}
|
||||
h.WriteToken(jsontext.BeginObject)
|
||||
h.WriteToken(jsontext.String("vantage_point"))
|
||||
if err := t.VantagePoint.Encode(enc); err != nil {
|
||||
return err
|
||||
}
|
||||
h.WriteToken(jsontext.String("common_fields"))
|
||||
if err := t.CommonFields.Encode(enc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.WriteToken(jsontext.EndObject)
|
||||
return h.err
|
||||
}
|
||||
|
||||
@@ -40,18 +40,33 @@ func unmarshal(data []byte, v any) error {
|
||||
return json.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
func newTracer() (Trace, *bytes.Buffer) {
|
||||
buf := &bytes.Buffer{}
|
||||
trace := NewFileSeq(nopWriteCloser(buf))
|
||||
go trace.Run()
|
||||
return trace, buf
|
||||
func TestTraceMetadata(t *testing.T) {
|
||||
t.Run("non-connection trace", func(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
trace := NewFileSeq(nopWriteCloser(buf))
|
||||
go trace.Run()
|
||||
producer := trace.AddProducer()
|
||||
producer.Close()
|
||||
|
||||
testTraceMetadata(t, buf, "transport", "")
|
||||
})
|
||||
|
||||
t.Run("connection trace", func(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
trace := NewConnectionFileSeq(
|
||||
nopWriteCloser(buf),
|
||||
false,
|
||||
protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
|
||||
)
|
||||
go trace.Run()
|
||||
producer := trace.AddProducer()
|
||||
producer.Close()
|
||||
|
||||
testTraceMetadata(t, buf, "server", "deadbeef")
|
||||
})
|
||||
}
|
||||
|
||||
func TestTraceMetadata(t *testing.T) {
|
||||
trace, buf := newTracer()
|
||||
producer := trace.AddProducer()
|
||||
producer.Close()
|
||||
|
||||
func testTraceMetadata(t *testing.T, buf *bytes.Buffer, expectedVantagePoint, expectedGroupID string) {
|
||||
var m map[string]any
|
||||
require.NoError(t, unmarshal(buf.Bytes(), &m))
|
||||
require.Equal(t, "0.3", m["qlog_version"])
|
||||
@@ -60,49 +75,24 @@ func TestTraceMetadata(t *testing.T) {
|
||||
tr := m["trace"].(map[string]any)
|
||||
require.Contains(t, tr, "common_fields")
|
||||
commonFields := tr["common_fields"].(map[string]any)
|
||||
require.NotContains(t, commonFields, "ODCID")
|
||||
require.NotContains(t, commonFields, "group_id")
|
||||
if expectedGroupID != "" {
|
||||
require.Contains(t, commonFields, "group_id")
|
||||
require.Equal(t, expectedGroupID, commonFields["group_id"])
|
||||
} else {
|
||||
require.NotContains(t, commonFields, "group_id")
|
||||
}
|
||||
require.Contains(t, commonFields, "reference_time")
|
||||
referenceTime := time.Unix(0, int64(commonFields["reference_time"].(float64)*1e6))
|
||||
require.WithinDuration(t, time.Now(), referenceTime, scaleDuration(10*time.Millisecond))
|
||||
require.Equal(t, "relative", commonFields["time_format"])
|
||||
referenceTimeMap := commonFields["reference_time"].(map[string]any)
|
||||
require.Contains(t, referenceTimeMap, "clock_type")
|
||||
require.Equal(t, "monotonic", referenceTimeMap["clock_type"])
|
||||
require.Contains(t, referenceTimeMap, "epoch")
|
||||
require.Equal(t, "unknown", referenceTimeMap["epoch"])
|
||||
require.Contains(t, referenceTimeMap, "wall_clock_time")
|
||||
wallClockTimeStr := referenceTimeMap["wall_clock_time"].(string)
|
||||
wallClockTime, err := time.Parse(time.RFC3339Nano, wallClockTimeStr)
|
||||
require.NoError(t, err)
|
||||
require.WithinDuration(t, time.Now(), wallClockTime, scaleDuration(10*time.Millisecond))
|
||||
require.Contains(t, tr, "vantage_point")
|
||||
vantagePoint := tr["vantage_point"].(map[string]any)
|
||||
require.Equal(t, "transport", vantagePoint["type"])
|
||||
}
|
||||
|
||||
func newConnectionTracer(t *testing.T) (Recorder, *bytes.Buffer) {
|
||||
buf := &bytes.Buffer{}
|
||||
trace := NewConnectionFileSeq(
|
||||
nopWriteCloser(buf),
|
||||
false,
|
||||
protocol.ParseConnectionID([]byte{0xde, 0xad, 0xbe, 0xef}),
|
||||
)
|
||||
go trace.Run()
|
||||
recorder := trace.AddProducer()
|
||||
t.Cleanup(func() { recorder.Close() })
|
||||
return recorder, buf
|
||||
}
|
||||
|
||||
func TestConnectionTraceMetadata(t *testing.T) {
|
||||
tracer, buf := newConnectionTracer(t)
|
||||
tracer.Close()
|
||||
|
||||
m := make(map[string]any)
|
||||
require.NoError(t, unmarshal(buf.Bytes(), &m))
|
||||
require.Equal(t, "0.3", m["qlog_version"])
|
||||
require.Contains(t, m, "title")
|
||||
require.Contains(t, m, "trace")
|
||||
trace := m["trace"].(map[string]any)
|
||||
require.Contains(t, trace, "common_fields")
|
||||
commonFields := trace["common_fields"].(map[string]any)
|
||||
require.Equal(t, "deadbeef", commonFields["ODCID"])
|
||||
require.Equal(t, "deadbeef", commonFields["group_id"])
|
||||
require.Contains(t, commonFields, "reference_time")
|
||||
referenceTime := time.Unix(0, int64(commonFields["reference_time"].(float64)*1e6))
|
||||
require.WithinDuration(t, time.Now(), referenceTime, scaleDuration(10*time.Millisecond))
|
||||
require.Equal(t, "relative", commonFields["time_format"])
|
||||
require.Contains(t, trace, "vantage_point")
|
||||
vantagePoint := trace["vantage_point"].(map[string]any)
|
||||
require.Equal(t, "server", vantagePoint["type"])
|
||||
require.Equal(t, expectedVantagePoint, vantagePoint["type"])
|
||||
}
|
||||
|
||||
@@ -70,20 +70,16 @@ func NewConnectionFileSeq(w io.WriteCloser, isClient bool, odcid ConnectionID) *
|
||||
|
||||
func newFileSeq(w io.WriteCloser, pers string, odcid *ConnectionID) *FileSeq {
|
||||
now := time.Now()
|
||||
tr := &trace{
|
||||
VantagePoint: vantagePoint{Type: pers},
|
||||
CommonFields: commonFields{
|
||||
ODCID: odcid,
|
||||
GroupID: odcid,
|
||||
ReferenceTime: now,
|
||||
},
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
enc := jsontext.NewEncoder(buf)
|
||||
if _, err := buf.Write(recordSeparator); err != nil {
|
||||
panic(fmt.Sprintf("qlog encoding into a bytes.Buffer failed: %s", err))
|
||||
}
|
||||
if err := (&topLevel{trace: *tr}).Encode(enc); err != nil {
|
||||
if err := (&traceHeader{
|
||||
VantagePointType: pers,
|
||||
GroupID: odcid,
|
||||
ReferenceTime: now,
|
||||
}).Encode(enc); err != nil {
|
||||
panic(fmt.Sprintf("qlog encoding into a bytes.Buffer failed: %s", err))
|
||||
}
|
||||
_, encodeErr := w.Write(buf.Bytes())
|
||||
|
||||
Reference in New Issue
Block a user