Skip to content

Commit 41d9038

Browse files
authored
[Dynamic Instrumentation] DEBUG-3223 Add compression support for SymDB (#6427)
## Summary of changes Add support of SymDB payload compresion using gzip. an option is still available to disable compression.
1 parent 0b82c1e commit 41d9038

File tree

8 files changed

+102
-41
lines changed

8 files changed

+102
-41
lines changed

tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.Debugger.cs

+7
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ internal static class Debugger
5555
/// <seealso cref="DebuggerSettings.SymbolDatabaseUploadEnabled"/>
5656
public const string SymbolDatabaseUploadEnabled = "DD_SYMBOL_DATABASE_UPLOAD_ENABLED";
5757

58+
/// <summary>
59+
/// Configuration key for enabling or disabling compression for symbols payload.
60+
/// Default value is true (enabled).
61+
/// </summary>
62+
/// <seealso cref="DebuggerSettings.SymbolDatabaseUploadEnabled"/>
63+
public const string SymbolDatabaseCompressionEnabled = "DD_SYMBOL_DATABASE_COMPRESSION_ENABLED";
64+
5865
/// <summary>
5966
/// Configuration key for a separated comma list of libraries to include in the 3rd party detection
6067
/// Default value is empty.

tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs

+4
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,16 @@ public DebuggerSettings(IConfigurationSource? source, IConfigurationTelemetry te
123123
.WithKeys(ConfigurationKeys.Debugger.CodeOriginMaxUserFrames)
124124
.AsInt32(DefaultCodeOriginExitSpanFrames, frames => frames > 0)
125125
.Value;
126+
127+
SymbolDatabaseCompressionEnabled = config.WithKeys(ConfigurationKeys.Debugger.SymbolDatabaseCompressionEnabled).AsBool(true);
126128
}
127129

128130
public bool Enabled { get; }
129131

130132
public bool SymbolDatabaseUploadEnabled { get; }
131133

134+
public bool SymbolDatabaseCompressionEnabled { get; }
135+
132136
public int MaxSerializationTimeInMilliseconds { get; }
133137

134138
public int MaximumDepthOfMembersToCopy { get; }

tracer/src/Datadog.Trace/Debugger/LiveDebuggerFactory.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ private static DiagnosticsUploader CreateDiagnosticsUploader(IDiscoveryService d
105105

106106
private static IDebuggerUploader CreateSymbolsUploader(IDiscoveryService discoveryService, IRcmSubscriptionManager remoteConfigurationManager, TracerSettings tracerSettings, string serviceName, DebuggerSettings settings, IGitMetadataTagsProvider gitMetadataTagsProvider)
107107
{
108-
var symbolBatchApi = DebuggerUploadApiFactory.CreateSymbolsUploadApi(GetApiFactory(tracerSettings, true), discoveryService, gitMetadataTagsProvider, serviceName);
108+
var symbolBatchApi = DebuggerUploadApiFactory.CreateSymbolsUploadApi(GetApiFactory(tracerSettings, true), discoveryService, gitMetadataTagsProvider, serviceName, settings.SymbolDatabaseCompressionEnabled);
109109
var symbolsUploader = SymbolsUploader.Create(symbolBatchApi, discoveryService, remoteConfigurationManager, settings, tracerSettings, serviceName);
110110
return symbolsUploader;
111111
}

tracer/src/Datadog.Trace/Debugger/Symbols/SymbolsUploader.cs

+47-24
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
using Datadog.Trace.Debugger.Sink;
1919
using Datadog.Trace.Debugger.Symbols.Model;
2020
using Datadog.Trace.Debugger.Upload;
21-
using Datadog.Trace.ExtensionMethods;
2221
using Datadog.Trace.Logging;
2322
using Datadog.Trace.RemoteConfigurationManagement;
2423
using Datadog.Trace.Util;
@@ -230,8 +229,10 @@ private async Task UploadAssemblySymbols(Assembly assembly)
230229

231230
private async Task UploadClasses(Root root, IEnumerable<Model.Scope?> classes)
232231
{
232+
var rootAsString = JsonConvert.SerializeObject(root);
233+
var rootBytes = Encoding.UTF8.GetByteCount(rootAsString);
234+
var builder = StringBuilderCache.Acquire((int)_thresholdInBytes + rootBytes + 4);
233235
var accumulatedBytes = 0;
234-
var builder = StringBuilderCache.Acquire((int)_thresholdInBytes);
235236

236237
try
237238
{
@@ -242,20 +243,36 @@ private async Task UploadClasses(Root root, IEnumerable<Model.Scope?> classes)
242243
continue;
243244
}
244245

245-
accumulatedBytes += SerializeClass(classSymbol.Value, builder);
246-
if (accumulatedBytes < _thresholdInBytes)
246+
// Try to serialize and append the class
247+
if (!TrySerializeClass(classSymbol.Value, builder, accumulatedBytes, out var newByteCount))
247248
{
248-
continue;
249+
// If we couldn't append because it would exceed capacity,
250+
// upload current batch first
251+
bool succeeded = false;
252+
if (builder.Length > 0)
253+
{
254+
await Upload(rootAsString, builder).ConfigureAwait(false);
255+
builder.Clear();
256+
accumulatedBytes = 0;
257+
// Try again with empty builder
258+
succeeded = TrySerializeClass(classSymbol.Value, builder, accumulatedBytes, out newByteCount);
259+
}
260+
261+
if (!succeeded)
262+
{
263+
// If it still doesn't fit, this single class is too large
264+
Log.Warning("Class {Name} exceeds maximum capacity", classSymbol.Value.Name);
265+
continue;
266+
}
249267
}
250268

251-
await Upload(root, builder).ConfigureAwait(false);
252-
builder.Clear();
253-
accumulatedBytes = 0;
269+
accumulatedBytes = newByteCount;
254270
}
255271

256-
if (accumulatedBytes > 0)
272+
// Upload any remaining data
273+
if (builder.Length > 0)
257274
{
258-
await Upload(root, builder).ConfigureAwait(false);
275+
await Upload(rootAsString, builder).ConfigureAwait(false);
259276
}
260277
}
261278
finally
@@ -267,9 +284,9 @@ private async Task UploadClasses(Root root, IEnumerable<Model.Scope?> classes)
267284
}
268285
}
269286

270-
private async Task Upload(Root root, StringBuilder builder)
287+
private async Task Upload(string rootAsString, StringBuilder builder)
271288
{
272-
FinalizeSymbolForSend(root, builder);
289+
FinalizeSymbolForSend(rootAsString, builder);
273290
await SendSymbol(builder.ToString()).ConfigureAwait(false);
274291
ResetPayload();
275292
}
@@ -294,33 +311,39 @@ private async Task<bool> SendSymbol(string symbol)
294311
return await _api.SendBatchAsync(new ArraySegment<byte>(_payload)).ConfigureAwait(false);
295312
}
296313

297-
private int SerializeClass(Model.Scope classScope, StringBuilder sb)
314+
private bool TrySerializeClass(Model.Scope classScope, StringBuilder sb, int currentBytes, out int newTotalBytes)
298315
{
299-
if (sb.Length != 0)
316+
// Calculate the serialized string first
317+
var symbolAsString = JsonConvert.SerializeObject(classScope, _jsonSerializerSettings);
318+
var classBytes = Encoding.UTF8.GetByteCount(symbolAsString);
319+
320+
newTotalBytes = currentBytes;
321+
if (sb.Length > 0)
300322
{
301-
sb.Append(',');
323+
classBytes += 1; // for comma
302324
}
303325

304-
var symbolAsString = JsonConvert.SerializeObject(classScope, _jsonSerializerSettings);
326+
newTotalBytes += classBytes;
305327

306-
try
328+
if (newTotalBytes > _thresholdInBytes)
307329
{
308-
sb.Append(symbolAsString);
330+
return false;
309331
}
310-
catch (ArgumentOutOfRangeException)
332+
333+
// Safe to append
334+
if (sb.Length > 0)
311335
{
312-
return 0;
336+
sb.Append(',');
313337
}
314338

315-
return Encoding.UTF8.GetByteCount(symbolAsString);
339+
sb.Append(symbolAsString);
340+
return true;
316341
}
317342

318-
private void FinalizeSymbolForSend(Root root, StringBuilder sb)
343+
private void FinalizeSymbolForSend(string rootAsString, StringBuilder sb)
319344
{
320345
const string classScopeString = "\"scopes\":null";
321346

322-
var rootAsString = JsonConvert.SerializeObject(root);
323-
324347
var classesIndex = rootAsString.IndexOf(classScopeString, StringComparison.Ordinal);
325348

326349
sb.Insert(0, rootAsString.Substring(0, classesIndex + classScopeString.Length - "null".Length) + "[");

tracer/src/Datadog.Trace/Debugger/Upload/DebuggerUploadApiFactory.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ internal static IBatchUploadApi CreateDiagnosticsUploadApi(IApiRequestFactory ap
2222
return DiagnosticsUploadApi.Create(apiRequestFactory, discoveryService, gitMetadataTagsProvider);
2323
}
2424

25-
internal static IBatchUploadApi CreateSymbolsUploadApi(IApiRequestFactory apiRequestFactory, IDiscoveryService discoveryService, IGitMetadataTagsProvider gitMetadataTagsProvider, string serviceName)
25+
internal static IBatchUploadApi CreateSymbolsUploadApi(IApiRequestFactory apiRequestFactory, IDiscoveryService discoveryService, IGitMetadataTagsProvider gitMetadataTagsProvider, string serviceName, bool enableCompression)
2626
{
27-
return SymbolUploadApi.Create(apiRequestFactory, discoveryService, gitMetadataTagsProvider, serviceName);
27+
return SymbolUploadApi.Create(apiRequestFactory, discoveryService, gitMetadataTagsProvider, serviceName, enableCompression);
2828
}
2929
}
3030
}

tracer/src/Datadog.Trace/Debugger/Upload/SymbolUploadApi.cs

+33-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
#nullable enable
77
using System;
8+
using System.IO;
9+
using System.IO.Compression;
810
using System.Text;
911
using System.Threading.Tasks;
1012
using Datadog.Trace.Agent;
@@ -24,25 +26,28 @@ internal class SymbolUploadApi : DebuggerUploadApiBase
2426

2527
private readonly IApiRequestFactory _apiRequestFactory;
2628
private readonly ArraySegment<byte> _eventMetadata;
29+
private readonly bool _enableCompression;
2730

2831
private SymbolUploadApi(
2932
IApiRequestFactory apiRequestFactory,
3033
IDiscoveryService discoveryService,
3134
IGitMetadataTagsProvider gitMetadataTagsProvider,
32-
ArraySegment<byte> eventMetadata)
35+
ArraySegment<byte> eventMetadata,
36+
bool enableCompression)
3337
: base(apiRequestFactory, gitMetadataTagsProvider)
3438
{
3539
_apiRequestFactory = apiRequestFactory;
3640
_eventMetadata = eventMetadata;
37-
41+
_enableCompression = enableCompression;
3842
discoveryService.SubscribeToChanges(c => Endpoint = c.SymbolDbEndpoint);
3943
}
4044

4145
public static IBatchUploadApi Create(
4246
IApiRequestFactory apiRequestFactory,
4347
IDiscoveryService discoveryService,
4448
IGitMetadataTagsProvider gitMetadataTagsProvider,
45-
string serviceName)
49+
string serviceName,
50+
bool enableCompression)
4651
{
4752
ArraySegment<byte> GetEventMetadataAsArraySegment()
4853
{
@@ -55,11 +60,16 @@ ArraySegment<byte> GetEventMetadataAsArraySegment()
5560
}
5661

5762
var eventMetadata = GetEventMetadataAsArraySegment();
58-
return new SymbolUploadApi(apiRequestFactory, discoveryService, gitMetadataTagsProvider, eventMetadata);
63+
return new SymbolUploadApi(apiRequestFactory, discoveryService, gitMetadataTagsProvider, eventMetadata, enableCompression);
5964
}
6065

6166
public override async Task<bool> SendBatchAsync(ArraySegment<byte> symbols)
6267
{
68+
if (symbols.Array == null || symbols.Count == 0)
69+
{
70+
return false;
71+
}
72+
6373
var uri = BuildUri();
6474
if (string.IsNullOrEmpty(uri))
6575
{
@@ -72,11 +82,26 @@ public override async Task<bool> SendBatchAsync(ArraySegment<byte> symbols)
7282
var retries = 0;
7383
var sleepDuration = StartingSleepDuration;
7484

75-
var items = new MultipartFormItem[]
85+
MultipartFormItem symbolsItem;
86+
87+
if (_enableCompression)
88+
{
89+
using var memoryStream = new MemoryStream();
90+
#if NETFRAMEWORK
91+
using var gzipStream = new Vendors.ICSharpCode.SharpZipLib.GZip.GZipOutputStream(memoryStream);
92+
await gzipStream.WriteAsync(symbols.Array, 0, symbols.Array.Length).ConfigureAwait(false);
93+
#else
94+
using var gzipStream = new GZipStream(memoryStream, CompressionMode.Compress);
95+
await gzipStream.WriteAsync(symbols.Array, 0, symbols.Array.Length).ConfigureAwait(false);
96+
#endif
97+
symbolsItem = new MultipartFormItem("file", "gzip", "file.gz", new ArraySegment<byte>(memoryStream.ToArray()));
98+
}
99+
else
76100
{
77-
new("file", MimeTypes.Json, "file.json", symbols),
78-
new("event", MimeTypes.Json, "event.json", _eventMetadata)
79-
};
101+
symbolsItem = new MultipartFormItem("file", MimeTypes.Json, "file.json", symbols);
102+
}
103+
104+
var items = new[] { symbolsItem, new MultipartFormItem("event", MimeTypes.Json, "event.json", _eventMetadata) };
80105

81106
while (retries < MaxRetries)
82107
{

tracer/test/Datadog.Trace.Tests/Debugger/DebuggerSettingsTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public void DebuggerSettings_UseSettings()
8181
NullConfigurationTelemetry.Instance);
8282

8383
settings.Enabled.Should().BeTrue();
84+
settings.SymbolDatabaseCompressionEnabled.Should().BeTrue();
8485
settings.SymbolDatabaseUploadEnabled.Should().BeTrue();
8586
settings.MaximumDepthOfMembersToCopy.Should().Be(100);
8687
settings.MaxSerializationTimeInMilliseconds.Should().Be(1000);

tracer/test/Datadog.Trace.Tests/Telemetry/config_norm_rules.json

+7-6
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"tracer_instance_count": "trace_instance_count",
7878
"trace.db.client.split-by-instance": "trace_db_client_split_by_instance",
7979
"trace.db.client.split-by-instance.type.suffix": "trace_db_client_split_by_instance_type_suffix",
80-
"trace.http.client.split-by-domain" : "trace_http_client_split_by_domain",
80+
"trace.http.client.split-by-domain": "trace_http_client_split_by_domain",
8181
"trace.agent.timeout": "trace_agent_timeout",
8282
"trace.header.tags.legacy.parsing.enabled": "trace_header_tags_legacy_parsing_enabled",
8383
"trace.client-ip.resolver.enabled": "trace_client_ip_resolver_enabled",
@@ -456,11 +456,11 @@
456456
"DD_APPSEC_SCA_ENABLED": "appsec_sca_enabled",
457457
"DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE": "appsec_auto_user_instrumentation_mode",
458458
"DD_EXPERIMENTAL_APPSEC_USE_UNSAFE_ENCODER": "appsec_use_unsafe_encoder",
459-
"DD_API_SECURITY_REQUEST_SAMPLE_RATE":"api_security_request_sample_rate",
460-
"DD_API_SECURITY_MAX_CONCURRENT_REQUESTS":"api_security_max_concurrent_requests",
461-
"DD_API_SECURITY_SAMPLE_DELAY":"api_security_sample_delay",
462-
"DD_API_SECURITY_ENABLED":"api_security_enabled",
463-
"DD_EXPERIMENTAL_API_SECURITY_ENABLED":"experimental_api_security_enabled",
459+
"DD_API_SECURITY_REQUEST_SAMPLE_RATE": "api_security_request_sample_rate",
460+
"DD_API_SECURITY_MAX_CONCURRENT_REQUESTS": "api_security_max_concurrent_requests",
461+
"DD_API_SECURITY_SAMPLE_DELAY": "api_security_sample_delay",
462+
"DD_API_SECURITY_ENABLED": "api_security_enabled",
463+
"DD_EXPERIMENTAL_API_SECURITY_ENABLED": "experimental_api_security_enabled",
464464
"DD_APPSEC_WAF_DEBUG": "appsec_waf_debug_enabled",
465465
"DD_AZURE_APP_SERVICES": "aas_enabled",
466466
"DD_AAS_DOTNET_EXTENSION_VERSION": "aas_site_extensions_version",
@@ -501,6 +501,7 @@
501501
"DD_THIRD_PARTY_DETECTION_EXCLUDES": "third_party_detection_excludes",
502502
"DD_SYMBOL_DATABASE_THIRD_PARTY_DETECTION_INCLUDES": "symbol_database_third_party_detection_includes",
503503
"DD_SYMBOL_DATABASE_THIRD_PARTY_DETECTION_EXCLUDES": "symbol_database_third_party_detection_excludes",
504+
"DD_SYMBOL_DATABASE_COMPRESSION_ENABLED": "symbol_database_compression_enabled",
504505
"DD_CODE_ORIGIN_FOR_SPANS_ENABLED": "code_origin_for_spans_enabled",
505506
"DD_CODE_ORIGIN_FOR_SPANS_MAX_USER_FRAMES": "code_origin_for_spans_max_user_frames",
506507
"DD_LOGS_DIRECT_SUBMISSION_INTEGRATIONS": "logs_direct_submission_integrations",

0 commit comments

Comments
 (0)