Skip to content

Commit 82d3351

Browse files
authored
Cache response in order to avoid unnecessary deserializations (#7320)
## Summary of changes Cache agent's response to avoid unnecessary deserializations in case sample rates haven't changed from previous response. ## Reason for change Reduce performance overhead. ## Implementation details ## Test coverage ## Other details This PR implements part of the functionality of this [one](#7251). As commented the rates versioning payload will be handled internally in the native exporter but until it is available the response will be cached in the same way as in the managed exporter. <!-- Fixes #{issue} --> <!-- ⚠️ Note: where possible, please obtain 2 approvals prior to merging. Unless CODEOWNERS specifies otherwise, for external teams it is typically best to have one review from a team member, and one review from apm-dotnet. Trivial changes do not require 2 reviews. -->
1 parent 0bce481 commit 82d3351

File tree

2 files changed

+39
-11
lines changed

2 files changed

+39
-11
lines changed

tracer/src/Datadog.Trace/LibDatadog/TraceExporter.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ internal class TraceExporter : SafeHandle, IApi
2222
{
2323
private readonly IDatadogLogger _log;
2424
private readonly Action<Dictionary<string, float>> _updateSampleRates;
25+
private string _cachedResponse;
2526

2627
public TraceExporter(
2728
TraceExporterConfiguration configuration,
@@ -32,6 +33,7 @@ public TraceExporter(
3233
_updateSampleRates = updateSampleRates;
3334
_log = log ?? DatadogLogging.GetLoggerFor<TraceExporter>();
3435
_log.Debug("Creating new TraceExporter");
36+
_cachedResponse = string.Empty;
3537
using var errPtr = NativeInterop.Exporter.New(out var ptr, configuration);
3638
errPtr.ThrowIfError();
3739
SetHandle(ptr);
@@ -134,6 +136,11 @@ private unsafe void Send(ArraySegment<byte> traces, int numberOfTraces, ref IntP
134136

135137
private unsafe void ProcessResponse(IntPtr response)
136138
{
139+
if (_updateSampleRates is null)
140+
{
141+
return;
142+
}
143+
137144
if (response == IntPtr.Zero)
138145
{
139146
// If response is Null bail out immediately.
@@ -149,8 +156,11 @@ private unsafe void ProcessResponse(IntPtr response)
149156
}
150157

151158
var json = System.Text.Encoding.UTF8.GetString((byte*)body, len);
152-
var apiResponse = JsonConvert.DeserializeObject<ApiResponse>(json);
153-
154-
_updateSampleRates(apiResponse.RateByService);
159+
if (json != _cachedResponse)
160+
{
161+
var apiResponse = JsonConvert.DeserializeObject<ApiResponse>(json);
162+
_updateSampleRates(apiResponse.RateByService);
163+
_cachedResponse = json;
164+
}
155165
}
156166
}

tracer/test/Datadog.Trace.IntegrationTests/LibDatadog/TraceExporterTests.cs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,20 +93,37 @@ public async Task SendsTracesUsingDataPipeline(TestTransports transport)
9393
span.Type = "test";
9494
span.SetMetaStruct("test-meta-struct", metaStructBytes);
9595
}
96+
97+
using (var span2 = tracer.StartSpan("operationName2"))
98+
{
99+
span2.ResourceName = "resourceName2";
100+
span2.Type = "test";
101+
span2.SetMetaStruct("test-meta-struct", metaStructBytes);
102+
}
96103
}
97104

98-
var recordedSpans = await agent.WaitForSpansAsync(1);
99-
recordedSpans.Should().ContainSingle();
105+
var recordedSpans = await agent.WaitForSpansAsync(2);
106+
recordedSpans.Should().HaveCount(2);
100107

101-
var recordedSpan = recordedSpans.Should().ContainSingle().Subject;
102-
recordedSpan.Name.Should().Be("operationName");
103-
recordedSpan.Resource.Should().Be("resourceName");
104-
recordedSpan.Service.Should().Be("default-service");
108+
var firstSpan = recordedSpans.Should()
109+
.Contain(s => s.Name == "operationName")
110+
.Subject;
111+
firstSpan.Resource.Should().Be("resourceName");
112+
firstSpan.Service.Should().Be("default-service");
105113

106-
recordedSpan.MetaStruct.Should().ContainSingle();
107-
var recordedMetaStructBytes = recordedSpan.MetaStruct["test-meta-struct"];
114+
firstSpan.MetaStruct.Should().ContainSingle();
115+
var recordedMetaStructBytes = firstSpan.MetaStruct["test-meta-struct"];
108116
recordedMetaStructBytes.Should().BeEquivalentTo(metaStructBytes);
109117

118+
var secondSpan = recordedSpans.Should()
119+
.Contain(s => s.Name == "operationName2")
120+
.Subject;
121+
secondSpan.Resource.Should().Be("resourceName2");
122+
secondSpan.Service.Should().Be("default-service");
123+
124+
var recordedMetaStructBytes2 = secondSpan.MetaStruct["test-meta-struct"];
125+
recordedMetaStructBytes2.Should().BeEquivalentTo(metaStructBytes);
126+
110127
var expectedRates = new Dictionary<string, float>
111128
{
112129
{ "service:default-service,env:test", 1.0f },
@@ -115,6 +132,7 @@ public async Task SendsTracesUsingDataPipeline(TestTransports transport)
115132
sampleRateResponses.Should()
116133
.NotBeEmpty()
117134
.And.AllSatisfy(rates => rates.Should().BeEquivalentTo(expectedRates));
135+
sampleRateResponses.Should().HaveCount(1);
118136

119137
Dictionary<string, object> GetSettings()
120138
{

0 commit comments

Comments
 (0)