Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dynamic Instrumentation] DEBUG-3223 Add compression support for SymDB #6427

Merged
merged 5 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()));
dudikeleti marked this conversation as resolved.
Show resolved Hide resolved
}
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
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",
dudikeleti marked this conversation as resolved.
Show resolved Hide resolved
"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
Loading