[Tracer] Add endpoint discovery and dropping of P0 traces to stats computation feature#2988
[Tracer] Add endpoint discovery and dropping of P0 traces to stats computation feature#2988zacharycmontoya wants to merge 23 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
Code Coverage Report 📊✔️ Merging #2988 into master will not change line coverage
View the full report for further details: Datadog.Trace Breakdown ✔️
The following classes have significant coverage changes.
The following classes were added in #2988:
3 classes were removed from Datadog.Trace in #2988 View the full reports for further details: |
6f678b7 to
7084b89
Compare
3bab229 to
80c24f9
Compare
This comment has been minimized.
This comment has been minimized.
…Operation, Type, HTTPStatusCode, and Synthetics will be stored in different buckets
…ount for service entry spans that are child spans
f4909b5 to
e74fd99
Compare
This comment has been minimized.
This comment has been minimized.
e74fd99 to
bb3221b
Compare
… support them. Since this has not yet been implemented, the test is skipped
… case is skipped because the feature is not yet implemented
…d in the creation of the ApiFactory
Caveat: We may have the StatsComputationSetting enabled but the agent we try to reach does not support it. That complicated mess will be addressed next.
…ss when StatsAggregator is constructed. If the agent is unable to receive stats from tracers, the feature is disabled correctly and no stats are submitted.
…ion tests to allow us to delay test code until we've confirmed that the MockTracerAgent can receive the tracer stats. The delay should make the test results more stable
… object which can be converted directly to JSON
…ake sure to send P0 traces to the tracer.
…0-Traces" and "Datadog-Client-Dropped-P0-Spans" when we drop P0 traces
bb3221b to
229de9c
Compare
This comment has been minimized.
This comment has been minimized.
| if (store.Counts.Length > 0 && !store.IsEmpty()) | ||
| { | ||
| Counts = new double[store.MaxIndex - store.MinIndex + 1]; | ||
| // This change seems correct based on the code in the java repo: https://github.com/DataDog/sketches-java/blob/5031ddff8e6d6de5174d2c06e2ced618f3a48621/src/main/java/com/datadoghq/sketch/ddsketch/store/DenseStore.java#L60-L64 |
There was a problem hiding this comment.
@kevingosse what do you think? I haven't noticed any particular issues stemming from this code, but I noticed a discrepancy when reviewing this code
Benchmarks Report 🐌Benchmarks for #2988 compared to master:
The following thresholds were used for comparing the benchmark speeds:
Allocation changes below 0.5% are ignored. Benchmark detailsBenchmarks.Trace.AgentWriterBenchmark - Slower
|
| Benchmark | diff/base | Base Median (ns) | Diff Median (ns) | Modality |
|---|---|---|---|---|
| Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces‑netcoreapp3.1 | 1.181 | 460,907.57 | 544,367.68 |
| Benchmark | Base Allocated | Diff Allocated | Change | Change % |
|---|---|---|---|---|
| Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces‑net472 | 3.18 KB | 6.78 KB | 3.6 KB | 113.48% |
| Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces‑netcoreapp3.1 | 2.58 KB | 5.34 KB | 2.75 KB | 106.54% |
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | WriteAndFlushEnrichedTraces |
net472 | 711μs | 439ns | 1.64μs | 0.353 | 0 | 0 | 3.18 KB |
| master | WriteAndFlushEnrichedTraces |
netcoreapp3.1 | 461μs | 414ns | 1.55μs | 0 | 0 | 0 | 2.58 KB |
| #2988 | WriteAndFlushEnrichedTraces |
net472 | 759μs | 385ns | 1.49μs | 0.758 | 0 | 0 | 6.78 KB |
| #2988 | WriteAndFlushEnrichedTraces |
netcoreapp3.1 | 544μs | 419ns | 1.62μs | 0 | 0 | 0 | 5.34 KB |
Benchmarks.Trace.AppSecBodyBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | AllCycleSimpleBody |
net472 | 1.69μs | 1.18ns | 4.43ns | 0.237 | 0 | 0 | 1.49 KB |
| master | AllCycleSimpleBody |
netcoreapp3.1 | 1.82μs | 4.03ns | 15.1ns | 0.0189 | 0 | 0 | 1.37 KB |
| master | AllCycleMoreComplexBody |
net472 | 17.1μs | 16.4ns | 63.4ns | 1.38 | 0.025 | 0 | 8.75 KB |
| master | AllCycleMoreComplexBody |
netcoreapp3.1 | 14.1μs | 20.2ns | 72.7ns | 0.106 | 0 | 0 | 7.85 KB |
| master | BodyExtractorSimpleBody |
net472 | 257ns | 0.255ns | 0.989ns | 0.0574 | 0 | 0 | 361 B |
| master | BodyExtractorSimpleBody |
netcoreapp3.1 | 220ns | 0.383ns | 1.43ns | 0.00367 | 0 | 0 | 272 B |
| master | BodyExtractorMoreComplexBody |
net472 | 15.4μs | 12.3ns | 47.5ns | 1.21 | 0.0154 | 0 | 7.62 KB |
| master | BodyExtractorMoreComplexBody |
netcoreapp3.1 | 11.7μs | 10.9ns | 40.7ns | 0.0875 | 0 | 0 | 6.75 KB |
| #2988 | AllCycleSimpleBody |
net472 | 1.68μs | 1.62ns | 6.27ns | 0.237 | 0 | 0 | 1.49 KB |
| #2988 | AllCycleSimpleBody |
netcoreapp3.1 | 1.82μs | 4ns | 15.5ns | 0.0191 | 0 | 0 | 1.37 KB |
| #2988 | AllCycleMoreComplexBody |
net472 | 16.8μs | 13.3ns | 51.5ns | 1.39 | 0.0251 | 0 | 8.75 KB |
| #2988 | AllCycleMoreComplexBody |
netcoreapp3.1 | 14.1μs | 13ns | 50.4ns | 0.105 | 0 | 0 | 7.85 KB |
| #2988 | BodyExtractorSimpleBody |
net472 | 258ns | 0.165ns | 0.618ns | 0.0573 | 0 | 0 | 361 B |
| #2988 | BodyExtractorSimpleBody |
netcoreapp3.1 | 219ns | 0.257ns | 0.962ns | 0.00374 | 0 | 0 | 272 B |
| #2988 | BodyExtractorMoreComplexBody |
net472 | 15.1μs | 9.87ns | 38.2ns | 1.21 | 0.015 | 0 | 7.62 KB |
| #2988 | BodyExtractorMoreComplexBody |
netcoreapp3.1 | 11.9μs | 13ns | 50.2ns | 0.089 | 0 | 0 | 6.75 KB |
Benchmarks.Trace.AspNetCoreBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | SendRequest |
net472 | 0ns | 0ns | 0ns | 0 | 0 | 0 | 0 b |
| master | SendRequest |
netcoreapp3.1 | 179μs | 180ns | 697ns | 0.269 | 0 | 0 | 20.37 KB |
| #2988 | SendRequest |
net472 | 0ns | 0ns | 0ns | 0 | 0 | 0 | 0 b |
| #2988 | SendRequest |
netcoreapp3.1 | 180μs | 56.9ns | 213ns | 0.27 | 0 | 0 | 20.37 KB |
Benchmarks.Trace.DbCommandBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | ExecuteNonQuery |
net472 | 1.52μs | 0.586ns | 2.19ns | 0.126 | 0.000766 | 0 | 794 B |
| master | ExecuteNonQuery |
netcoreapp3.1 | 1.28μs | 0.624ns | 2.42ns | 0.0109 | 0 | 0 | 824 B |
| #2988 | ExecuteNonQuery |
net472 | 1.59μs | 0.404ns | 1.56ns | 0.126 | 0.000794 | 0 | 794 B |
| #2988 | ExecuteNonQuery |
netcoreapp3.1 | 1.26μs | 0.537ns | 2.01ns | 0.0113 | 0 | 0 | 824 B |
Benchmarks.Trace.ElasticsearchBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | CallElasticsearch |
net472 | 2.12μs | 1.08ns | 4.17ns | 0.159 | 0 | 0 | 1 KB |
| master | CallElasticsearch |
netcoreapp3.1 | 1.43μs | 2.45ns | 9.49ns | 0.0134 | 0 | 0 | 984 B |
| master | CallElasticsearchAsync |
net472 | 2.34μs | 0.834ns | 3.12ns | 0.18 | 0 | 0 | 1.14 KB |
| master | CallElasticsearchAsync |
netcoreapp3.1 | 1.49μs | 0.656ns | 2.54ns | 0.0149 | 0 | 0 | 1.1 KB |
| #2988 | CallElasticsearch |
net472 | 2.18μs | 1.49ns | 5.59ns | 0.159 | 0 | 0 | 1 KB |
| #2988 | CallElasticsearch |
netcoreapp3.1 | 1.38μs | 0.772ns | 2.78ns | 0.0131 | 0 | 0 | 984 B |
| #2988 | CallElasticsearchAsync |
net472 | 2.21μs | 1.95ns | 7.54ns | 0.181 | 0 | 0 | 1.14 KB |
| #2988 | CallElasticsearchAsync |
netcoreapp3.1 | 1.48μs | 0.394ns | 1.47ns | 0.0149 | 0 | 0 | 1.1 KB |
Benchmarks.Trace.GraphQLBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | ExecuteAsync |
net472 | 2.53μs | 1.39ns | 5.36ns | 0.2 | 0 | 0 | 1.26 KB |
| master | ExecuteAsync |
netcoreapp3.1 | 1.6μs | 1.1ns | 4.12ns | 0.0161 | 0 | 0 | 1.22 KB |
| #2988 | ExecuteAsync |
net472 | 2.42μs | 5.04ns | 19.5ns | 0.199 | 0 | 0 | 1.26 KB |
| #2988 | ExecuteAsync |
netcoreapp3.1 | 1.6μs | 2.01ns | 7.54ns | 0.0168 | 0 | 0 | 1.22 KB |
Benchmarks.Trace.HttpClientBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | SendAsync |
net472 | 5.11μs | 2.09ns | 7.81ns | 0.393 | 0 | 0 | 2.48 KB |
| master | SendAsync |
netcoreapp3.1 | 3.36μs | 2.13ns | 7.95ns | 0.0318 | 0 | 0 | 2.4 KB |
| #2988 | SendAsync |
net472 | 5.09μs | 8.98ns | 34.8ns | 0.392 | 0 | 0 | 2.48 KB |
| #2988 | SendAsync |
netcoreapp3.1 | 3.36μs | 4.12ns | 15.4ns | 0.0318 | 0 | 0 | 2.4 KB |
Benchmarks.Trace.ILoggerBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | EnrichedLog |
net472 | 2.78μs | 1.8ns | 6.97ns | 0.263 | 0 | 0 | 1.66 KB |
| master | EnrichedLog |
netcoreapp3.1 | 2.27μs | 0.595ns | 2.06ns | 0.0239 | 0 | 0 | 1.73 KB |
| #2988 | EnrichedLog |
net472 | 2.94μs | 3.9ns | 14.6ns | 0.264 | 0 | 0 | 1.66 KB |
| #2988 | EnrichedLog |
netcoreapp3.1 | 2.4μs | 1.6ns | 6.21ns | 0.0229 | 0 | 0 | 1.73 KB |
Benchmarks.Trace.Log4netBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | EnrichedLog |
net472 | 144μs | 101ns | 391ns | 0.651 | 0.217 | 0 | 4.5 KB |
| master | EnrichedLog |
netcoreapp3.1 | 110μs | 90.6ns | 351ns | 0.0551 | 0 | 0 | 4.38 KB |
| #2988 | EnrichedLog |
net472 | 147μs | 196ns | 758ns | 0.659 | 0.22 | 0 | 4.5 KB |
| #2988 | EnrichedLog |
netcoreapp3.1 | 113μs | 72.6ns | 262ns | 0.057 | 0 | 0 | 4.38 KB |
Benchmarks.Trace.NLogBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | EnrichedLog |
net472 | 5.45μs | 1.63ns | 6.32ns | 0.545 | 0.00273 | 0 | 3.43 KB |
| master | EnrichedLog |
netcoreapp3.1 | 4.23μs | 7.92ns | 30.7ns | 0.0524 | 0 | 0 | 3.8 KB |
| #2988 | EnrichedLog |
net472 | 5.41μs | 9.32ns | 36.1ns | 0.545 | 0.00266 | 0 | 3.43 KB |
| #2988 | EnrichedLog |
netcoreapp3.1 | 4.25μs | 1.77ns | 6.64ns | 0.0509 | 0 | 0 | 3.8 KB |
Benchmarks.Trace.RedisBenchmark - Slower ⚠️ Same allocations ✔️
Slower ⚠️ in #2988
Benchmark
diff/base
Base Median (ns)
Diff Median (ns)
Modality
Benchmarks.Trace.RedisBenchmark.SendReceive‑netcoreapp3.1
1.136
1,576.11
1,790.88
| Benchmark | diff/base | Base Median (ns) | Diff Median (ns) | Modality |
|---|---|---|---|---|
| Benchmarks.Trace.RedisBenchmark.SendReceive‑netcoreapp3.1 | 1.136 | 1,576.11 | 1,790.88 |
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | SendReceive |
net472 | 1.99μs | 0.447ns | 1.73ns | 0.193 | 0 | 0 | 1.22 KB |
| master | SendReceive |
netcoreapp3.1 | 1.58μs | 1.15ns | 4.32ns | 0.0165 | 0 | 0 | 1.21 KB |
| #2988 | SendReceive |
net472 | 1.99μs | 1.2ns | 4.65ns | 0.193 | 0 | 0 | 1.22 KB |
| #2988 | SendReceive |
netcoreapp3.1 | 1.79μs | 0.79ns | 3.06ns | 0.0168 | 0 | 0 | 1.21 KB |
Benchmarks.Trace.SerilogBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | EnrichedLog |
net472 | 4.75μs | 1.56ns | 5.82ns | 0.33 | 0 | 0 | 2.08 KB |
| master | EnrichedLog |
netcoreapp3.1 | 4.1μs | 2.39ns | 9.24ns | 0.0226 | 0 | 0 | 1.69 KB |
| #2988 | EnrichedLog |
net472 | 4.61μs | 1.71ns | 6.61ns | 0.33 | 0 | 0 | 2.08 KB |
| #2988 | EnrichedLog |
netcoreapp3.1 | 4.15μs | 1.75ns | 6.31ns | 0.0229 | 0 | 0 | 1.69 KB |
Benchmarks.Trace.SpanBenchmark - Faster 🎉 Same allocations ✔️
Faster 🎉 in #2988
Benchmark
base/diff
Base Median (ns)
Diff Median (ns)
Modality
Benchmarks.Trace.SpanBenchmark.StartFinishSpan‑netcoreapp3.1
1.153
821.56
712.30
| Benchmark | base/diff | Base Median (ns) | Diff Median (ns) | Modality |
|---|---|---|---|---|
| Benchmarks.Trace.SpanBenchmark.StartFinishSpan‑netcoreapp3.1 | 1.153 | 821.56 | 712.30 |
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | StartFinishSpan |
net472 | 870ns | 0.129ns | 0.464ns | 0.104 | 0 | 0 | 658 B |
| master | StartFinishSpan |
netcoreapp3.1 | 820ns | 0.891ns | 3.33ns | 0.00864 | 0 | 0 | 648 B |
| master | StartFinishScope |
net472 | 1.02μs | 0.411ns | 1.59ns | 0.117 | 0 | 0 | 738 B |
| master | StartFinishScope |
netcoreapp3.1 | 902ns | 0.35ns | 1.26ns | 0.0104 | 0 | 0 | 768 B |
| #2988 | StartFinishSpan |
net472 | 802ns | 0.137ns | 0.495ns | 0.104 | 0 | 0 | 658 B |
| #2988 | StartFinishSpan |
netcoreapp3.1 | 712ns | 0.244ns | 0.911ns | 0.0089 | 0 | 0 | 648 B |
| #2988 | StartFinishScope |
net472 | 1.04μs | 0.359ns | 1.34ns | 0.117 | 0 | 0 | 738 B |
| #2988 | StartFinishScope |
netcoreapp3.1 | 960ns | 0.25ns | 0.934ns | 0.0105 | 0 | 0 | 768 B |
Benchmarks.Trace.TraceAnnotationsBenchmark - Same speed ✔️ Same allocations ✔️
Raw results
| Branch | Method | Toolchain | Mean | StdError | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|
| master | RunOnMethodBegin |
net472 | 1.23μs | 0.493ns | 1.91ns | 0.117 | 0 | 0 | 738 B |
| master | RunOnMethodBegin |
netcoreapp3.1 | 1.04μs | 0.449ns | 1.74ns | 0.0104 | 0 | 0 | 768 B |
| #2988 | RunOnMethodBegin |
net472 | 1.21μs | 0.385ns | 1.49ns | 0.117 | 0 | 0 | 738 B |
| #2988 | RunOnMethodBegin |
netcoreapp3.1 | 1.07μs | 0.379ns | 1.47ns | 0.0102 | 0 | 0 | 768 B |
| var rawHttpStatusCode = span.GetTag(Tags.HttpStatusCode); | ||
| try | ||
| { | ||
| var isDiscoverySuccessful = await _discoveryService.DiscoverAsync().ConfigureAwait(false); |
There was a problem hiding this comment.
The way we're currently using DiscoveryService where we're only querying it on initialization means that if the customer enables stats in datadog-agent config (or upgrades datadog-agent to a version that has these endpoints) after the monitored processes have started, they'd need to restart all the monitored processes for the change to take effect.
This is not an ideal user experience, so I think we should definitely consider changing things such that DiscoveryService will be continuously polling the /info endpoint and triggering some sort of event / notification to all of its consumers when things change.
This is just a general comment - not related to your PR really, just wanted to bring it to your attention.
There was a problem hiding this comment.
This continuous check would benefit this feature as well, because if the agent has been upgraded/downgrade while the application is alive, we should adjust in real-time
Adds testing for various requirements of the stats computation feature, as agreed upon by other tracers. This is PR 1/3 in an attempt to break down the massive PR #2988
Summary of changes
Adds two new capabilities to the stats computation feature:
/infoendpoint to ensure that this feature is supported by the trace agent. If not, the feature is disabled."Datadog-Client-Dropped-P0-Traces"and"Datadog-Client-Dropped-P0-Spans"request headers are set on the next trace payload.Additionally, adds unit tests and integration tests that test all of the agreed upon cross-tracer tests for the stats computation feature.
Reason for change
Computing stats in the tracer reduces a large amount of span serialization (from the tracer) and span deserialization (from the trace agent), which reduces CPU overhead for users with significant amounts of trace agent traffic. The increased testing also adds confidence that our implementation is on par with the implementations within other tracers (since this is moving a trace agent feature to the individual language tracers).
Implementation details
StatsAggregator initialization now executes Endpoint Discovery
Much like the LiveDebugger initialization, the constructor of
StatsAggregatorwill invoke a task on the threadpool to ping the agent URL at the/infoendpoint to check what endpoints and features it supports. We specifically check for two properties:endpoints[]contains"/v0.6/stats". If true, stats computation is enabled via the new propertyCanComputeStats.client_drop_p0sproperty is set to true. If so, dropping P0 spans is enabled via the new propertyCanDropP0s.Note: Since spans and traces can be finishing while the service discovery is ongoing, we let the StatsAggregator store stats points while the operation is ongoing. If initialization succeeds, the stats aggregrator can start sending stats to the trace agent. If initialization fails, the stats aggregator does not store new stats points and refuses to send stats to the trace agent.
TraceContext changes
If
Tracer.CanDropP0s == true, then the following algorithm is run on each of the finished spans to determine if the span should be kept. If at least one span is kept, then the entire trace is kept. If not, then the entire trace is dropped.We then pass the keep/drop decision along with the finished span array to the
AgentWriter.WriteTraceAPI.Note: There will need to be some special handling if a trace is supposed to be sampled out but the new single span ingestion controls determine that single spans should be kept. This is documented in the RFC and will need to be handled when the feature is implemented in the .NET Tracer.
AgentWriter / IApi changes
When the
AgentWriterreceives spans, it now accumulates the number of dropped P0 traces and dropped P0 spans. Then, when a span buffer needs to be serialized andIApi.SendTracesAsyncis invoked, it passes along these counts and they are added as headers to the trace agent request.Small changes
DiscoveryService.Createmethod was refactored because theIConfigurationSourceparameter was redundant.MockTracerAgenttest class and all of its subclasses have been updated to accept anAgentConfigurationobject which allows us to mock different agent capabilities. This was used to test scenarios where stats computation was allowed but not the dropping of P0 spans.Test coverage
New tests cover those documented by internal RFCs.
Other details
N/A