Skip to content

Commit

Permalink
[Dynamic Instrumentation] DEBUG-3223 Add compression support for SymDB (
Browse files Browse the repository at this point in the history
#6427)

## Summary of changes
Add support of SymDB payload compresion using gzip. an option is still
available to disable compression.
  • Loading branch information
dudikeleti authored Dec 17, 2024
1 parent 0b82c1e commit 41d9038
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ internal static class Debugger
/// <seealso cref="DebuggerSettings.SymbolDatabaseUploadEnabled"/>
public const string SymbolDatabaseUploadEnabled = "DD_SYMBOL_DATABASE_UPLOAD_ENABLED";

/// <summary>
/// Configuration key for enabling or disabling compression for symbols payload.
/// Default value is true (enabled).
/// </summary>
/// <seealso cref="DebuggerSettings.SymbolDatabaseUploadEnabled"/>
public const string SymbolDatabaseCompressionEnabled = "DD_SYMBOL_DATABASE_COMPRESSION_ENABLED";

/// <summary>
/// Configuration key for a separated comma list of libraries to include in the 3rd party detection
/// Default value is empty.
Expand Down
4 changes: 4 additions & 0 deletions tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,16 @@ public DebuggerSettings(IConfigurationSource? source, IConfigurationTelemetry te
.WithKeys(ConfigurationKeys.Debugger.CodeOriginMaxUserFrames)
.AsInt32(DefaultCodeOriginExitSpanFrames, frames => frames > 0)
.Value;

SymbolDatabaseCompressionEnabled = config.WithKeys(ConfigurationKeys.Debugger.SymbolDatabaseCompressionEnabled).AsBool(true);
}

public bool Enabled { get; }

public bool SymbolDatabaseUploadEnabled { get; }

public bool SymbolDatabaseCompressionEnabled { get; }

public int MaxSerializationTimeInMilliseconds { get; }

public int MaximumDepthOfMembersToCopy { get; }
Expand Down
2 changes: 1 addition & 1 deletion tracer/src/Datadog.Trace/Debugger/LiveDebuggerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ private static DiagnosticsUploader CreateDiagnosticsUploader(IDiscoveryService d

private static IDebuggerUploader CreateSymbolsUploader(IDiscoveryService discoveryService, IRcmSubscriptionManager remoteConfigurationManager, TracerSettings tracerSettings, string serviceName, DebuggerSettings settings, IGitMetadataTagsProvider gitMetadataTagsProvider)
{
var symbolBatchApi = DebuggerUploadApiFactory.CreateSymbolsUploadApi(GetApiFactory(tracerSettings, true), discoveryService, gitMetadataTagsProvider, serviceName);
var symbolBatchApi = DebuggerUploadApiFactory.CreateSymbolsUploadApi(GetApiFactory(tracerSettings, true), discoveryService, gitMetadataTagsProvider, serviceName, settings.SymbolDatabaseCompressionEnabled);
var symbolsUploader = SymbolsUploader.Create(symbolBatchApi, discoveryService, remoteConfigurationManager, settings, tracerSettings, serviceName);
return symbolsUploader;
}
Expand Down
71 changes: 47 additions & 24 deletions tracer/src/Datadog.Trace/Debugger/Symbols/SymbolsUploader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
using Datadog.Trace.Debugger.Sink;
using Datadog.Trace.Debugger.Symbols.Model;
using Datadog.Trace.Debugger.Upload;
using Datadog.Trace.ExtensionMethods;
using Datadog.Trace.Logging;
using Datadog.Trace.RemoteConfigurationManagement;
using Datadog.Trace.Util;
Expand Down Expand Up @@ -230,8 +229,10 @@ private async Task UploadAssemblySymbols(Assembly assembly)

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

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

accumulatedBytes += SerializeClass(classSymbol.Value, builder);
if (accumulatedBytes < _thresholdInBytes)
// Try to serialize and append the class
if (!TrySerializeClass(classSymbol.Value, builder, accumulatedBytes, out var newByteCount))
{
continue;
// If we couldn't append because it would exceed capacity,
// upload current batch first
bool succeeded = false;
if (builder.Length > 0)
{
await Upload(rootAsString, builder).ConfigureAwait(false);
builder.Clear();
accumulatedBytes = 0;
// Try again with empty builder
succeeded = TrySerializeClass(classSymbol.Value, builder, accumulatedBytes, out newByteCount);
}

if (!succeeded)
{
// If it still doesn't fit, this single class is too large
Log.Warning("Class {Name} exceeds maximum capacity", classSymbol.Value.Name);
continue;
}
}

await Upload(root, builder).ConfigureAwait(false);
builder.Clear();
accumulatedBytes = 0;
accumulatedBytes = newByteCount;
}

if (accumulatedBytes > 0)
// Upload any remaining data
if (builder.Length > 0)
{
await Upload(root, builder).ConfigureAwait(false);
await Upload(rootAsString, builder).ConfigureAwait(false);
}
}
finally
Expand All @@ -267,9 +284,9 @@ private async Task UploadClasses(Root root, IEnumerable<Model.Scope?> classes)
}
}

private async Task Upload(Root root, StringBuilder builder)
private async Task Upload(string rootAsString, StringBuilder builder)
{
FinalizeSymbolForSend(root, builder);
FinalizeSymbolForSend(rootAsString, builder);
await SendSymbol(builder.ToString()).ConfigureAwait(false);
ResetPayload();
}
Expand All @@ -294,33 +311,39 @@ private async Task<bool> SendSymbol(string symbol)
return await _api.SendBatchAsync(new ArraySegment<byte>(_payload)).ConfigureAwait(false);
}

private int SerializeClass(Model.Scope classScope, StringBuilder sb)
private bool TrySerializeClass(Model.Scope classScope, StringBuilder sb, int currentBytes, out int newTotalBytes)
{
if (sb.Length != 0)
// Calculate the serialized string first
var symbolAsString = JsonConvert.SerializeObject(classScope, _jsonSerializerSettings);
var classBytes = Encoding.UTF8.GetByteCount(symbolAsString);

newTotalBytes = currentBytes;
if (sb.Length > 0)
{
sb.Append(',');
classBytes += 1; // for comma
}

var symbolAsString = JsonConvert.SerializeObject(classScope, _jsonSerializerSettings);
newTotalBytes += classBytes;

try
if (newTotalBytes > _thresholdInBytes)
{
sb.Append(symbolAsString);
return false;
}
catch (ArgumentOutOfRangeException)

// Safe to append
if (sb.Length > 0)
{
return 0;
sb.Append(',');
}

return Encoding.UTF8.GetByteCount(symbolAsString);
sb.Append(symbolAsString);
return true;
}

private void FinalizeSymbolForSend(Root root, StringBuilder sb)
private void FinalizeSymbolForSend(string rootAsString, StringBuilder sb)
{
const string classScopeString = "\"scopes\":null";

var rootAsString = JsonConvert.SerializeObject(root);

var classesIndex = rootAsString.IndexOf(classScopeString, StringComparison.Ordinal);

sb.Insert(0, rootAsString.Substring(0, classesIndex + classScopeString.Length - "null".Length) + "[");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ internal static IBatchUploadApi CreateDiagnosticsUploadApi(IApiRequestFactory ap
return DiagnosticsUploadApi.Create(apiRequestFactory, discoveryService, gitMetadataTagsProvider);
}

internal static IBatchUploadApi CreateSymbolsUploadApi(IApiRequestFactory apiRequestFactory, IDiscoveryService discoveryService, IGitMetadataTagsProvider gitMetadataTagsProvider, string serviceName)
internal static IBatchUploadApi CreateSymbolsUploadApi(IApiRequestFactory apiRequestFactory, IDiscoveryService discoveryService, IGitMetadataTagsProvider gitMetadataTagsProvider, string serviceName, bool enableCompression)
{
return SymbolUploadApi.Create(apiRequestFactory, discoveryService, gitMetadataTagsProvider, serviceName);
return SymbolUploadApi.Create(apiRequestFactory, discoveryService, gitMetadataTagsProvider, serviceName, enableCompression);
}
}
}
41 changes: 33 additions & 8 deletions tracer/src/Datadog.Trace/Debugger/Upload/SymbolUploadApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#nullable enable
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Threading.Tasks;
using Datadog.Trace.Agent;
Expand All @@ -24,25 +26,28 @@ internal class SymbolUploadApi : DebuggerUploadApiBase

private readonly IApiRequestFactory _apiRequestFactory;
private readonly ArraySegment<byte> _eventMetadata;
private readonly bool _enableCompression;

private SymbolUploadApi(
IApiRequestFactory apiRequestFactory,
IDiscoveryService discoveryService,
IGitMetadataTagsProvider gitMetadataTagsProvider,
ArraySegment<byte> eventMetadata)
ArraySegment<byte> eventMetadata,
bool enableCompression)
: base(apiRequestFactory, gitMetadataTagsProvider)
{
_apiRequestFactory = apiRequestFactory;
_eventMetadata = eventMetadata;

_enableCompression = enableCompression;
discoveryService.SubscribeToChanges(c => Endpoint = c.SymbolDbEndpoint);
}

public static IBatchUploadApi Create(
IApiRequestFactory apiRequestFactory,
IDiscoveryService discoveryService,
IGitMetadataTagsProvider gitMetadataTagsProvider,
string serviceName)
string serviceName,
bool enableCompression)
{
ArraySegment<byte> GetEventMetadataAsArraySegment()
{
Expand All @@ -55,11 +60,16 @@ ArraySegment<byte> GetEventMetadataAsArraySegment()
}

var eventMetadata = GetEventMetadataAsArraySegment();
return new SymbolUploadApi(apiRequestFactory, discoveryService, gitMetadataTagsProvider, eventMetadata);
return new SymbolUploadApi(apiRequestFactory, discoveryService, gitMetadataTagsProvider, eventMetadata, enableCompression);
}

public override async Task<bool> SendBatchAsync(ArraySegment<byte> symbols)
{
if (symbols.Array == null || symbols.Count == 0)
{
return false;
}

var uri = BuildUri();
if (string.IsNullOrEmpty(uri))
{
Expand All @@ -72,11 +82,26 @@ public override async Task<bool> SendBatchAsync(ArraySegment<byte> symbols)
var retries = 0;
var sleepDuration = StartingSleepDuration;

var items = new MultipartFormItem[]
MultipartFormItem symbolsItem;

if (_enableCompression)
{
using var memoryStream = new MemoryStream();
#if NETFRAMEWORK
using var gzipStream = new Vendors.ICSharpCode.SharpZipLib.GZip.GZipOutputStream(memoryStream);
await gzipStream.WriteAsync(symbols.Array, 0, symbols.Array.Length).ConfigureAwait(false);
#else
using var gzipStream = new GZipStream(memoryStream, CompressionMode.Compress);
await gzipStream.WriteAsync(symbols.Array, 0, symbols.Array.Length).ConfigureAwait(false);
#endif
symbolsItem = new MultipartFormItem("file", "gzip", "file.gz", new ArraySegment<byte>(memoryStream.ToArray()));
}
else
{
new("file", MimeTypes.Json, "file.json", symbols),
new("event", MimeTypes.Json, "event.json", _eventMetadata)
};
symbolsItem = new MultipartFormItem("file", MimeTypes.Json, "file.json", symbols);
}

var items = new[] { symbolsItem, new MultipartFormItem("event", MimeTypes.Json, "event.json", _eventMetadata) };

while (retries < MaxRetries)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public void DebuggerSettings_UseSettings()
NullConfigurationTelemetry.Instance);

settings.Enabled.Should().BeTrue();
settings.SymbolDatabaseCompressionEnabled.Should().BeTrue();
settings.SymbolDatabaseUploadEnabled.Should().BeTrue();
settings.MaximumDepthOfMembersToCopy.Should().Be(100);
settings.MaxSerializationTimeInMilliseconds.Should().Be(1000);
Expand Down
13 changes: 7 additions & 6 deletions tracer/test/Datadog.Trace.Tests/Telemetry/config_norm_rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"tracer_instance_count": "trace_instance_count",
"trace.db.client.split-by-instance": "trace_db_client_split_by_instance",
"trace.db.client.split-by-instance.type.suffix": "trace_db_client_split_by_instance_type_suffix",
"trace.http.client.split-by-domain" : "trace_http_client_split_by_domain",
"trace.http.client.split-by-domain": "trace_http_client_split_by_domain",
"trace.agent.timeout": "trace_agent_timeout",
"trace.header.tags.legacy.parsing.enabled": "trace_header_tags_legacy_parsing_enabled",
"trace.client-ip.resolver.enabled": "trace_client_ip_resolver_enabled",
Expand Down Expand Up @@ -456,11 +456,11 @@
"DD_APPSEC_SCA_ENABLED": "appsec_sca_enabled",
"DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE": "appsec_auto_user_instrumentation_mode",
"DD_EXPERIMENTAL_APPSEC_USE_UNSAFE_ENCODER": "appsec_use_unsafe_encoder",
"DD_API_SECURITY_REQUEST_SAMPLE_RATE":"api_security_request_sample_rate",
"DD_API_SECURITY_MAX_CONCURRENT_REQUESTS":"api_security_max_concurrent_requests",
"DD_API_SECURITY_SAMPLE_DELAY":"api_security_sample_delay",
"DD_API_SECURITY_ENABLED":"api_security_enabled",
"DD_EXPERIMENTAL_API_SECURITY_ENABLED":"experimental_api_security_enabled",
"DD_API_SECURITY_REQUEST_SAMPLE_RATE": "api_security_request_sample_rate",
"DD_API_SECURITY_MAX_CONCURRENT_REQUESTS": "api_security_max_concurrent_requests",
"DD_API_SECURITY_SAMPLE_DELAY": "api_security_sample_delay",
"DD_API_SECURITY_ENABLED": "api_security_enabled",
"DD_EXPERIMENTAL_API_SECURITY_ENABLED": "experimental_api_security_enabled",
"DD_APPSEC_WAF_DEBUG": "appsec_waf_debug_enabled",
"DD_AZURE_APP_SERVICES": "aas_enabled",
"DD_AAS_DOTNET_EXTENSION_VERSION": "aas_site_extensions_version",
Expand Down Expand Up @@ -501,6 +501,7 @@
"DD_THIRD_PARTY_DETECTION_EXCLUDES": "third_party_detection_excludes",
"DD_SYMBOL_DATABASE_THIRD_PARTY_DETECTION_INCLUDES": "symbol_database_third_party_detection_includes",
"DD_SYMBOL_DATABASE_THIRD_PARTY_DETECTION_EXCLUDES": "symbol_database_third_party_detection_excludes",
"DD_SYMBOL_DATABASE_COMPRESSION_ENABLED": "symbol_database_compression_enabled",
"DD_CODE_ORIGIN_FOR_SPANS_ENABLED": "code_origin_for_spans_enabled",
"DD_CODE_ORIGIN_FOR_SPANS_MAX_USER_FRAMES": "code_origin_for_spans_max_user_frames",
"DD_LOGS_DIRECT_SUBMISSION_INTEGRATIONS": "logs_direct_submission_integrations",
Expand Down

0 comments on commit 41d9038

Please sign in to comment.