66package tracer
77
88import (
9- "bytes"
10- "encoding/binary"
119 "io"
1210 "sync"
13- "sync/atomic"
14-
15- "github.com/DataDog/dd-trace-go/v2/internal/processtags"
16- "github.com/tinylib/msgp/msgp"
1711)
1812
1913// payloadStats contains the statistics of a payload.
@@ -52,206 +46,35 @@ type payload interface {
5246 payloadReader
5347}
5448
55- // unsafePayload is a wrapper on top of the msgpack encoder which allows constructing an
56- // encoded array by pushing its entries sequentially, one at a time. It basically
57- // allows us to encode as we would with a stream, except that the contents of the stream
58- // can be read as a slice by the msgpack decoder at any time. It follows the guidelines
59- // from the msgpack array spec:
60- // https://github.com/msgpack/msgpack/blob/master/spec.md#array-format-family
61- //
62- // unsafePayload implements io.Reader and can be used with the decoder directly.
63- //
64- // unsafePayload is not safe for concurrent use.
65- //
66- // unsafePayload is meant to be used only once and eventually dismissed with the
67- // single exception of retrying failed flush attempts.
68- //
69- // ⚠️ Warning!
70- //
71- // The payload should not be reused for multiple sets of traces. Resetting the
72- // payload for re-use requires the transport to wait for the HTTP package to
73- // Close the request body before attempting to re-use it again! This requires
74- // additional logic to be in place. See:
75- //
76- // • https://github.com/golang/go/blob/go1.16/src/net/http/client.go#L136-L138
77- // • https://github.com/DataDog/dd-trace-go/pull/475
78- // • https://github.com/DataDog/dd-trace-go/pull/549
79- // • https://github.com/DataDog/dd-trace-go/pull/976
80- type unsafePayload struct {
81- // header specifies the first few bytes in the msgpack stream
82- // indicating the type of array (fixarray, array16 or array32)
83- // and the number of items contained in the stream.
84- header []byte
85-
86- // off specifies the current read position on the header.
87- off int
88-
89- // count specifies the number of items in the stream.
90- count uint32
91-
92- // buf holds the sequence of msgpack-encoded items.
93- buf bytes.Buffer
94-
95- // reader is used for reading the contents of buf.
96- reader * bytes.Reader
97-
98- // protocolVersion specifies the trace protocolVersion to use.
99- protocolVersion float64
100- }
101-
102- var _ io.Reader = (* unsafePayload )(nil )
103-
104- // newUnsafePayload returns a ready to use unsafe payload.
105- func newUnsafePayload (protocol float64 ) * unsafePayload {
106- p := & unsafePayload {
107- header : make ([]byte , 8 ),
108- off : 8 ,
109- protocolVersion : protocol ,
110- }
111- return p
112- }
113-
114- // push pushes a new item into the stream.
115- func (p * unsafePayload ) push (t []* Span ) (stats payloadStats , err error ) {
116- p .setTracerTags (t )
117- sl := spanList (t )
118- p .buf .Grow (sl .Msgsize ())
119- if err := msgp .Encode (& p .buf , sl ); err != nil {
120- return payloadStats {}, err
121- }
122- p .recordItem ()
123- return p .stats (), nil
124- }
125-
126- func (p * unsafePayload ) setTracerTags (t []* Span ) {
127- // set on first chunk
128- if atomic .LoadUint32 (& p .count ) != 0 {
129- return
130- }
131- if len (t ) == 0 {
132- return
133- }
134- pTags := processtags .GlobalTags ().String ()
135- if pTags == "" {
136- return
49+ // newPayload returns a ready to use payload.
50+ func newPayload (protocol float64 ) payload {
51+ if protocol == traceProtocolV1 {
52+ return & safePayload {
53+ p : newPayloadV1 (),
54+ }
13755 }
138- t [0 ].setProcessTags (pTags )
139- }
140-
141- // itemCount returns the number of items available in the stream.
142- func (p * unsafePayload ) itemCount () int {
143- return int (atomic .LoadUint32 (& p .count ))
144- }
145-
146- // size returns the payload size in bytes. After the first read the value becomes
147- // inaccurate by up to 8 bytes.
148- func (p * unsafePayload ) size () int {
149- return p .buf .Len () + len (p .header ) - p .off
150- }
151-
152- // reset sets up the payload to be read a second time. It maintains the
153- // underlying byte contents of the buffer. reset should not be used in order to
154- // reuse the payload for another set of traces.
155- func (p * unsafePayload ) reset () {
156- p .updateHeader ()
157- if p .reader != nil {
158- p .reader .Seek (0 , 0 )
56+ return & safePayload {
57+ p : newPayloadV04 (),
15958 }
16059}
16160
162- // clear empties the payload buffers.
163- func (p * unsafePayload ) clear () {
164- p .buf = bytes.Buffer {}
165- p .reader = nil
166- }
167-
16861// https://github.com/msgpack/msgpack/blob/master/spec.md#array-format-family
16962const (
63+ // arrays
17064 msgpackArrayFix byte = 144 // up to 15 items
17165 msgpackArray16 byte = 0xdc // up to 2^16-1 items, followed by size in 2 bytes
17266 msgpackArray32 byte = 0xdd // up to 2^32-1 items, followed by size in 4 bytes
173- )
174-
175- // updateHeader updates the payload header based on the number of items currently
176- // present in the stream.
177- func (p * unsafePayload ) updateHeader () {
178- n := uint64 (atomic .LoadUint32 (& p .count ))
179- switch {
180- case n <= 15 :
181- p .header [7 ] = msgpackArrayFix + byte (n )
182- p .off = 7
183- case n <= 1 << 16 - 1 :
184- binary .BigEndian .PutUint64 (p .header , n ) // writes 2 bytes
185- p .header [5 ] = msgpackArray16
186- p .off = 5
187- default : // n <= 1<<32-1
188- binary .BigEndian .PutUint64 (p .header , n ) // writes 4 bytes
189- p .header [3 ] = msgpackArray32
190- p .off = 3
191- }
192- }
193-
194- // Close implements io.Closer
195- func (p * unsafePayload ) Close () error {
196- return nil
197- }
198-
199- // Read implements io.Reader. It reads from the msgpack-encoded stream.
200- func (p * unsafePayload ) Read (b []byte ) (n int , err error ) {
201- if p .off < len (p .header ) {
202- // reading header
203- n = copy (b , p .header [p .off :])
204- p .off += n
205- return n , nil
206- }
207- if p .reader == nil {
208- p .reader = bytes .NewReader (p .buf .Bytes ())
209- }
210- return p .reader .Read (b )
211- }
212-
213- // Write implements io.Writer. It writes data directly to the buffer.
214- func (p * unsafePayload ) Write (data []byte ) (n int , err error ) {
215- return p .buf .Write (data )
216- }
217-
218- // grow grows the buffer to ensure it can accommodate n more bytes.
219- func (p * unsafePayload ) grow (n int ) {
220- p .buf .Grow (n )
221- }
22267
223- // recordItem records that an item was added and updates the header.
224- func (p * unsafePayload ) recordItem () {
225- atomic .AddUint32 (& p .count , 1 )
226- p .updateHeader ()
227- }
228-
229- // stats returns the current stats of the payload.
230- func (p * unsafePayload ) stats () payloadStats {
231- return payloadStats {
232- size : p .size (),
233- itemCount : int (atomic .LoadUint32 (& p .count )),
234- }
235- }
236-
237- // protocol returns the protocol version of the payload.
238- func (p * unsafePayload ) protocol () float64 {
239- return p .protocolVersion
240- }
241-
242- var _ io.Reader = (* safePayload )(nil )
243-
244- // newPayload returns a ready to use thread-safe payload.
245- func newPayload (protocol float64 ) payload {
246- return & safePayload {
247- p : newUnsafePayload (protocol ),
248- }
249- }
68+ // maps
69+ msgpackMapFix byte = 0x80 // up to 15 items
70+ msgpackMap16 byte = 0xde // up to 2^16-1 items, followed by size in 2 bytes
71+ msgpackMap32 byte = 0xdf // up to 2^32-1 items, followed by size in 4 bytes
72+ )
25073
251- // safePayload provides a thread-safe wrapper around unsafePayload .
74+ // safePayload provides a thread-safe wrapper around payload .
25275type safePayload struct {
25376 mu sync.RWMutex
254- p * unsafePayload
77+ p payload
25578}
25679
25780// push pushes a new item into the stream in a thread-safe manner.
@@ -262,9 +85,9 @@ func (sp *safePayload) push(t spanList) (stats payloadStats, err error) {
26285}
26386
26487// itemCount returns the number of items available in the stream in a thread-safe manner.
88+ // This method is not thread-safe, but the underlying payload.itemCount() must be.
26589func (sp * safePayload ) itemCount () int {
266- // Use direct atomic access for better performance - no mutex needed
267- return int (atomic .LoadUint32 (& sp .p .count ))
90+ return sp .p .itemCount ()
26891}
26992
27093// size returns the payload size in bytes in a thread-safe manner.
0 commit comments