Skip to content

Commit 28382ec

Browse files
committed
feat(ddtrace/tracer): embedded streaming string table for v1 trace protocol
1 parent dec355c commit 28382ec

File tree

1 file changed

+136
-73
lines changed

1 file changed

+136
-73
lines changed

ddtrace/tracer/payload_v1.go

Lines changed: 136 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ type payloadV1 struct {
3434
// the 0th position in the stringTable should always be the empty string.
3535
strings *stringTable
3636

37-
// Bitmap to track which index fields are set (bits 1-11 for field IDs 1-11)
37+
// fieldSet tracks which index fields are set (bits 1-11 for field IDs 1-11)
3838
// Bit 0 is unused since field IDs start from 1
39-
fieldSet uint16
39+
fieldSet bitmap
4040

4141
// Array to store the actual index values for set fields
4242
// Index in array corresponds to field ID (fieldValues[1] = containerID value, etc.)
@@ -190,21 +190,24 @@ func (p *payloadV1) Read(b []byte) (int, error) {
190190
}
191191

192192
func (p *payloadV1) encode() {
193-
p.buf = encodeField(p.buf, 1, p.strings)
193+
// fieldEncoded tracks which index fields have been encoded (bits 1-11 for field IDs 1-11)
194+
// Bit 0 is unused since field IDs start from 1.
195+
var fieldEncoded bitmap
196+
194197
for i := uint32(2); i <= 9; i++ {
195-
if !p.isFieldSet(i) {
198+
if !p.hasIndexField(i) {
199+
continue
200+
}
201+
if fieldEncoded.Has(i) {
202+
p.buf = encodeField(p.buf, i, p.getIndexField(i))
196203
continue
197204
}
198-
p.buf = encodeField(p.buf, i, p.getIndexField(i))
205+
v := encodableString(p.getStringField(i))
206+
p.buf = encodeField(p.buf, i, &v)
207+
fieldEncoded.Set(i)
199208
}
200209
}
201210

202-
func encodeField[T encodeDecoder](buf []byte, ref uint32, w T) []byte {
203-
buf = msgp.AppendUint32(buf, ref)
204-
buf = w.encode(buf)
205-
return buf
206-
}
207-
208211
// Write implements io.Writer. It writes data directly to the internal buffers.
209212
func (p *payloadV1) Write(data []byte) (int, error) {
210213
p.buf = append(p.buf, data...)
@@ -227,7 +230,7 @@ func (p *payloadV1) hydrate() ([]byte, error) {
227230
break
228231
}
229232
switch field {
230-
case 1: // strings
233+
case 1: // strings - unused because we are using an embedded streaming string table
231234
o, err = p.strings.decode(o)
232235
case 2: // containerID
233236
fallthrough
@@ -244,12 +247,25 @@ func (p *payloadV1) hydrate() ([]byte, error) {
244247
case 8: // hostname
245248
fallthrough
246249
case 9: // appVersion
247-
var idx index
248-
o, err = idx.decode(o)
249-
if err != nil {
250-
break
250+
switch detectStringOrUint32Format(o[0]) {
251+
case 0: // string
252+
var v encodableString
253+
o, err = v.decode(o)
254+
if err != nil {
255+
break
256+
}
257+
idx := p.strings.Add(string(v))
258+
p.setIndexField(field, idx)
259+
case 1: // string
260+
var idx index
261+
o, err = idx.decode(o)
262+
if err != nil {
263+
break
264+
}
265+
p.setIndexField(field, idx)
266+
default:
267+
err = fmt.Errorf("invalid type on field %d", field)
251268
}
252-
p.setIndexField(field, idx)
253269
case 10, 11:
254270
// TODO: implement remaining fields
255271
default:
@@ -268,32 +284,12 @@ func (p *payloadV1) Close() error {
268284
return nil
269285
}
270286

271-
type encodeDecoder interface {
272-
encode([]byte) []byte
273-
decode([]byte) ([]byte, error)
274-
}
275-
276-
type index uint32
277-
278-
func (i index) encode(buf []byte) []byte {
279-
return msgp.AppendUint32(buf, uint32(i))
280-
}
281-
282-
func (i *index) decode(buf []byte) ([]byte, error) {
283-
v, o, err := msgp.ReadUint32Bytes(buf)
284-
if err != nil {
285-
return o, err
286-
}
287-
*i = index(v)
288-
return o, nil
289-
}
290-
291287
// getIndexField returns the index value for the given field ID if it's set, nil otherwise
292288
func (p *payloadV1) getIndexField(fieldID uint32) *index {
293289
if fieldID == 0 || fieldID >= 12 {
294290
return nil
295291
}
296-
if p.fieldSet&(1<<fieldID) != 0 {
292+
if p.fieldSet.Has(fieldID) {
297293
return &p.fieldValues[fieldID]
298294
}
299295
return nil
@@ -305,24 +301,24 @@ func (p *payloadV1) setIndexField(fieldID uint32, value index) {
305301
return
306302
}
307303
p.fieldValues[fieldID] = value
308-
p.fieldSet |= (1 << fieldID)
304+
p.fieldSet.Set(fieldID)
309305
p.fields++
310306
}
311307

312-
// isFieldSet checks if a field is set using bitwise operations
313-
func (p *payloadV1) isFieldSet(fieldID uint32) bool {
308+
// hasIndexField checks if a field is set using bitwise operations
309+
func (p *payloadV1) hasIndexField(fieldID uint32) bool {
314310
if fieldID == 0 || fieldID >= 12 {
315311
return false
316312
}
317-
return p.fieldSet&(1<<fieldID) != 0
313+
return p.fieldSet.Has(fieldID)
318314
}
319315

320316
// getStringField returns the string value for the given field ID by looking up the index in the string table
321317
func (p *payloadV1) getStringField(fieldID uint32) string {
322318
if fieldID == 0 || fieldID >= 12 {
323319
return ""
324320
}
325-
if p.fieldSet&(1<<fieldID) == 0 {
321+
if !p.fieldSet.Has(fieldID) {
326322
return ""
327323
}
328324
idx := p.fieldValues[fieldID]
@@ -382,12 +378,108 @@ func (p *payloadV1) SetAppVersion(value string) {
382378
p.setIndexField(9, idx)
383379
}
384380

381+
func encodeField[T encodeDecoder](buf []byte, ref uint32, w T) []byte {
382+
buf = msgp.AppendUint32(buf, ref)
383+
buf = w.encode(buf)
384+
return buf
385+
}
386+
387+
// detectStringOrUint32Format examines the first byte of MessagePack data
388+
// to determine if it represents a string or uint32 format.
389+
// Returns 0 if string, 1 if uint32, or -1 if invalid.
390+
func detectStringOrUint32Format(firstByte byte) int8 {
391+
switch firstByte {
392+
// String formats
393+
case 0xd9, 0xda, 0xdb: // str8, str16, str32
394+
return 0
395+
case 0xce: // uint32
396+
return 1
397+
default:
398+
// Check for fixstr: high 3 bits should be 0b101 (0xa0)
399+
if firstByte&0xe0 == 0xa0 {
400+
return 0
401+
}
402+
// Check for positive fixint: high bit should be 0 (values 0-127)
403+
if firstByte&0x80 == 0 {
404+
return 1
405+
}
406+
return -1
407+
}
408+
}
409+
410+
type encodableString string
411+
412+
func (es encodableString) encode(buf []byte) []byte {
413+
return msgp.AppendString(buf, string(es))
414+
}
415+
416+
func (es *encodableString) decode(buf []byte) ([]byte, error) {
417+
v, o, err := msgp.ReadStringBytes(buf)
418+
if err != nil {
419+
return o, err
420+
}
421+
*es = encodableString(v)
422+
return o, nil
423+
}
424+
425+
type encodeDecoder interface {
426+
encode([]byte) []byte
427+
decode([]byte) ([]byte, error)
428+
}
429+
430+
type index uint32
431+
432+
func (i index) encode(buf []byte) []byte {
433+
return msgp.AppendUint32(buf, uint32(i))
434+
}
435+
436+
func (i *index) decode(buf []byte) ([]byte, error) {
437+
v, o, err := msgp.ReadUint32Bytes(buf)
438+
if err != nil {
439+
return o, err
440+
}
441+
*i = index(v)
442+
return o, nil
443+
}
444+
445+
type bitmap uint16
446+
447+
func (bm *bitmap) Set(i uint32) {
448+
*bm |= (1 << i)
449+
}
450+
451+
func (bm bitmap) Has(i uint32) bool {
452+
return bm&(1<<i) != 0
453+
}
454+
385455
type stringTable struct {
386456
strings []string // list of strings
387457
indices map[unique.Handle[string]]index // map strings to their indices
388458
nextIndex index // last index of the stringTable
389459
}
390460

461+
func newStringTable() *stringTable {
462+
return &stringTable{
463+
strings: []string{""},
464+
indices: map[unique.Handle[string]]index{
465+
unique.Make(""): 0,
466+
},
467+
nextIndex: 1,
468+
}
469+
}
470+
471+
func (s *stringTable) Add(str string) index {
472+
k := unique.Make(str)
473+
if v, ok := s.indices[k]; ok {
474+
return v
475+
}
476+
v := s.nextIndex
477+
s.indices[k] = v
478+
s.strings = append(s.strings, str)
479+
s.nextIndex += 1
480+
return v
481+
}
482+
391483
func (st *stringTable) encode(buf []byte) []byte {
392484
o := msgp.AppendArrayHeader(buf, uint32(len(st.strings)))
393485
for ix := range st.strings {
@@ -439,44 +531,15 @@ const (
439531
keyValueListType // []keyValue -- 7
440532
)
441533

442-
// keys in a keyValue can either be a string or a uint32 index
443-
// isString is true when the key is a string value, and false when the key is a uint32 index
444-
type streamingKey struct {
445-
isString bool
446-
stringValue string
447-
}
448-
449534
// keyValue is made up of the key and an AnyValue (the type of the value and the value itself)
450535
// The key is either a uint32 index into the string table or a string value.
451536
type keyValue struct {
452-
key streamingKey
537+
key encodableString
453538
value anyValue
454539
}
455540

456541
type keyValueList []keyValue
457542

458-
func newStringTable() *stringTable {
459-
return &stringTable{
460-
strings: []string{""},
461-
indices: map[unique.Handle[string]]index{
462-
unique.Make(""): 0,
463-
},
464-
nextIndex: 1,
465-
}
466-
}
467-
468-
func (s *stringTable) Add(str string) index {
469-
k := unique.Make(str)
470-
if v, ok := s.indices[k]; ok {
471-
return v
472-
}
473-
v := s.nextIndex
474-
s.indices[k] = v
475-
s.strings = append(s.strings, str)
476-
s.nextIndex += 1
477-
return v
478-
}
479-
480543
// traceChunk represents a list of spans with the same trace ID,
481544
// i.e. a chunk of a trace
482545
type traceChunk struct {

0 commit comments

Comments
 (0)