Skip to content

Instantly share code, notes, and snippets.

@radixdev
Created December 20, 2025 06:23
Show Gist options
  • Select an option

  • Save radixdev/6d1cb034ccb2fb8a7ec8df62a6541999 to your computer and use it in GitHub Desktop.

Select an option

Save radixdev/6d1cb034ccb2fb8a7ec8df62a6541999 to your computer and use it in GitHub Desktop.
pprof
Search regexp
main alloc_space
bytes.(*Buffer).grow
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/bytes/buffer.go
Total: 0 88.72MB (flat, cum) 28.32%
102 . .
103 . . // Reset resets the buffer to be empty,
104 . . // but it retains the underlying storage for use by future writes.
105 . . // Reset is the same as [Buffer.Truncate](0).
106 . . func (b *Buffer) Reset() {
107 . . b.buf = b.buf[:0]
108 . . b.off = 0
109 . . b.lastRead = opInvalid
110 . . }
111 . .
112 . . // tryGrowByReslice is an inlineable version of grow for the fast-case where the
113 . . // internal buffer only needs to be resliced.
114 . . // It returns the index where bytes should be written and whether it succeeded.
115 . . func (b *Buffer) tryGrowByReslice(n int) (int, bool) {
116 . . if l := len(b.buf); n <= cap(b.buf)-l {
117 . . b.buf = b.buf[:l+n]
118 . . return l, true
119 . . }
120 . . return 0, false
121 . . }
122 . .
123 . . // grow grows the buffer to guarantee space for n more bytes.
124 . . // It returns the index where bytes should be written.
125 . . // If the buffer can't grow it will panic with ErrTooLarge.
126 . . func (b *Buffer) grow(n int) int {
127 . . m := b.Len()
128 . . // If buffer is empty, reset to recover space.
129 . . if m == 0 && b.off != 0 {
130 . . b.Reset()
131 . . }
132 . . // Try to grow by means of a reslice.
133 . . if i, ok := b.tryGrowByReslice(n); ok {
134 . . return i
135 . . }
136 . . if b.buf == nil && n <= smallBufferSize {
137 . . b.buf = make([]byte, n, smallBufferSize)
138 . . return 0
139 . . }
140 . . c := cap(b.buf)
141 . . if n <= c/2-m {
142 . . // We can slide things down instead of allocating a new
143 . . // slice. We only need m+n <= c to slide, but
144 . . // we instead let capacity get twice as large so we
145 . . // don't spend all our time copying.
146 . . copy(b.buf, b.buf[b.off:])
147 . . } else if c > maxInt-c-n {
148 . . panic(ErrTooLarge)
149 . . } else {
150 . . // Add b.off to account for b.buf[:b.off] being sliced off the front.
151 . 88.72MB b.buf = growSlice(b.buf[b.off:], b.off+n)
152 . . }
153 . . // Restore b.off and len(b.buf).
154 . . b.off = 0
155 . . b.buf = b.buf[:m+n]
156 . . return m
157 . . }
158 . .
159 . . // Grow grows the buffer's capacity, if necessary, to guarantee space for
160 . . // another n bytes. After Grow(n), at least n bytes can be written to the
161 . . // buffer without another allocation.
bytes.(*Buffer).Write
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/bytes/buffer.go
Total: 0 1MB (flat, cum) 0.32%
170 . . }
171 . .
172 . . // Write appends the contents of p to the buffer, growing the buffer as
173 . . // needed. The return value n is the length of p; err is always nil. If the
174 . . // buffer becomes too large, Write will panic with [ErrTooLarge].
175 . . func (b *Buffer) Write(p []byte) (n int, err error) {
176 . . b.lastRead = opInvalid
177 . . m, ok := b.tryGrowByReslice(len(p))
178 . . if !ok {
179 . 1MB m = b.grow(len(p))
180 . . }
181 . . return copy(b.buf[m:], p), nil
182 . . }
183 . .
184 . . // WriteString appends the contents of s to the buffer, growing the buffer as
185 . . // needed. The return value n is the length of s; err is always nil. If the
186 . . // buffer becomes too large, WriteString will panic with [ErrTooLarge].
bytes.growSlice
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/bytes/buffer.go
Total: 88.72MB 88.72MB (flat, cum) 28.32%
224 . . }
225 . . }
226 . .
227 . . // growSlice grows b by n, preserving the original content of b.
228 . . // If the allocation fails, it panics with ErrTooLarge.
229 . . func growSlice(b []byte, n int) []byte {
230 . . defer func() {
231 . . if recover() != nil {
232 . . panic(ErrTooLarge)
233 . . }
234 . . }()
235 . . // TODO(http://golang.org/issue/51462): We should rely on the append-make
236 . . // pattern so that the compiler can call runtime.growslice. For example:
237 . . // return append(b, make([]byte, n)...)
238 . . // This avoids unnecessary zero-ing of the first len(b) bytes of the
239 . . // allocated slice, but this pattern causes b to escape onto the heap.
240 . . //
241 . . // Instead use the append-make pattern with a nil slice to ensure that
242 . . // we allocate buffers rounded up to the closest size class.
243 . . c := len(b) + n // ensure enough space for n elements
244 . . if c < 2*cap(b) {
245 . . // The growth rate has historically always been 2x. In the future,
246 . . // we could rely purely on append to determine the growth rate.
247 . . c = 2 * cap(b)
248 . . }
249 88.72MB 88.72MB b2 := append([]byte(nil), make([]byte, c)...)
250 . . i := copy(b2, b)
251 . . return b2[:i]
252 . . }
253 . .
254 . . // WriteTo writes data to w until the buffer is drained or an error occurs.
255 . . // The return value n is the number of bytes written; it always fits into an
256 . . // int, but it is int64 to match the [io.WriterTo] interface. Any error
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.spanStart
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/span.go
Total: 2.50MB 2.50MB (flat, cum) 0.8%
527 . . return builder.String()
528 . . }
529 . .
530 . . // setMeta sets a string tag. This method is not safe for concurrent use.
531 . . func (s *Span) setMeta(key, v string) {
532 . . if s.meta == nil {
533 2.50MB 2.50MB s.meta = make(map[string]string, 1)
534 . . }
535 . . delete(s.metrics, key)
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.spanStart
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/span.go
Total: 20.51MB 20.51MB (flat, cum) 6.54%
546 20.51MB 20.51MB s.meta[key] = v
547 . . }
548 . . }
549 . .
550 . . func (s *Span) setMetaStruct(key string, v any) {
551 . . if s.metaStruct == nil {
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*Span).setMetric
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/span.go
Total: 18.50MB 18.50MB (flat, cum) 5.90%
580 . . }
581 . . }
582 . .
583 . . // setMetric sets a numeric tag, in our case called a metric. This method
584 . . // is not safe for concurrent use.
585 . . func (s *Span) setMetric(key string, v float64) {
586 . . if s.metrics == nil {
587 5MB 5MB s.metrics = make(map[string]float64, 1)
588 . . }
589 . . delete(s.meta, key)
590 . . switch key {
591 . . case ext.ManualKeep:
592 . . if v == float64(samplernames.AppSec) {
593 . . s.setSamplingPriorityLocked(ext.PriorityUserKeep, samplernames.AppSec)
594 . . }
595 . . case "_sampling_priority_v1shim":
596 . . // We have this for backward compatibility with the v1 shim.
597 . . s.setSamplingPriorityLocked(int(v), samplernames.Manual)
598 . . default:
599 13.50MB 13.50MB s.metrics[key] = v
600 . . }
601 . . }
602 . .
603 . . // AddLink appends the given link to the span's span links.
604 . . func (s *Span) AddLink(link SpanLink) {
605 . . if s == nil {
606 . . return
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.newSpanContext
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/spancontext.go
Total: 13.50MB 15MB (flat, cum) 4.79%
153 . . // newSpanContext creates a new SpanContext to serve as context for the given
154 . . // span. If the provided parent is not nil, the context will inherit the trace,
155 . . // baggage and other values from it. This method also pushes the span into the
156 . . // new context's trace and as a result, it should not be called multiple times
157 . . // for the same span.
158 . . func newSpanContext(span *Span, parent *SpanContext) *SpanContext {
159 13.50MB 13.50MB context := &SpanContext{
160 . . spanID: span.spanID,
161 . . span: span,
162 . . }
163 . .
164 . . context.traceID.SetLower(span.traceID)
165 . . if parent != nil {
166 . . if !parent.baggageOnly {
167 . . context.traceID.SetUpper(parent.traceID.Upper())
168 . . context.trace = parent.trace
169 . . context.origin = parent.origin
170 . . context.errors.Store(parent.errors.Load())
171 . . }
172 . . parent.ForeachBaggageItem(func(k, v string) bool {
173 . . context.setBaggageItem(k, v)
174 . . return true
175 . . })
176 . . } else if sharedinternal.BoolEnv("DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED", true) {
177 . . // add 128 bit trace id, if enabled, formatted as big-endian:
178 . . // <32-bit unix seconds> <32 bits of zero> <64 random bits>
179 . . id128 := time.Duration(span.start) / time.Second
180 . . // casting from int64 -> uint32 should be safe since the start time won't be
181 . . // negative, and the seconds should fit within 32-bits for the foreseeable future.
182 . . // (We only want 32 bits of time, then the rest is zero)
183 . . tUp := uint64(uint32(id128)) << 32 // We need the time at the upper 32 bits of the uint
184 . . context.traceID.SetUpper(tUp)
185 . . }
186 . . if context.trace == nil {
187 . . context.trace = newTrace()
188 . . }
189 . . if context.trace.root == nil {
190 . . // first span in the trace can safely be assumed to be the root
191 . . context.trace.root = span
192 . . }
193 . . // put span in context's trace
194 . 1.50MB context.trace.push(span)
195 . . // setting context.updated to false here is necessary to distinguish
196 . . // between initializing properties of the span (priority)
197 . . // and updating them after extracting context through propagators
198 . . context.updated = false
199 . . return context
200 . . }
201 . .
202 . . // SpanID implements ddtrace.SpanContext.
203 . . func (c *SpanContext) SpanID() uint64 {
204 . . if c == nil {
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*SpanContext).setSamplingPriority
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/spancontext.go
Total: 0 11MB (flat, cum) 3.51%
262 . . }
263 . . }
264 . . }
265 . .
266 . . // sets the sampling priority and decision maker (based on `sampler`).
267 . . func (c *SpanContext) setSamplingPriority(p int, sampler samplernames.SamplerName) {
268 . . if c.trace == nil {
269 . . c.trace = newTrace()
270 . . }
271 . 11MB if c.trace.setSamplingPriority(p, sampler) {
272 . . // the trace's sampling priority or sampler was updated: mark this as updated
273 . . c.updated = true
274 . . }
275 . . }
276 . .
277 . . // forceSetSamplingPriority sets (and forces if the trace is locked) the sampling priority and decision maker (based on `sampler`).
278 . . func (c *SpanContext) forceSetSamplingPriority(p int, sampler samplernames.SamplerName) {
279 . . if c.trace == nil {
280 . . c.trace = newTrace()
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.newSpanContext
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/spancontext.go
Total: 6MB 6MB (flat, cum) 1.91%
381 . . )
382 . .
383 . . // newTrace creates a new trace using the given callback which will be called
384 . . // upon completion of the trace.
385 . . func newTrace() *trace {
386 6MB 6MB return &trace{spans: make([]*Span, 0, traceStartSize)}
387 . . }
388 . .
389 . . func (t *trace) samplingPriorityLocked() (p int, ok bool) {
390 . . if t.priority == nil {
391 . . return 0, false
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*trace).setSamplingPriorityLockedWithForce
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/spancontext.go
Total: 1MB 1MB (flat, cum) 0.32%
435 . . }
436 . . t.tags[key] = value
437 . . }
438 . .
439 . . func samplerToDM(sampler samplernames.SamplerName) string {
440 512.01kB 512.01kB return "-" + strconv.Itoa(int(sampler))
441 . . }
442 . .
443 . . // setSamplingPriority sets the sampling priority and the decision maker
444 . . // and returns true if it was modified.
445 . . //
446 . . // The force parameter is used to bypass the locked sampling decision check
447 . . // when setting the sampling priority. This is used to apply a manual keep or drop decision.
448 . . func (t *trace) setSamplingPriorityLockedWithForce(p int, sampler samplernames.SamplerName, force bool) bool {
449 . . if t.locked && !force {
450 . . return false
451 . . }
452 . .
453 . . updatedPriority := t.priority == nil || *t.priority != float64(p)
454 . .
455 . . if t.priority == nil {
456 512.01kB 512.01kB t.priority = new(float64)
457 . . }
458 . . *t.priority = float64(p)
459 . . curDM, existed := t.propagatingTags[keyDecisionMaker]
460 . . if p > 0 && sampler != samplernames.Unknown {
461 . . // We have a positive priority and the sampling mechanism isn't set.
462 . . // Send nothing when sampler is `Unknown` for RFC compliance.
463 . . // If a global sampling rate is set, it was always applied first. And this call can be
464 . . // triggered again by applying a rule sampler. The sampling priority will be the same, but
465 . . // the decision maker will be different. So we compare the decision makers as well.
466 . . // Note that once global rate sampling is deprecated, we no longer need to compare
467 . . // the DMs. Sampling priority is sufficient to distinguish a change in DM.
468 . . dm := samplerToDM(sampler)
469 . . updatedDM := !existed || dm != curDM
470 . . if updatedDM {
471 . . t.setPropagatingTagLocked(keyDecisionMaker, dm)
472 . . return true
473 . . }
474 . . }
475 . . if p <= 0 && existed {
476 . . delete(t.propagatingTags, keyDecisionMaker)
477 . . }
478 . .
479 . . return updatedPriority
480 . . }
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*trace).setSamplingPriority
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/spancontext.go
Total: 0 11MB (flat, cum) 3.51%
482 . . func (t *trace) setSamplingPriorityLocked(p int, sampler samplernames.SamplerName) bool {
483 . 11MB return t.setSamplingPriorityLockedWithForce(p, sampler, false)
484 . . }
485 . .
486 . . func (t *trace) isLocked() bool {
487 . . t.mu.RLock()
488 . . defer t.mu.RUnlock()
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*trace).push
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/spancontext.go
Total: 1.50MB 1.50MB (flat, cum) 0.48%
495 . . t.locked = locked
496 . . }
497 . .
498 . . // push pushes a new span into the trace. If the buffer is full, it returns
499 . . // a errBufferFull error.
500 . . func (t *trace) push(sp *Span) {
501 . . t.mu.Lock()
502 . . defer t.mu.Unlock()
503 . . if t.full {
504 . . return
505 . . }
506 . . tr := getGlobalTracer()
507 . . if len(t.spans) >= traceMaxSize {
508 . . // capacity is reached, we will not be able to complete this trace.
509 . . t.full = true
510 . . t.spans = nil // allow our spans to be collected by GC.
511 . . log.Error("trace buffer full (%d spans), dropping trace", traceMaxSize)
512 . . if tr != nil {
513 . . tracerstats.Signal(tracerstats.TracesDropped, 1)
514 . . }
515 . . return
516 . . }
517 . . if v, ok := sp.metrics[keySamplingPriority]; ok {
518 . . t.setSamplingPriorityLocked(int(v), samplernames.Unknown)
519 . . }
520 1.50MB 1.50MB t.spans = append(t.spans, sp)
521 . . if tr != nil {
522 . . tracerstats.Signal(tracerstats.SpanStarted, 1)
523 . . }
524 . . }
525 . .
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*trace).setTraceTags
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/spancontext.go
Total: 0 2.50MB (flat, cum) 0.8%
527 . . // t must already be locked.
528 . . func (t *trace) setTraceTags(s *Span) {
529 . . for k, v := range t.tags {
530 . . s.setMeta(k, v)
531 . . }
532 . . for k, v := range t.propagatingTags {
533 . . s.setMeta(k, v)
534 . . }
535 . 2.50MB for k, v := range sharedinternal.GetTracerGitMetadataTags() {
536 . . s.setMeta(k, v)
537 . . }
538 . . if s.context != nil && s.context.traceID.HasUpper() {
539 . . s.setMeta(keyTraceID128, s.context.traceID.UpperHex())
540 . . }
541 . . }
542 . .
543 . . // finishedOne acknowledges that another span in the trace has finished, and checks
544 . . // if the trace is complete, in which case it calls the onFinish function. It uses
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*trace).finishedOne
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/spancontext.go
Total: 2MB 5MB (flat, cum) 1.60%
545 . . // the given priority, if non-nil, to mark the root span. This also will trigger a partial flush
546 . . // if enabled and the total number of finished spans is greater than or equal to the partial flush limit.
547 . . // The provided span must be locked.
548 . . func (t *trace) finishedOne(s *Span) {
549 . . t.mu.Lock()
550 . . defer t.mu.Unlock()
551 . . s.finished = true
552 . . if t.full {
553 . . // capacity has been reached, the buffer is no longer tracking
554 . . // all the spans in the trace, so the below conditions will not
555 . . // be accurate and would trigger a pre-mature flush, exposing us
556 . . // to a race condition where spans can be modified while flushing.
557 . . //
558 . . // TODO(partialFlush): should we do a partial flush in this scenario?
559 . . return
560 . . }
561 . . t.finished++
562 . . tr := getGlobalTracer()
563 . . if tr == nil {
564 . . return
565 . . }
566 . . tc := tr.TracerConf()
567 . . setPeerService(s, tc)
568 . .
569 . . // attach the _dd.base_service tag only when the globally configured service name is different from the
570 . . // span service name.
571 . . if s.service != "" && !strings.EqualFold(s.service, tc.ServiceTag) {
572 . . s.meta[keyBaseService] = tc.ServiceTag
573 . . }
574 . . if s == t.root && t.priority != nil {
575 . . // after the root has finished we lock down the priority;
576 . . // we won't be able to make changes to a span after finishing
577 . . // without causing a race condition.
578 . . t.root.setMetric(keySamplingPriority, *t.priority)
579 . . t.locked = true
580 . . }
581 . . if len(t.spans) > 0 && s == t.spans[0] {
582 . . // first span in chunk finished, lock down the tags
583 . . //
584 . . // TODO(barbayar): make sure this doesn't happen in vain when switching to
585 . . // the new wire format. We won't need to set the tags on the first span
586 . . // in the chunk there.
587 . 3MB t.setTraceTags(s)
588 . . }
589 . .
590 . . // This is here to support the mocktracer. It would be nice to be able to not do this.
591 . . // We need to track when any single span is finished.
592 . . if mtr, ok := tr.(interface{ FinishSpan(*Span) }); ok {
593 . . mtr.FinishSpan(s)
594 . . }
595 . .
596 . . if len(t.spans) == t.finished { // perform a full flush of all spans
597 . . if tr, ok := tr.(*tracer); ok {
598 2MB 2MB t.finishChunk(tr, &chunk{
599 . . spans: t.spans,
600 . . willSend: decisionKeep == samplingDecision(atomic.LoadUint32((*uint32)(&t.samplingDecision))),
601 . . })
602 . . }
603 . . t.spans = nil
604 . . return
605 . . }
606 . .
607 . . doPartialFlush := tc.PartialFlush && t.finished >= tc.PartialFlushMinSpans
608 . . if !doPartialFlush {
609 . . return // The trace hasn't completed and partial flushing will not occur
610 . . }
611 . . log.Debug("Partial flush triggered with %d finished spans", t.finished)
612 . . telemetry.Count(telemetry.NamespaceTracers, "trace_partial_flush.count", []string{"reason:large_trace"}).Submit(1)
613 . . finishedSpans := make([]*Span, 0, t.finished)
614 . . leftoverSpans := make([]*Span, 0, len(t.spans)-t.finished)
615 . . for _, s2 := range t.spans {
616 . . if s2.finished {
617 . . finishedSpans = append(finishedSpans, s2)
618 . . } else {
619 . . leftoverSpans = append(leftoverSpans, s2)
620 . . }
621 . . }
622 . . telemetry.Distribution(telemetry.NamespaceTracers, "trace_partial_flush.spans_closed", nil).Submit(float64(len(finishedSpans)))
623 . . telemetry.Distribution(telemetry.NamespaceTracers, "trace_partial_flush.spans_remaining", nil).Submit(float64(len(leftoverSpans)))
624 . . finishedSpans[0].setMetric(keySamplingPriority, *t.priority)
625 . . if s != t.spans[0] {
626 . . // Make sure the first span in the chunk has the trace-level tags
627 . . t.setTraceTags(finishedSpans[0])
628 . . }
629 . . if tr, ok := tr.(*tracer); ok {
630 . . t.finishChunk(tr, &chunk{
631 . . spans: finishedSpans,
632 . . willSend: decisionKeep == samplingDecision(atomic.LoadUint32((*uint32)(&t.samplingDecision))),
633 . . })
634 . . }
635 . . t.spans = leftoverSpans
636 . . }
637 . .
638 . . func (t *trace) finishChunk(tr *tracer, ch *chunk) {
639 . . tr.submitChunk(ch)
640 . . t.finished = 0 // important, because a buffer can be used for several flushes
641 . . }
642 . .
643 . . // setPeerService sets the peer.service, _dd.peer.service.source, and _dd.peer.service.remapped_from
644 . . // tags as applicable for the given span.
645 . . func setPeerService(s *Span, tc TracerConf) {
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.spanStart
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/tracer.go
Total: 18.50MB 52.01MB (flat, cum) 16.60%
621 . . }
622 . . default:
623 . . }
624 . . }
625 . .
626 . . func spanStart(operationName string, options ...StartSpanOption) *Span {
627 6.50MB 6.50MB var opts StartSpanConfig
628 . . for _, fn := range options {
629 . . if fn == nil {
630 . . continue
631 . . }
632 . . fn(&opts)
633 . . }
634 . . var startTime int64
635 . . if opts.StartTime.IsZero() {
636 . . startTime = now()
637 . . } else {
638 . . startTime = opts.StartTime.UnixNano()
639 . . }
640 . . var context *SpanContext
641 . . // The default pprof context is taken from the start options and is
642 . . // not nil when using StartSpanFromContext()
643 . . pprofContext := opts.Context
644 . . if opts.Parent != nil {
645 . . context = opts.Parent
646 . . if pprofContext == nil && context.span != nil {
647 . . // Inherit the context.Context from parent span if it was propagated
648 . . // using ChildOf() rather than StartSpanFromContext(), see
649 . . // applyPPROFLabels() below.
650 . . context.span.mu.RLock()
651 . . pprofContext = context.span.pprofCtxActive
652 . . context.span.mu.RUnlock()
653 . . }
654 . . }
655 . . if pprofContext == nil {
656 . . // For root span's without context, there is no pprofContext, but we need
657 . . // one to avoid a panic() in pprof.WithLabels(). Using context.Background()
658 . . // is not ideal here, as it will cause us to remove all labels from the
659 . . // goroutine when the span finishes. However, the alternatives of not
660 . . // applying labels for such spans or to leave the endpoint/hotspot labels
661 . . // on the goroutine after it finishes are even less appealing. We'll have
662 . . // to properly document this for users.
663 . . pprofContext = gocontext.Background()
664 . . }
665 . . id := opts.SpanID
666 . . if id == 0 {
667 . . id = generateSpanID(startTime)
668 . . }
669 . . // span defaults
670 12MB 12MB span := &Span{
671 . . name: operationName,
672 . . service: "",
673 . . resource: operationName,
674 . . spanID: id,
675 . . traceID: id,
676 . . start: startTime,
677 . . integration: "manual",
678 . . }
679 . .
680 . . span.spanLinks = append(span.spanLinks, opts.SpanLinks...)
681 . .
682 . . if context != nil && !context.baggageOnly {
683 . . // this is a child span
684 . . span.traceID = context.traceID.Lower()
685 . . span.parentID = context.spanID
686 . . if p, ok := context.SamplingPriority(); ok {
687 . 12MB span.setMetric(keySamplingPriority, float64(p))
688 . . }
689 . . if context.span != nil {
690 . . // local parent, inherit service
691 . . context.span.mu.RLock()
692 . . span.service = context.span.service
693 . . context.span.mu.RUnlock()
694 . . } else {
695 . . // remote parent
696 . . if context.origin != "" {
697 . . // mark origin
698 . . span.setMeta(keyOrigin, context.origin)
699 . . }
700 . . }
701 . .
702 . . if context.reparentID != "" {
703 . . span.setMeta(keyReparentID, context.reparentID)
704 . . }
705 . .
706 . . }
707 . 21MB span.context = newSpanContext(span, context)
708 . . if pprofContext != nil {
709 . . setLLMObsPropagatingTags(pprofContext, span.context)
710 . . }
711 . . span.setMeta("language", "go")
712 . . // add tags from options
713 . . for k, v := range opts.Tags {
714 . 512.28kB span.SetTag(k, v)
715 . . }
716 . . isRootSpan := context == nil || context.span == nil
717 . . if isRootSpan {
718 . . traceprof.SetProfilerRootTags(span)
719 . . }
720 . . if isRootSpan || context.span.service != span.service {
721 . . // The span is the local root span.
722 . . span.setMetric(keyTopLevel, 1)
723 . . // all top level spans are measured. So the measured tag is redundant.
724 . . delete(span.metrics, keyMeasured)
725 . . }
726 . . pprofContext, span.taskEnd = startExecutionTracerTask(pprofContext, span)
727 . . span.pprofCtxRestore = pprofContext
728 . . return span
729 . . }
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*tracer).StartSpan
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/tracer.go
Total: 0 81.52MB (flat, cum) 26.02%
731 . . // StartSpan creates, starts, and returns a new Span with the given `operationName`.
732 . . func (t *tracer) StartSpan(operationName string, options ...StartSpanOption) *Span {
733 . . if !t.config.enabled.current {
734 . . return nil
735 . . }
736 . 81.52MB span := spanStart(operationName, options...)
737 . . if span.service == "" {
738 . . span.service = t.config.serviceName
739 . . }
740 . . span.noDebugStack = t.config.noDebugStack
741 . . if t.config.hostname != "" {
742 . . span.setMeta(keyHostname, t.config.hostname)
743 . . }
744 . . span.supportsEvents = t.config.agent.spanEventsAvailable
745 . .
746 . . // add global tags
747 . . for k, v := range t.config.globalTags.get() {
748 . . span.SetTag(k, v)
749 . . }
750 . . if t.config.serviceMappings != nil {
751 . . if newSvc, ok := t.config.serviceMappings[span.service]; ok {
752 . . span.service = newSvc
753 . . }
754 . . }
755 . . if t.config.version != "" {
756 . . if t.config.universalVersion || (!t.config.universalVersion && span.service == t.config.serviceName) {
757 . . span.setMeta(ext.Version, t.config.version)
github.com/lesismal/nbio.(*Engine).Start
/Users/julian/go/pkg/mod/github.com/lesismal/nbio@v1.6.7/engine_unix.go
Total: 16MB 16MB (flat, cum) 5.11%
22 . . )
23 . .
24 . . // Start inits and starts pollers.
25 . . //
26 . . //go:norace
27 . . func (g *Engine) Start() error {
28 16MB 16MB g.connsUnix = make([]*Conn, MaxOpenFiles)
29 . .
30 . . // Create pollers and listeners.
31 . . g.pollers = make([]*poller, g.NPoller)
32 . . g.listeners = make([]*poller, len(g.Addrs))[0:0]
33 . . udpListeners := make([]*net.UDPConn, len(g.Addrs))[0:0]
34 . .
35 . . switch g.Network {
36 . . case NETWORK_UNIX, NETWORK_TCP, NETWORK_TCP4, NETWORK_TCP6:
37 . . for i := range g.Addrs {
38 . . ln, err := newPoller(g, true, i)
39 . . if err != nil {
40 . . for j := 0; j < i; j++ {
41 . . g.listeners[j].stop()
42 . . }
43 . . return err
44 . . }
45 . . g.Addrs[i] = ln.listener.Addr().String()
46 . . g.listeners = append(g.listeners, ln)
47 . . }
48 . . case NETWORK_UDP, NETWORK_UDP4, NETWORK_UDP6:
49 . . for i, addrStr := range g.Addrs {
50 . . addr, err := net.ResolveUDPAddr(g.Network, addrStr)
51 . . if err != nil {
52 . . for j := 0; j < i; j++ {
53 . . _ = udpListeners[j].Close()
54 . . }
github.com/lesismal/nbio.(*Engine).Start
/Users/julian/go/pkg/mod/github.com/lesismal/nbio@v1.6.7/engine_unix.go
Total: 544.67kB 544.67kB (flat, cum) 0.17%
65 . . udpListeners = append(udpListeners, ln)
66 . . }
67 . . }
68 . .
69 . . // Create IO pollers.
70 . . for i := 0; i < g.NPoller; i++ {
71 . . p, err := newPoller(g, false, i)
72 . . if err != nil {
73 . . for j := 0; j < len(g.listeners); j++ {
74 . . g.listeners[j].stop()
75 . . }
76 . .
77 . . for j := 0; j < i; j++ {
78 . . g.pollers[j].stop()
79 . . }
80 . . return err
81 . . }
82 . . g.pollers[i] = p
83 . . }
84 . .
85 . . // Start IO pollers.
86 . . for i := 0; i < g.NPoller; i++ {
87 544.67kB 544.67kB g.pollers[i].ReadBuffer = make([]byte, g.ReadBufferSize)
88 . . g.Add(1)
89 . . go g.pollers[i].start()
90 . . }
91 . .
92 . . // Start TCP/Unix listener pollers.
93 . . for _, l := range g.listeners {
94 . . g.Add(1)
95 . . go l.start()
96 . . }
97 . .
98 . . // Start UDP listener pollers.
99 . . for _, ln := range udpListeners {
100 . . _, err := g.AddConn(ln)
101 . . if err != nil {
102 . . for j := 0; j < len(g.listeners); j++ {
103 . . g.listeners[j].stop()
104 . . }
105 . .
106 . . for j := 0; j < len(g.pollers); j++ {
107 . . g.pollers[j].stop()
108 . . }
109 . .
110 . . for j := 0; j < len(udpListeners); j++ {
111 . . _ = udpListeners[j].Close()
112 . . }
113 . .
114 . . return err
115 . . }
116 . . }
117 . .
118 . . g.Timer.Start()
119 . . g.isOneshot = (g.EpollMod == EPOLLET && g.EPOLLONESHOT == EPOLLONESHOT)
120 . .
121 . . if g.AsyncReadInPoller {
122 . . if g.IOExecute == nil {
123 . . g.ioTaskPool = taskpool.NewIO(0, 0, 0)
124 . . g.IOExecute = g.ioTaskPool.Go
125 . . }
126 . . }
127 . .
128 . . if len(g.Addrs) == 0 {
encoding/json.(*Decoder).Decode
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/encoding/json/stream.go
Total: 0 18.13MB (flat, cum) 5.79%
44 . . // Decode reads the next JSON-encoded value from its
45 . . // input and stores it in the value pointed to by v.
46 . . //
47 . . // See the documentation for [Unmarshal] for details about
48 . . // the conversion of JSON into a Go value.
49 . . func (dec *Decoder) Decode(v any) error {
50 . . if dec.err != nil {
51 . . return dec.err
52 . . }
53 . .
54 . . if err := dec.tokenPrepareForDecode(); err != nil {
55 . . return err
56 . . }
57 . .
58 . . if !dec.tokenValueAllowed() {
59 . . return &SyntaxError{msg: "not at beginning of value", Offset: dec.InputOffset()}
60 . . }
61 . .
62 . . // Read whole value into buffer.
63 . 11.61MB n, err := dec.readValue()
64 . . if err != nil {
65 . . return err
66 . . }
67 . . dec.d.init(dec.buf[dec.scanp : dec.scanp+n])
68 . . dec.scanp += n
69 . .
70 . . // Don't save err from unmarshal into dec.err:
71 . . // the connection is still usable since we read a complete JSON
72 . . // object from it before the error happened.
73 . 6.52MB err = dec.d.unmarshal(v)
74 . .
75 . . // fixup token streaming state
76 . . dec.tokenValueEnd()
77 . .
78 . . return err
79 . . }
80 . .
81 . . // Buffered returns a reader of the data remaining in the Decoder's
82 . . // buffer. The reader is valid until the next call to [Decoder.Decode].
83 . . func (dec *Decoder) Buffered() io.Reader {
encoding/json.(*Decoder).readValue
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/encoding/json/stream.go
Total: 0 11.61MB (flat, cum) 3.70%
84 . . return bytes.NewReader(dec.buf[dec.scanp:])
85 . . }
86 . .
87 . . // readValue reads a JSON value into dec.buf.
88 . . // It returns the length of the encoding.
89 . . func (dec *Decoder) readValue() (int, error) {
90 . . dec.scan.reset()
91 . .
92 . . scanp := dec.scanp
93 . . var err error
94 . . Input:
95 . . // help the compiler see that scanp is never negative, so it can remove
96 . . // some bounds checks below.
97 . . for scanp >= 0 {
98 . .
99 . . // Look in the buffer for a new value.
100 . . for ; scanp < len(dec.buf); scanp++ {
101 . . c := dec.buf[scanp]
102 . . dec.scan.bytes++
103 . . switch dec.scan.step(&dec.scan, c) {
104 . . case scanEnd:
105 . . // scanEnd is delayed one byte so we decrement
106 . . // the scanner bytes count by 1 to ensure that
107 . . // this value is correct in the next call of Decode.
108 . . dec.scan.bytes--
109 . . break Input
110 . . case scanEndObject, scanEndArray:
111 . . // scanEnd is delayed one byte.
112 . . // We might block trying to get that byte from src,
113 . . // so instead invent a space byte.
114 . . if stateEndValue(&dec.scan, ' ') == scanEnd {
115 . . scanp++
116 . . break Input
117 . . }
118 . . case scanError:
119 . . dec.err = dec.scan.err
120 . . return 0, dec.scan.err
121 . . }
122 . . }
123 . .
124 . . // Did the last read have an error?
125 . . // Delayed until now to allow buffer scan.
126 . . if err != nil {
127 . . if err == io.EOF {
128 . . if dec.scan.step(&dec.scan, ' ') == scanEnd {
129 . . break Input
130 . . }
131 . . if nonSpace(dec.buf) {
132 . . err = io.ErrUnexpectedEOF
133 . . }
134 . . }
135 . . dec.err = err
136 . . return 0, err
137 . . }
138 . .
139 . . n := scanp - dec.scanp
140 . 11.61MB err = dec.refill()
141 . . scanp = dec.scanp + n
142 . . }
143 . . return scanp - dec.scanp, nil
144 . . }
encoding/json.(*Decoder).refill
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/encoding/json/stream.go
Total: 11.61MB 11.61MB (flat, cum) 3.70%
145 . .
146 . . func (dec *Decoder) refill() error {
147 . . // Make room to read more into the buffer.
148 . . // First slide down data already consumed.
149 . . if dec.scanp > 0 {
150 . . dec.scanned += int64(dec.scanp)
151 . . n := copy(dec.buf, dec.buf[dec.scanp:])
152 . . dec.buf = dec.buf[:n]
153 . . dec.scanp = 0
154 . . }
155 . .
156 . . // Grow buffer if not large enough.
157 . . const minRead = 512
158 . . if cap(dec.buf)-len(dec.buf) < minRead {
159 11.61MB 11.61MB newBuf := make([]byte, len(dec.buf), 2*cap(dec.buf)+minRead)
160 . . copy(newBuf, dec.buf)
161 . . dec.buf = newBuf
162 . . }
163 . .
164 . . // Read. Delay error for next iteration (after scan).
165 . . n, err := dec.r.Read(dec.buf[len(dec.buf):cap(dec.buf)])
166 . . dec.buf = dec.buf[0 : len(dec.buf)+n]
167 . .
168 . . return err
169 . . }
encoding/json.(*Encoder).Encode
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/encoding/json/stream.go
Total: 0 512.04kB (flat, cum) 0.16%
197 . . // with insignificant space characters elided,
198 . . // followed by a newline character.
199 . . //
200 . . // See the documentation for [Marshal] for details about the
201 . . // conversion of Go values to JSON.
202 . . func (enc *Encoder) Encode(v any) error {
203 . . if enc.err != nil {
204 . . return enc.err
205 . . }
206 . .
207 . . e := newEncodeState()
208 . . defer encodeStatePool.Put(e)
209 . .
210 . . err := e.marshal(v, encOpts{escapeHTML: enc.escapeHTML})
211 . . if err != nil {
212 . . return err
213 . . }
214 . .
215 . . // Terminate each value with a newline.
216 . . // This makes the output look a little nicer
217 . . // when debugging, and some kind of space
218 . . // is required if the encoded value was a number,
219 . . // so that the reader knows there aren't more
220 . . // digits coming.
221 . . e.WriteByte('\n')
222 . .
223 . . b := e.Bytes()
224 . . if enc.indentPrefix != "" || enc.indentValue != "" {
225 . . enc.indentBuf, err = appendIndent(enc.indentBuf[:0], b, enc.indentPrefix, enc.indentValue)
226 . . if err != nil {
227 . . return err
228 . . }
229 . . b = enc.indentBuf
230 . . }
231 . 512.04kB if _, err = enc.w.Write(b); err != nil {
232 . . enc.err = err
233 . . }
234 . . return err
235 . . }
236 . .
237 . . // SetIndent instructs the encoder to format each subsequent encoded
238 . . // value as if indented by the package-level function Indent(dst, src, prefix, indent).
239 . . // Calling SetIndent("", "") disables indentation.
encoding/json.(*RawMessage).UnmarshalJSON
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/encoding/json/stream.go
Total: 1MB 1MB (flat, cum) 0.32%
265 . . }
266 . . return m, nil
267 . . }
268 . .
269 . . // UnmarshalJSON sets *m to a copy of data.
270 . . func (m *RawMessage) UnmarshalJSON(data []byte) error {
271 . . if m == nil {
272 . . return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
273 . . }
274 1MB 1MB *m = append((*m)[0:0], data...)
275 . . return nil
276 . . }
277 . .
278 . . var _ Marshaler = (*RawMessage)(nil)
279 . . var _ Unmarshaler = (*RawMessage)(nil)
280 . .
runtime/pprof.WithLabels
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/runtime/pprof/label.go
Total: 2.50MB 8.50MB (flat, cum) 2.71%
55 . . return "{" + strings.Join(keyVals, ", ") + "}"
56 . . }
57 . .
58 . . // WithLabels returns a new [context.Context] with the given labels added.
59 . . // A label overwrites a prior label with the same key.
60 . . func WithLabels(ctx context.Context, labels LabelSet) context.Context {
61 . . parentLabels := labelValue(ctx)
62 2.50MB 8.50MB return context.WithValue(ctx, labelContextKey{}, &labelMap{mergeLabelSets(parentLabels.LabelSet, labels)})
63 . . }
runtime/pprof.mergeLabelSets
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/runtime/pprof/label.go
Total: 2MB 2MB (flat, cum) 0.64%
64 . .
65 . . func mergeLabelSets(left, right LabelSet) LabelSet {
66 . . if len(left.list) == 0 {
67 . . return right
68 . . } else if len(right.list) == 0 {
69 . . return left
70 . . }
71 . .
72 . . l, r := 0, 0
73 2MB 2MB result := make([]label, 0, len(right.list))
74 . . for l < len(left.list) && r < len(right.list) {
75 . . switch strings.Compare(left.list[l].key, right.list[r].key) {
76 . . case -1: // left key < right key
77 . . result = append(result, left.list[l])
78 . . l++
79 . . case 1: // right key < left key
80 . . result = append(result, right.list[r])
81 . . r++
82 . . case 0: // keys are equal, right value overwrites left value
83 . . result = append(result, right.list[r])
84 . . l++
85 . . r++
86 . . }
87 . . }
88 . .
89 . . // Append the remaining elements
90 . . result = append(result, left.list[l:]...)
91 . . result = append(result, right.list[r:]...)
92 . .
93 . . return LabelSet{list: result}
94 . . }
95 . .
96 . . // Labels takes an even number of strings representing key-value pairs
97 . . // and makes a [LabelSet] containing them.
runtime/pprof.Labels
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/runtime/pprof/label.go
Total: 8MB 8MB (flat, cum) 2.55%
98 . . // A label overwrites a prior label with the same key.
99 . . // Currently only the CPU and goroutine profiles utilize any labels
100 . . // information.
101 . . // See https://golang.org/issue/23458 for details.
102 . . func Labels(args ...string) LabelSet {
103 . . if len(args)%2 != 0 {
104 . . panic("uneven number of arguments to pprof.Labels")
105 . . }
106 8MB 8MB list := make([]label, 0, len(args)/2)
107 . . sortedNoDupes := true
108 . . for i := 0; i+1 < len(args); i += 2 {
109 . . list = append(list, label{key: args[i], value: args[i+1]})
110 . . sortedNoDupes = sortedNoDupes && (i < 2 || args[i] > args[i-2])
111 . . }
112 . . if !sortedNoDupes {
113 . . // slow path: keys are unsorted, contain duplicates, or both
114 . . slices.SortStableFunc(list, func(a, b label) int {
115 . . return strings.Compare(a.key, b.key)
116 . . })
117 . . deduped := make([]label, 0, len(list))
118 . . for i, lbl := range list {
119 . . if i == 0 || lbl.key != list[i-1].key {
120 . . deduped = append(deduped, lbl)
121 . . } else {
122 . . deduped[len(deduped)-1] = lbl
123 . . }
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*trace).setSamplingPriorityLockedWithForce
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/propagating_tags.go
Total: 10MB 10MB (flat, cum) 3.19%
52 . . }
53 . .
54 . . // setPropagatingTagLocked sets the key/value pair as a trace propagating tag.
55 . . // Not safe for concurrent use, setPropagatingTag should be used instead in that case.
56 . . func (t *trace) setPropagatingTagLocked(key, value string) {
57 . . if t.propagatingTags == nil {
58 512.02kB 512.02kB t.propagatingTags = make(map[string]string, 1)
59 . . }
60 9.50MB 9.50MB t.propagatingTags[key] = value
61 . . }
62 . .
63 . . // unsetPropagatingTag deletes the key/value pair from the trace's propagated tags.
64 . . func (t *trace) unsetPropagatingTag(key string) {
65 . . t.mu.Lock()
github.com/braze-inc/dust/redis.BulkResolveMiteStringsToInTransitMites
/Users/julian/code/dust/redis/mite_bulk_resolver.go
Total: 7.51MB 14.51MB (flat, cum) 4.63%
31 . . mites []*mite.Mite,
32 . . ) (userMitesMap mite.EntityMitesMap, err error) {
33 . 1.50MB span, ctx := tracer.StartSpanFromContext(ctx, "redis.bulk_resolve_mites")
context.WithValue
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/context/context.go
Total: 6.50MB 6.50MB (flat, cum) 2.07%
713 . . // packages using context. Users of WithValue should define their own
714 . . // types for keys. To avoid allocating when assigning to an
715 . . // interface{}, context keys often have concrete type
716 . . // struct{}. Alternatively, exported context key variables' static
717 . . // type should be a pointer or interface.
718 . . func WithValue(parent Context, key, val any) Context {
719 . . if parent == nil {
720 . . panic("cannot create context from nil parent")
721 . . }
722 . . if key == nil {
723 . . panic("nil key")
724 . . }
725 . . if !reflectlite.TypeOf(key).Comparable() {
726 . . panic("key is not comparable")
727 . . }
728 6.50MB 6.50MB return &valueCtx{parent, key, val}
729 . . }
730 . .
731 . . // A valueCtx carries a key-value pair. It implements Value for that key and
732 . . // delegates all other calls to the embedded Context.
733 . . type valueCtx struct {
reflect.growslice
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/runtime/slice.go
Total: 5.02MB 5.02MB (flat, cum) 1.60%
327 . . //
328 . . // Do not remove or change the type signature.
329 . . // See go.dev/issue/67401.
330 . . //
331 . . //go:linkname reflect_growslice reflect.growslice
332 . . func reflect_growslice(et *_type, old slice, num int) slice {
333 . . // Semantically equivalent to slices.Grow, except that the caller
334 . . // is responsible for ensuring that old.len+num > old.cap.
335 . . num -= old.cap - old.len // preserve memory of old[old.len:old.cap]
336 5.02MB 5.02MB new := growslice(old.array, old.cap+num, old.cap, num, et)
337 . . // growslice does not zero out new[old.cap:new.len] since it assumes that
338 . . // the memory will be overwritten by an append() that called growslice.
339 . . // Since the caller of reflect_growslice is not append(),
340 . . // zero out this region before returning the slice to the reflect package.
341 . . if !et.Pointers() {
342 . . oldcapmem := uintptr(old.cap) * et.Size_
343 . . newlenmem := uintptr(new.len) * et.Size_
344 . . memclrNoHeapPointers(add(new.array, oldcapmem), newlenmem-oldcapmem)
345 . . }
346 . . new.len = old.len // preserve the old length
347 . . return new
348 . . }
349 . .
350 . . func isPowerOfTwo(x uintptr) bool {
351 . . return x&(x-1) == 0
352 . . }
github.com/braze-inc/dust/events.PublishMessageBulk
/Users/julian/code/dust/events/events.go
Total: 2.50MB 57.01MB (flat, cum) 18.19%
103 . . transitMites []*mite.InTransitMite,
104 . . message *json.RawMessage,
105 . . ) (err error) {
106 . 54.51MB span, ctx := tracer.StartSpanFromContext(ctx, "events.publish_message_bulk")
107 . . defer func() { span.Finish(tracer.WithError(err)) }()
108 . .
strconv.formatBits
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/strconv/itoa.go
Total: 4.50MB 4.50MB (flat, cum) 1.44%
131 . . // u < 1e9
132 . . }
133 . .
134 . . // u guaranteed to fit into a uint
135 . . us := uint(u)
136 . . for us >= 100 {
137 . . is := us % 100 * 2
138 . . us /= 100
139 . . i -= 2
140 . . a[i+1] = smallsString[is+1]
141 . . a[i+0] = smallsString[is+0]
142 . . }
143 . .
144 . . // us < 100
145 . . is := us * 2
146 . . i--
147 . . a[i] = smallsString[is+1]
148 . . if us >= 10 {
149 . . i--
150 . . a[i] = smallsString[is]
151 . . }
152 . .
153 . . } else if isPowerOfTwo(base) {
154 . . // Use shifts and masks instead of / and %.
155 . . shift := uint(bits.TrailingZeros(uint(base)))
156 . . b := uint64(base)
157 . . m := uint(base) - 1 // == 1<<shift - 1
158 . . for u >= b {
159 . . i--
160 . . a[i] = digits[uint(u)&m]
161 . . u >>= shift
162 . . }
163 . . // u < base
164 . . i--
165 . . a[i] = digits[uint(u)]
166 . . } else {
167 . . // general case
168 . . b := uint64(base)
169 . . for u >= b {
170 . . i--
171 . . // Avoid using r = a%b in addition to q = a/b
172 . . // since 64bit division and modulo operations
173 . . // are calculated by runtime functions on 32bit machines.
174 . . q := u / b
175 . . a[i] = digits[uint(u-q*b)]
176 . . u = q
177 . . }
178 . . // u < base
179 . . i--
180 . . a[i] = digits[uint(u)]
181 . . }
182 . .
183 . . // add sign, if any
184 . . if neg {
185 . . i--
186 . . a[i] = '-'
187 . . }
188 . .
189 . . if append_ {
190 . . d = append(dst, a[i:]...)
191 . . return
192 . . }
193 4.50MB 4.50MB s = string(a[i:])
194 . . return
195 . . }
196 . .
197 . . func isPowerOfTwo(x int) bool {
198 . . return x&(x-1) == 0
199 . . }
github.com/DataDog/datadog-go/v5/statsd.newWithWriter
/Users/julian/go/pkg/mod/github.com/!data!dog/datadog-go/v5@v5.8.1/statsd/buffer.go
Total: 3.01MB 3.01MB (flat, cum) 0.96%
30 . . maxElements int
31 . . elementCount int
32 . . }
33 . .
34 . . func newStatsdBuffer(maxSize, maxElements int) *statsdBuffer {
35 . . return &statsdBuffer{
36 3.01MB 3.01MB buffer: make([]byte, 0, maxSize+metricOverhead), // pre-allocate the needed size + metricOverhead to avoid having Go re-allocate on it's own if an element does not fit
37 . . maxSize: maxSize,
38 . . maxElements: maxElements,
39 . . }
40 . . }
41 . .
42 . . func (b *statsdBuffer) writeGauge(namespace string, globalTags []string, name string, value float64, tags []string, rate float64, timestamp int64, originDetection bool, overrideCard Cardinality) error {
43 . . if b.elementCount >= b.maxElements {
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.ContextWithSpan
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/context.go
Total: 0 5MB (flat, cum) 1.60%
13 . . illmobs "github.com/DataDog/dd-trace-go/v2/internal/llmobs"
14 . . "github.com/DataDog/dd-trace-go/v2/internal/orchestrion"
15 . . )
16 . .
17 . . // ContextWithSpan returns a copy of the given context which includes the span s.
18 . . func ContextWithSpan(ctx context.Context, s *Span) context.Context {
19 . 2.50MB newCtx := orchestrion.CtxWithValue(ctx, internal.ActiveSpanKey, s)
20 . 2.50MB return contextWithPropagatedLLMSpan(newCtx, s)
21 . . }
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.contextWithPropagatedLLMSpan
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/context.go
Total: 0 2.50MB (flat, cum) 0.8%
22 . .
23 . . func contextWithPropagatedLLMSpan(ctx context.Context, s *Span) context.Context {
24 . . if s == nil {
25 . . return ctx
26 . . }
27 . . // if there is a propagated llm span already just skip
28 . . if _, ok := illmobs.PropagatedLLMSpanFromContext(ctx); ok {
29 . . return ctx
30 . . }
31 . . newCtx := ctx
32 . .
33 . 2.50MB propagatedLLMObs := propagatedLLMSpanFromTags(s)
34 . . if propagatedLLMObs.SpanID == "" || propagatedLLMObs.TraceID == "" {
35 . . return newCtx
36 . . }
37 . . return illmobs.ContextWithPropagatedLLMSpan(newCtx, propagatedLLMObs)
38 . . }
39 . .
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.propagatedLLMSpanFromTags
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/context.go
Total: 2.50MB 2.50MB (flat, cum) 0.8%
40 . . // propagatedLLMSpanFromTags extracts LLMObs propagation information from the trace propagating tags.
41 . . // This is used during distributed tracing to set the correct parent span for the current span.
42 . . func propagatedLLMSpanFromTags(s *Span) *illmobs.PropagatedLLMSpan {
43 2.50MB 2.50MB propagatedLLMObs := &illmobs.PropagatedLLMSpan{}
44 . . if s.context == nil || s.context.trace == nil {
45 . . return propagatedLLMObs
46 . . }
47 . . if parentID := s.context.trace.propagatingTag(keyPropagatedLLMObsParentID); parentID != "" {
48 . . propagatedLLMObs.SpanID = parentID
49 . . }
50 . . if mlApp := s.context.trace.propagatingTag(keyPropagatedLLMObsMLAPP); mlApp != "" {
51 . . propagatedLLMObs.MLApp = mlApp
52 . . }
53 . . if trID := s.context.trace.propagatingTag(keyPropagatedLLMObsTraceID); trID != "" {
54 . . propagatedLLMObs.TraceID = trID
55 . . }
56 . . return propagatedLLMObs
57 . . }
58 . .
59 . . // SpanFromContext returns the span contained in the given context. A second return
60 . . // value indicates if a span was found in the context. If no span is found, a no-op
61 . . // span is returned.
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.StartSpanFromContext
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/context.go
Total: 0 120.52MB (flat, cum) 38.46%
74 . . }
75 . .
76 . . // StartSpanFromContext returns a new span with the given operation name and options. If a span
77 . . // is found in the context, it will be used as the parent of the resulting span. If the ChildOf
78 . . // option is passed, it will only be used as the parent if there is no span found in `ctx`.
79 . . func StartSpanFromContext(ctx context.Context, operationName string, opts ...StartSpanOption) (*Span, context.Context) {
80 . . // copy opts in case the caller reuses the slice in parallel
81 . . // we will add at least 1, at most 2 items
82 . . optsLocal := options.Expand(opts, 0, 2)
83 . . if ctx == nil {
84 . . // default to context.Background() to avoid panics on Go >= 1.15
85 . . ctx = context.Background()
86 . . } else if s, ok := SpanFromContext(ctx); ok {
87 . . optsLocal = append(optsLocal, ChildOf(s.Context()))
88 . . }
89 . . optsLocal = append(optsLocal, withContext(ctx))
90 . 115.52MB s := StartSpan(operationName, optsLocal...)
91 . . if s != nil && s.pprofCtxActive != nil {
92 . . ctx = s.pprofCtxActive
93 . . }
94 . 5MB return s, ContextWithSpan(ctx, s)
95 . . }
github.com/DataDog/dd-trace-go/v2/internal.GetTracerGitMetadataTags
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/internal/gitmetadata.go
Total: 2.50MB 2.50MB (flat, cum) 0.8%
131 . .
132 . . // GetTracerGitMetadataTags returns git metadata tags for tracer
133 . . // NB: Currently tracer inject tags with some workaround
134 . . // (only with _dd prefix and only for the first span in payload)
135 . . // So we provide different tag names
136 . . func GetTracerGitMetadataTags() map[string]string {
137 2.50MB 2.50MB res := make(map[string]string)
138 . . tags := GetGitMetadataTags()
139 . .
140 . . updateTags(res, TraceTagRepositoryURL, tags[TagRepositoryURL])
141 . . updateTags(res, TraceTagCommitSha, tags[TagCommitSha])
142 . . updateTags(res, TraceTagGoPath, tags[TagGoPath])
143 . .
144 . . return res
145 . . }
146 . .
147 . . // removeCredentials returns the passed url with potential credentials removed.
148 . . // If the input string is not a valid URL, the string is returned as is.
149 . . func removeCredentials(urlStr string) string {
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.StartSpanFromContext
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/option.go
Total: 2.50MB 2.50MB (flat, cum) 0.8%
1448 . .
1449 . . // ChildOf tells StartSpan to use the given span context as a parent for the created span.
1450 . . //
1451 . . // Deprecated: Use [Span.StartChild] instead.
1452 . . func ChildOf(ctx *SpanContext) StartSpanOption {
1453 512.01kB 512.01kB return func(cfg *StartSpanConfig) {
1454 . . cfg.Parent = ctx
1455 . . }
1456 . . }
1457 . .
1458 . . // withContext associates the ctx with the span.
1459 . . func withContext(ctx context.Context) StartSpanOption {
1460 2MB 2MB return func(cfg *StartSpanConfig) {
1461 . . cfg.Context = ctx
1462 . . }
1463 . . }
1464 . .
1465 . . // StartTime sets a custom time as the start time for the created span. By
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*prioritySampler).apply
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/ddtrace/tracer/sampler.go
Total: 2MB 13MB (flat, cum) 4.15%
169 . . return ps.defaultRate
170 . . }
171 . .
172 . . // apply applies sampling priority to the given span. Caller must ensure it is safe
173 . . // to modify the span.
174 . . func (ps *prioritySampler) apply(spn *Span) {
175 . . rate := ps.getRate(spn)
176 . . if sampledByRate(spn.traceID, rate) {
177 . 11MB spn.setSamplingPriority(ext.PriorityAutoKeep, samplernames.AgentRate)
178 . . } else {
179 . . spn.setSamplingPriority(ext.PriorityAutoReject, samplernames.AgentRate)
180 . . }
181 512.01kB 512.01kB spn.SetTag(keySamplingPriorityRate, rate)
182 . . // Set the Knuth sampling rate tag when sampled by agent rate
183 1.50MB 1.50MB spn.SetTag(keyKnuthSamplingRate, formatKnuthSamplingRate(rate))
184 . . }
github.com/redis/go-redis/v9/internal/proto.(*Reader).ReadReply
/Users/julian/go/pkg/mod/github.com/redis/go-redis/v9@v9.16.0/internal/proto/reader.go
Total: 2MB 7MB (flat, cum) 2.23%
244 . . return nil, fmt.Errorf("redis: invalid reply: %q", b)
245 . . }
246 . . return b[:len(b)-2], nil
247 . . }
248 . .
249 . . func (r *Reader) ReadReply() (interface{}, error) {
250 . . line, err := r.ReadLine()
251 . . if err != nil {
252 . . return nil, err
253 . . }
254 . .
255 . . switch line[0] {
256 . . case RespStatus:
257 . . return string(line[1:]), nil
258 . . case RespInt:
259 . . return util.ParseInt(line[1:], 10, 64)
260 . . case RespFloat:
261 . . return r.readFloat(line)
262 . . case RespBool:
263 . . return r.readBool(line)
264 . . case RespBigInt:
265 . . return r.readBigInt(line)
266 . .
267 . . case RespString:
268 2MB 3.50MB return r.readStringReply(line)
269 . . case RespVerbatim:
270 . . return r.readVerb(line)
271 . .
272 . . case RespArray, RespSet, RespPush:
273 . 3.50MB return r.readSlice(line)
274 . . case RespMap:
275 . . return r.readMap(line)
276 . . }
277 . . return nil, fmt.Errorf("redis: can't parse %.100q", line)
278 . . }
279 . .
280 . . func (r *Reader) readFloat(line []byte) (float64, error) {
281 . . v := string(line[1:])
282 . . switch string(line[1:]) {
runtime.allocm
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/runtime/proc.go
Total: 1.50MB 1.50MB (flat, cum) 0.48%
2220 . . //
2221 . . // This function is allowed to have write barriers even if the caller
2222 . . // isn't because it borrows pp.
2223 . . //
2224 . . //go:yeswritebarrierrec
2225 . . func allocm(pp *p, fn func(), id int64) *m {
2226 . . allocmLock.rlock()
2227 . .
2228 . . // The caller owns pp, but we may borrow (i.e., acquirep) it. We must
2229 . . // disable preemption to ensure it is not stolen, which would make the
2230 . . // caller lose ownership.
2231 . . acquirem()
2232 . .
2233 . . gp := getg()
2234 . . if gp.m.p == 0 {
2235 . . acquirep(pp) // temporarily borrow p for mallocs in this function
2236 . . }
2237 . .
2238 . . // Release the free M list. We need to do this somewhere and
2239 . . // this may free up a stack we can use.
2240 . . if sched.freem != nil {
2241 . . lock(&sched.lock)
2242 . . var newList *m
2243 . . for freem := sched.freem; freem != nil; {
2244 . . // Wait for freeWait to indicate that freem's stack is unused.
2245 . . wait := freem.freeWait.Load()
2246 . . if wait == freeMWait {
2247 . . next := freem.freelink
2248 . . freem.freelink = newList
2249 . . newList = freem
2250 . . freem = next
2251 . . continue
2252 . . }
2253 . . // Drop any remaining trace resources.
2254 . . // Ms can continue to emit events all the way until wait != freeMWait,
2255 . . // so it's only safe to call traceThreadDestroy at this point.
2256 . . if traceEnabled() || traceShuttingDown() {
2257 . . traceThreadDestroy(freem)
2258 . . }
2259 . . // Free the stack if needed. For freeMRef, there is
2260 . . // nothing to do except drop freem from the sched.freem
2261 . . // list.
2262 . . if wait == freeMStack {
2263 . . // stackfree must be on the system stack, but allocm is
2264 . . // reachable off the system stack transitively from
2265 . . // startm.
2266 . . systemstack(func() {
2267 . . stackfree(freem.g0.stack)
2268 . . })
2269 . . }
2270 . . freem = freem.freelink
2271 . . }
2272 . . sched.freem = newList
2273 . . unlock(&sched.lock)
2274 . . }
2275 . .
2276 1.50MB 1.50MB mp := new(m)
2277 . . mp.mstartfn = fn
2278 . . mcommoninit(mp, id)
2279 . .
2280 . . // In case of cgo or Solaris or illumos or Darwin, pthread_create will make us a stack.
2281 . . // Windows and Plan 9 will layout sched stack on OS stack.
2282 . . if iscgo || mStackIsSystemAllocated() {
2283 . . mp.g0 = malg(-1)
2284 . . } else {
2285 . . mp.g0 = malg(16384 * sys.StackGuardMultiplier)
2286 . . }
2287 . . mp.g0.m = mp
2288 . .
2289 . . if pp == gp.m.p.ptr() {
2290 . . releasep()
2291 . . }
2292 . .
2293 . . releasem(gp.m)
2294 . . allocmLock.runlock()
2295 . . return mp
2296 . . }
2297 . .
2298 . . // needm is called when a cgo callback happens on a
2299 . . // thread without an m (a thread not created by Go).
2300 . . // In this case, needm is expected to find an m to use
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.StartSpanFromContext
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/instrumentation/options/options.go
Total: 512.01kB 512.01kB (flat, cum) 0.16%
25 . . // appended to the new slice.
26 . . // The new slice will have a capacity of initialPosition + len(opts) + trailCapacity
27 . . // and a length of initialPosition + len(opts).
28 . . func Expand[T any](opts []T, initialPosition, trailCapacity int) []T {
29 . . capacity := initialPosition + len(opts)
30 512.01kB 512.01kB dup := make([]T, capacity, capacity+trailCapacity)
31 . . copy(dup[initialPosition:], opts)
32 . . return dup
33 . . }
34 . .
35 . . // This is a workaround needed because of v2 changes that prevents contribs from accessing
36 . . // the internal directory. This function should not be used if the internal directory
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*trace).setTraceTags
/Users/julian/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.7.darwin-arm64/src/encoding/hex/hex.go
Total: 512.01kB 512.01kB (flat, cum) 0.16%
122 . . return dst[:len(dst)+n], err
123 . . }
124 . .
125 . . // EncodeToString returns the hexadecimal encoding of src.
126 . . func EncodeToString(src []byte) string {
127 . . dst := make([]byte, EncodedLen(len(src)))
128 . . Encode(dst, src)
129 512.01kB 512.01kB return string(dst)
130 . . }
131 . .
132 . . // DecodeString returns the bytes represented by the hexadecimal string s.
133 . . //
134 . . // DecodeString expects that src contains only hexadecimal
github.com/DataDog/datadog-go/v5/statsd.newWithWriter
/Users/julian/go/pkg/mod/github.com/!data!dog/datadog-go/v5@v5.8.1/statsd/statsdex.go
Total: 0 514.63kB (flat, cum) 0.16%
468 . . }
469 . .
470 . . // Inject values of DD_* environment variables as global tags.
471 . . for _, mapping := range ddEnvTagsMapping {
472 . . if value := os.Getenv(mapping.envName); value != "" {
473 . . c.tags = append(c.tags, fmt.Sprintf("%s:%s", mapping.tagName, value))
474 . . }
475 . . }
476 . . // Whether origin detection is enabled or not for this client, we need to initialize the global
477 . . // external environment variable in case another client has enabled it and needs to access it.
478 . . initExternalEnv()
479 . .
480 . . // Initializes the global tag cardinality with either the value passed in by the user or the value from the DD_CARDINALITY/DATADOG_CARDINALITY environment variable.
481 . . initTagCardinality(o.tagCardinality)
482 . .
483 . . initContainerID(o.containerID, fillInContainerID(o), isHostCgroupNamespace())
484 . . isUDS := writerName == writerNameUDS
485 . .
486 . . if o.maxBytesPerPayload == 0 {
487 . . if isUDS {
488 . . o.maxBytesPerPayload = DefaultMaxAgentPayloadSize
489 . . } else {
490 . . o.maxBytesPerPayload = OptimalUDPPayloadSize
491 . . }
492 . . }
493 . . if o.bufferPoolSize == 0 {
494 . . if isUDS {
495 . . o.bufferPoolSize = DefaultUDSBufferPoolSize
496 . . } else {
497 . . o.bufferPoolSize = DefaultUDPBufferPoolSize
498 . . }
499 . . }
500 . . if o.senderQueueSize == 0 {
501 . . if isUDS {
502 . . o.senderQueueSize = DefaultUDSBufferPoolSize
503 . . } else {
504 . . o.senderQueueSize = DefaultUDPBufferPoolSize
505 . . }
506 . . }
507 . .
508 . . bufferPool := newBufferPool(o.bufferPoolSize, o.maxBytesPerPayload, o.maxMessagesPerPayload)
509 . . c.sender = newSender(w, o.senderQueueSize, bufferPool, o.errorHandler)
510 . . c.aggregatorMode = o.receiveMode
511 . .
512 . . c.workersMode = o.receiveMode
513 . . // channelMode mode at the worker level is not enabled when
514 . . // ExtendedAggregation is since the user app will not directly
515 . . // use the worker (the aggregator sit between the app and the
516 . . // workers).
517 . . if o.extendedAggregation {
518 . . c.workersMode = mutexMode
519 . . }
520 . .
521 . . if o.aggregation || o.extendedAggregation || o.maxBufferedSamplesPerContext > 0 {
522 . . c.agg = newAggregator(&c, int64(o.maxBufferedSamplesPerContext))
523 . . c.agg.start(o.aggregationFlushInterval)
524 . .
525 . . if o.extendedAggregation {
526 . . c.aggExtended = c.agg
527 . .
528 . . if c.aggregatorMode == channelMode {
529 . . c.agg.startReceivingMetric(o.channelModeBufferSize, o.workersCount)
530 . . }
531 . . }
532 . . }
533 . .
534 . . for i := 0; i < o.workersCount; i++ {
535 . 514.63kB w := newWorker(bufferPool, c.sender)
536 . . c.workers = append(c.workers, w)
537 . .
538 . . if c.workersMode == channelMode {
539 . . w.startReceivingMetric(o.channelModeBufferSize)
540 . . }
541 . . }
542 . .
543 . . c.flushTime = o.bufferFlushInterval
544 . . c.stop = make(chan struct{}, 1)
545 . .
546 . . c.wg.Add(1)
547 . . go func() {
548 . . defer c.wg.Done()
549 . . c.watch()
550 . . }()
551 . .
552 . . if o.telemetry {
553 . . if o.telemetryAddr == "" {
554 . . c.telemetryClient = newTelemetryClient(&c, c.agg != nil)
555 . . } else {
556 . . var err error
557 . . c.telemetryClient, err = newTelemetryClientWithCustomAddr(&c, o.telemetryAddr, c.agg != nil, bufferPool, o.writeTimeout, o.connectTimeout)
558 . . if err != nil {
559 . . return nil, err
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.spanStart
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/internal/traceprof/profiler.go
Total: 0 6.50MB (flat, cum) 2.07%
16 . . func SetProfilerEnabled(val bool) bool {
17 . . return atomic.SwapUint32(&profiler.enabled, boolToUint32(val)) != 0
18 . . }
19 . .
20 . . func profilerEnabled() int {
21 . . return int(atomic.LoadUint32(&profiler.enabled))
22 . . }
23 . .
24 . . func boolToUint32(b bool) uint32 {
25 . . if b {
26 . . return 1
27 . . }
28 . . return 0
29 . . }
30 . .
31 . . func SetProfilerRootTags(localRootSpan TagSetter) {
32 . 6.50MB localRootSpan.SetTag("_dd.profiling.enabled", profilerEnabled())
33 . . }
34 . .
35 . . type TagSetter interface{ SetTag(string, interface{}) }
github.com/DataDog/dd-trace-go/v2/internal.NewStatsdClient
/Users/julian/go/pkg/mod/github.com/!data!dog/dd-trace-go/v2@v2.4.0/internal/statsd.go
Total: 0 3.51MB (flat, cum) 1.12%
24 . . Flush() error
25 . . Close() error
26 . . }
27 . .
28 . . // NewStatsdClient returns a new statsd client with the provided address and globaltags
29 . . func NewStatsdClient(addr string, globalTags []string) (StatsdClient, error) {
30 . . if addr == "" {
31 . . addr = DefaultDogstatsdAddr
32 . . }
33 . 3.51MB client, err := statsd.NewDirect(addr, statsd.WithMaxMessagesPerPayload(40), statsd.WithTags(globalTags))
34 . . if err != nil {
35 . . return &statsd.NoOpClientDirect{}, err
36 . . }
37 . . return client, nil
38 . . }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment