perf: improve process tags insertion#4010
Conversation
7dc6b29 to
8a5339e
Compare
BenchmarksBenchmark execution time: 2025-10-06 21:32:54 Comparing candidate commit a5d6611 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 24 metrics, 0 unstable metrics. |
8a5339e to
17e7345
Compare
1976f52 to
17e7345
Compare
290b71b to
7769535
Compare
328f168 to
28f42b0
Compare
Co-authored-by: Rodrigo Argüello <[email protected]>
| shallowCopySpan := *s //nolint:govet // to avoid the copylocks warning, the locks were not used further down | ||
| // favoring a copy of all fields to avoid missing one | ||
| shallowCopySpan.meta = metaWithProcessTags(s.meta, pTags) | ||
| t[i] = &shallowCopySpan |
There was a problem hiding this comment.
I think this is not required. Each unsafePayload function assumes that is under a lock from the same function at safePayload. Did you find race conditions during development?
There was a problem hiding this comment.
I did find a race condition on map accesses of span.meta, that should be possible too on the field span.meta when overwriting it, that's why I did the copy.
I'll try to trigger that race on span.meta field
There was a problem hiding this comment.
ok I did trigger the race on TestSpanProcessTags with -race otpion, you can try it out by removing the shallow copy and running
$ go test -run TestSpanProcessTags -race
==================
WARNING: DATA RACE
Write at 0x00c0000003c8 by goroutine 8263:
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*unsafePayload).setTracerTags()
/Users/raphael.gavache/dd/dd-trace-go/ddtrace/tracer/payload.go:141 +0x2bc
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*unsafePayload).push()
/Users/raphael.gavache/dd/dd-trace-go/ddtrace/tracer/payload.go:116 +0x40
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*safePayload).push()
/Users/raphael.gavache/dd/dd-trace-go/ddtrace/tracer/payload.go:277 +0xa8
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*agentTraceWriter).add()
/Users/raphael.gavache/dd/dd-trace-go/ddtrace/tracer/writer.go:74 +0x74
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*tracer).worker()
/Users/raphael.gavache/dd/dd-trace-go/ddtrace/tracer/tracer.go:518 +0x22c
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.newTracer.func2()
/Users/raphael.gavache/dd/dd-trace-go/ddtrace/tracer/tracer.go:473 +0x10c
Previous read at 0x00c0000003c8 by goroutine 8248:
github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.(*concentrator).newTracerStatSpan()
...
==================
--- FAIL: TestSpanProcessTags (0.02s)
--- FAIL: TestSpanProcessTags/enabled (0.01s)
testing.go:1490: race detected during execution of test
There was a problem hiding this comment.
what about just using the span mutex?
s.mu.Lock()
s.meta[keyProcessTags] = pTags
s.mu.Unlock()Given this is only done for the first span in the first chunk, it shouldn't add much lock contention and I think it should be slightly better performance-wise. WDYT? @darccio @raphaelgavache
There was a problem hiding this comment.
looks good to me, nice call out
I don't know the implications on your side though, as it forces the span.finishes and especially stat computation to be completed before merging. From afar that doesn't seem a problem
There was a problem hiding this comment.
updated with the lock, all tests pass
There was a problem hiding this comment.
@kakkoyun Could you check this, please? Specially, if you can check this lock with the checklocks tooling, that'd be interesting.
kakkoyun
left a comment
There was a problem hiding this comment.
This is looking good.
I have only want comment.
mtoffl01
left a comment
There was a problem hiding this comment.
Overall approach LGTM, just some nits.
Also, some feedback on the setTracerTags:
setProcessTagsmay be a better name as its more descriptive/accurate- Does it really need to be a method on
unsafePayload? It could just take in a[]*Spanand set tags on the first one in the slice. Then you can move theif atomic.LoadUint32(&p.count) != 0check to the unsafePayload.push method; if true, you call the set tags method.
I'm holding off on approving until I get confirmation about the Github Actions checks that are failing about the use of atomics 🤔 (reaching out now)
| if len(t) == 0 { | ||
| return | ||
| } |
There was a problem hiding this comment.
Why not move this above line 131? In this case, we can avoid processing lines 131-134 entirely.
| // TestPayloadDecode ensures that whatever we push into the payload can | ||
| // be decoded by the codec. | ||
| func TestPayloadDecode(t *testing.T) { | ||
| t.Setenv("DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED", "true") |
There was a problem hiding this comment.
Code quality preference: I'd probably prefer making envProcessTagsEnabled public, and using that here instead of the raw string version.
There was a problem hiding this comment.
this env var will stop being called in the PR that follows where process tags are enabled by default:
#4008
I won't expose it as it will become public and not useful
| processtags.Reload() | ||
| defer func() { | ||
| t.Setenv("DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED", "false") | ||
| processtags.Reload() | ||
| }() |
There was a problem hiding this comment.
- No need to reset this when using t.SetEnv (https://pkg.go.dev/testing#T.Setenv)
- Regarding the duplicate content in lines 69 and 72 -- judging by the name of the function, I'd expect
Reloadto be called at the start of a test, not at the end. Do we need line 72?
There was a problem hiding this comment.
the reload call changes a global var/state of the process, so if we want this test not to impact all other tests run in the same process (tests run after the execution of this one), we have to reset
mtoffl01
left a comment
There was a problem hiding this comment.
I still think setTracerTags should be called setProcessTags, but other than that - LGTM!
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
|
What does this PR do?
Process tags were set on first span of each chunk instead of first span of first chunk which is what the agent expects. This results in a significant addition of bytes (+25%) for single span chunks with no tags!
The cost was caught by this regression test on the PR that enables process tags by default
Change of behaviour in this PR is to set process tags at the encoding level on the first span of the first chunk
NB Revisited tests to use getMeta/setMeta which are safe reads. They would trigger race conditions on map accesses when process tags are enabled without these changes
Motivation
Reviewer's Checklist
./scripts/lint.shlocally.Unsure? Have a question? Request a review!