refactor(config): migrate peer service config to internal/config#4483
refactor(config): migrate peer service config to internal/config#4483funyjane wants to merge 9 commits intoDataDog:mainfrom
Conversation
Move peerServiceDefaultsEnabled and peerServiceMappings from the tracer's local config struct to internal/config.Config. These fields already existed in the global config singleton but lacked getter/setter methods. The tracer now reads/writes peer service configuration through the global config accessors instead of maintaining local copies.
genesor
left a comment
There was a problem hiding this comment.
Nice work! Some modifications are needed to really finish migration into the new struct
…e defaults - Fix gofmt: remove extra blank line left from field removal - Keep schema-dependent peerServiceDefaultsEnabled logic in the tracer since spanAttributeSchemaVersion uses namingschema (understands "v1") while loadConfig uses getInt (can't parse "v1"). Add TODO to move once spanAttributeSchemaVersion is fully migrated.
SetPeerServiceMappings and SetPeerServiceMapping have non-standard signatures (map param / three value params) that the reflective test helper cannot auto-generate values for. Register them as special cases.
Address PR review comments: move the peer service defaults enabled logic (schema-dependent calculation) from tracer's newConfig() to internal config's loadConfig(). Add parseSpanAttributeSchema parser that handles "v0"/"v1" string values (matching namingschema behavior), replacing the previous getInt call that couldn't parse these formats.
internal/config/config.go
Outdated
| cfg.profilerEndpoints = provider.getBool("DD_PROFILING_ENDPOINT_COLLECTION_ENABLED", true) | ||
| cfg.spanAttributeSchemaVersion = provider.getInt("DD_TRACE_SPAN_ATTRIBUTE_SCHEMA", 0) | ||
| cfg.peerServiceDefaultsEnabled = provider.getBool("DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED", false) | ||
| cfg.spanAttributeSchemaVersion = get(provider, "DD_TRACE_SPAN_ATTRIBUTE_SCHEMA", 0, parseSpanAttributeSchema) |
There was a problem hiding this comment.
get is a nested helper function and should not be called directly outside of configprovider. Instead, you can create a new getBoolWithValidator function (see globalSampleRate and getFloatWithValidator for example).
There was a problem hiding this comment.
Changed it to using provider.getString to read the raw value, then parsing it with parseSpanAttributeSchema in the derived evaluations section below. Looked it up on featureFlags and logToStdout.
internal/config/config.go
Outdated
| // peer.service tag default calculation is enabled by default if using attribute schema >= 1. | ||
| // When schema >= v1 the env var default is overridden to true. |
There was a problem hiding this comment.
These lines express the same thing; use only one or the other.
internal/config/config.go
Outdated
| if cfg.spanAttributeSchemaVersion >= 1 { | ||
| cfg.peerServiceDefaultsEnabled = true | ||
| } else { | ||
| cfg.peerServiceDefaultsEnabled = provider.getBool("DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED", false) | ||
| } |
There was a problem hiding this comment.
The status quo right now is to keep all the evaluations that are provider.getX based together and, down below, perform the evaluations that rely on other configs.
That is -- move this to the line 158+ section.
There was a problem hiding this comment.
Moved the schema-dependent override to the derived evaluations section below, after the feature flags parsing. All provider.getX calls are now grouped together.
ddtrace/tracer/tracer.go
Outdated
| PeerServiceDefaults: t.config.peerServiceDefaultsEnabled, | ||
| PeerServiceMappings: t.config.peerServiceMappings, | ||
| PeerServiceDefaults: t.config.internalConfig.PeerServiceDefaultsEnabled(), | ||
| PeerServiceMappings: t.config.internalConfig.PeerServiceMappings(), |
There was a problem hiding this comment.
This is a bit of a problem.
TracerConf() is called inside of setPeerService, which is invoked for every single span we create. This means -- if t.config.internalConfig.PeerServiceMappings() is not empty -- that we are acquiring the lock and iterating over and copying internalconfig's PeerServiceMappings map on every single span, just to ultimately query the map by a key value. This creates lock contention and unnecessary memory allocation.
There was a problem hiding this comment.
I don't have a solution off the top of my head, but perhaps you can run this^ concern, along with the guidelines of the new readme, through cursor/whatever AI dev tool you may be using and have it help you come up with an alternate solution. Perhaps function signatures may have to change.
There was a problem hiding this comment.
This one is slightly more complicated, I replaced PeerServiceMappings map[string]string in TracerConf with PeerServiceMapping func(string) (string, bool). Do you think a single-key lookup that avoids copying the map and acquiring the lock on every span is a good solution here? As you can see from these changes setPeerService now calls this function directly.
| // PeerServiceMapping performs a single mapping lookup without copying the underlying map. | ||
| // This is better than PeerServiceMappings() for hot paths to avoid per-call allocations. | ||
| func (c *Config) PeerServiceMapping(from string) (to string, ok bool) { | ||
| c.mu.RLock() | ||
| m := c.peerServiceMappings | ||
| if m == nil { | ||
| c.mu.RUnlock() | ||
| return "", false | ||
| } | ||
| to, ok = m[from] | ||
| c.mu.RUnlock() | ||
| return to, ok | ||
| } |
There was a problem hiding this comment.
No need to introduce APIs that are as of yet unused. So, depending on the outcome of this, you can potentially delete this, as well as SetPeerServiceMapping.
…okup - Add getIntWithParser to configprovider instead of calling get() directly - Keep all provider.getX calls together in loadConfig; move the schema-dependent peer service defaults override to the derived evaluations section below - Replace PeerServiceMappings map in TracerConf with a PeerServiceMapping func for single-key lookups, avoiding lock contention and per-call map copies on the hot path (setPeerService is called for every span) - Update setPeerService to use the function-based lookup with nil guard - Fix CiVisibility TracerConf test to compare fields individually since functions cannot be compared with reflect.DeepEqual
…al mutation Made-with: Cursor
Follow the existing configprovider pattern: use getString to read the raw DD_TRACE_SPAN_ATTRIBUTE_SCHEMA value and parse it in the derived evaluations section with parseSpanAttributeSchema, matching how featureFlags and logToStdout are handled. Remove the getIntWithParser method that was just a passthrough to get().
Codecov Report❌ Patch coverage is
Additional details and impacted files
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Background
The goal is for internal/config to become the single source of truth for configuration values.
That means, our end state will not capture and store locally any configuration values (unless there is a specific reason for capturing a "snapshot" of config at a certain time, but that's not the case here).
TracerConf is a bandaid for getting access to configuration values that were previously only available inside the tracer.config object. Now that we have global access to configuration values via internal/config, it is no longer necessary.
Recommendation
So I would recommend deleting PeerServiceMapping from TracerConf entirely, changing the function signature of setPeerService to just setPeerService(s *Span), call the config singleton inside of setPeerService and use its peer service value:
if to, ok := internalconfig.Get().PeerServiceMapping(ps); ok {
s.setMetaLocked(keyPeerServiceRemappedFrom, ps)
s.setMetaLocked(ext.PeerService, to)
}
Concerns
The thing is -- this approach presents lots of potential for lock contention. We will be acquiring the lock in internalconfig.Get() in a very hot path (setPeerService is called for every span). I'd suggest you make this change and observe any issues in existing tests or benchmarks. We may need to replace the internalconfig.Get() logic to not use a lock.
cc @genesor
…r own packages (#4494) <!-- * New contributors are highly encouraged to read our [CONTRIBUTING](/CONTRIBUTING.md) documentation. * Commit and PR titles should be prefixed with the general area of the pull request's change. --> ### What does this PR do? Migrates `configprovider` and `telemetry` logic in the `internal/config` package into their own respective packages. "Internalize" logic that is just an implementation detail. ### Motivation Inspired by early commits in: #4483 The API contract for the configprovider and the telemetry logic was not clear. I migrated these to their own packages to make their API usage clearer. ### Reviewer's Checklist <!-- * Authors can use this list as a reference to ensure that there are no problems during the review but the signing off is to be done by the reviewer(s). --> - [ ] Changed code has unit tests for its functionality at or near 100% coverage. - [ ] [System-Tests](https://github.com/DataDog/system-tests/) covering this feature have been added and enabled with the va.b.c-dev version tag. - [ ] There is a benchmark for any new code, or changes to existing code. - [ ] If this interacts with the agent in a new way, a system test has been added. - [ ] New code is free of linting errors. You can check this by running `make lint` locally. - [ ] New code doesn't break existing tests. You can check this by running `make test` locally. - [ ] Add an appropriate team label so this PR gets put in the right place for the release notes. - [ ] All generated files are up to date. You can check this by running `make generate` locally. - [ ] Non-trivial go.mod changes, e.g. adding new modules, are reviewed by @DataDog/dd-trace-go-guild. Make sure all nested modules are up to date by running `make fix-modules` locally. Unsure? Have a question? Request a review! Co-authored-by: kemal.akkoyun <[email protected]>
I agree with you @mtoffl01, we should look into removing the band-aid in the future but currently My 2cts on this topic would be to keep the migration simple and tackle the |
Summary
internal/config.ConfigforpeerServiceDefaultsEnabledandpeerServiceMappings(fields already existed but lacked accessors)peerServiceDefaultsEnabledandpeerServiceMappingsfields from the tracer's localconfigstructTest plan
go test ./ddtrace/tracer/... -run "TestTracerOptionsDefaults/peer-service"— config parsing, env vars, programmatic optionsgo test ./ddtrace/tracer/... -run "TestSpanPeerService"— span tag calculation, mappings, defaults (17 test cases)go build ./internal/config/... ./ddtrace/tracer/...— compiles cleanly