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-3076 Code origin for exit spans #6216

Merged
merged 16 commits into from
Nov 7, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ internal static class Debugger
public const string ThirdPartyDetectionIncludes = "DD_THIRD_PARTY_DETECTION_INCLUDES";

/// <summary>
/// Configuration key for a separated comma list of libraries to include in the 3rd party detection
/// Configuration key for a separated comma list of libraries to exclude for the 3rd party detection
/// Default value is empty.
/// </summary>
public const string ThirdPartyDetectionExcludes = "DD_THIRD_PARTY_DETECTION_EXCLUDES";
Expand Down Expand Up @@ -146,6 +146,20 @@ internal static class Debugger
/// </summary>
/// <seealso cref="ExceptionReplaySettings.MaxExceptionAnalysisLimit"/>
public const string MaxExceptionAnalysisLimit = "DD_EXCEPTION_REPLAY_MAX_EXCEPTION_ANALYSIS_LIMIT";

/// <summary>
/// Configuration key to enable tag code origin for span.
/// Default value is false.
/// </summary>
/// <seealso cref="DebuggerSettings.CodeOriginForSpansEnabled"/>
public const string CodeOriginForSpansEnabled = "DD_CODE_ORIGIN_FOR_SPANS_ENABLED";

/// <summary>
/// Configuration key for setting the number of frames to be tagged in exit span code origin.
/// Default value is <c>8</c>.
/// </summary>
/// <seealso cref="DebuggerSettings.CodeOriginMaxUserFrames"/>
public const string CodeOriginMaxUserFrames = "DD_CODE_ORIGIN_FOR_SPANS_MAX_USER_FRAMES";
dudikeleti marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
16 changes: 14 additions & 2 deletions tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ internal class DebuggerSettings
public const int DefaultSymbolBatchSizeInBytes = 100000;
private const int DefaultDiagnosticsIntervalSeconds = 60 * 60; // 1 hour
private const int DefaultUploadFlushIntervalMilliseconds = 0;
public const int DefaultCodeOriginExitSpanFrames = 8;

public DebuggerSettings(IConfigurationSource? source, IConfigurationTelemetry telemetry)
{
Expand Down Expand Up @@ -80,15 +81,15 @@ public DebuggerSettings(IConfigurationSource? source, IConfigurationTelemetry te
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) ??
Enumerable.Empty<string>();

SymDbThirdPartyDetectionIncludes = new HashSet<string>([..symDb3rdPartyIncludeLibraries, ..ThirdPartyDetectionIncludes]).ToImmutableHashSet();
SymDbThirdPartyDetectionIncludes = new HashSet<string>([.. symDb3rdPartyIncludeLibraries, .. ThirdPartyDetectionIncludes]).ToImmutableHashSet();

var symDb3rdPartyExcludeLibraries = config
.WithKeys(ConfigurationKeys.Debugger.SymDbThirdPartyDetectionExcludes)
.AsString()?
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) ??
Enumerable.Empty<string>();

SymDbThirdPartyDetectionExcludes = new HashSet<string>([..symDb3rdPartyExcludeLibraries, ..ThirdPartyDetectionExcludes]).ToImmutableHashSet();
SymDbThirdPartyDetectionExcludes = new HashSet<string>([.. symDb3rdPartyExcludeLibraries, .. ThirdPartyDetectionExcludes]).ToImmutableHashSet();

DiagnosticsIntervalSeconds = config
.WithKeys(ConfigurationKeys.Debugger.DiagnosticsInterval)
Expand All @@ -115,6 +116,13 @@ public DebuggerSettings(IConfigurationSource? source, IConfigurationTelemetry te
Enumerable.Empty<string>();

RedactedTypes = new HashSet<string>(redactedTypes, StringComparer.OrdinalIgnoreCase);

CodeOriginForSpansEnabled = config.WithKeys(ConfigurationKeys.Debugger.CodeOriginForSpansEnabled).AsBool(false);

CodeOriginMaxUserFrames = config
.WithKeys(ConfigurationKeys.Debugger.CodeOriginMaxUserFrames)
.AsInt32(DefaultCodeOriginExitSpanFrames, frames => frames > 0)
.Value;
}

public bool Enabled { get; }
Expand Down Expand Up @@ -145,6 +153,10 @@ public DebuggerSettings(IConfigurationSource? source, IConfigurationTelemetry te

public HashSet<string> RedactedTypes { get; }

public bool CodeOriginForSpansEnabled { get; }

public int CodeOriginMaxUserFrames { get; }

public static DebuggerSettings FromSource(IConfigurationSource source, IConfigurationTelemetry telemetry)
{
return new DebuggerSettings(source, telemetry);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ private ExpressionEvaluationResult Evaluate(DebuggerSnapshotCreator snapshotCrea
return evaluationResult;
}

CheckSpanDecoration(snapshotCreator, ref shouldStopCapture, evaluationResult);
SetSpanDecoration(snapshotCreator, ref shouldStopCapture, evaluationResult);

if (evaluationResult.Metric.HasValue)
{
Expand Down Expand Up @@ -370,7 +370,7 @@ private ExpressionEvaluationResult Evaluate(DebuggerSnapshotCreator snapshotCrea
return evaluationResult;
}

private void CheckSpanDecoration(DebuggerSnapshotCreator snapshotCreator, ref bool shouldStopCapture, ExpressionEvaluationResult evaluationResult)
private void SetSpanDecoration(DebuggerSnapshotCreator snapshotCreator, ref bool shouldStopCapture, ExpressionEvaluationResult evaluationResult)
{
if (evaluationResult.Decorations == null)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// <copyright file="SpanCodeOriginManager.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
#nullable enable

using System;
using System.Diagnostics;
using Datadog.Trace.Debugger.Symbols;
using Datadog.Trace.Logging;
using Datadog.Trace.VendoredMicrosoftCode.System.Buffers;

namespace Datadog.Trace.Debugger.SpanCodeOrigin
{
internal class SpanCodeOriginManager
{
private const string CodeOriginTag = "_dd.code_origin";

private const string FramesPrefix = "frames";

private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(SpanCodeOriginManager));

private readonly DebuggerSettings _settings = LiveDebugger.Instance?.Settings ?? DebuggerSettings.FromDefaultSource();

internal static SpanCodeOriginManager Instance { get; } = new();

internal void SetCodeOrigin(Span? span)
{
if (span == null || !_settings.CodeOriginForSpansEnabled)
{
return;
}

AddExitSpanTag(span);
}

private void AddExitSpanTag(Span span)
{
var frames = ArrayPool<FrameInfo>.Shared.Rent(_settings.CodeOriginMaxUserFrames);
try
{
var framesLength = PopulateUserFrames(frames);
if (framesLength == 0)
{
Log.Warning("No user frames were found");
return;
}

span.Tags.SetTag($"{CodeOriginTag}.type", "exit");
for (var i = 0; i < framesLength; i++)
{
ref var info = ref frames[i];

// PopulateUserFrames returns only frames that have method
var method = info.Frame.GetMethod()!;
var type = method.DeclaringType?.FullName ?? method.DeclaringType?.Name;
if (string.IsNullOrEmpty(type))
{
continue;
}

span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.index", info.FrameIndex.ToString());
span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.method", method.Name);
span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.type", type);

var fileName = info.Frame.GetFileName();
if (!string.IsNullOrEmpty(fileName))
{
// todo: should we normalize?
dudikeleti marked this conversation as resolved.
Show resolved Hide resolved
span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.file", fileName);
}

var line = info.Frame.GetFileLineNumber();
if (line > 0)
{
span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.line", line.ToString());
}

var column = info.Frame.GetFileColumnNumber();
if (column > 0)
{
span.Tags.SetTag($"{CodeOriginTag}.{FramesPrefix}.{i}.column", column.ToString());
}
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to create exit span tag for {Span}", span.SpanId);
}
finally
{
if (frames != null!)
{
ArrayPool<FrameInfo>.Shared.Return(frames);
}
}
}

private int PopulateUserFrames(FrameInfo[] frames)
{
var stackTrace = new StackTrace(fNeedFileInfo: true);
var stackFrames = stackTrace.GetFrames();

if (stackFrames == null!)
{
return 0;
}

var count = 0;
for (var walkIndex = 0; walkIndex < stackFrames.Length && count < _settings.CodeOriginMaxUserFrames; walkIndex++)
{
var frame = stackFrames[walkIndex];

var assembly = frame?.GetMethod()?.DeclaringType?.Module.Assembly;
if (assembly == null)
{
continue;
}

if (AssemblyFilter.ShouldSkipAssembly(assembly, _settings.ThirdPartyDetectionExcludes, _settings.ThirdPartyDetectionIncludes))
{
// use cache when this will be merged: https://github.com/DataDog/dd-trace-dotnet/pull/6093
continue;
}

frames[count++] = new FrameInfo(walkIndex, frame!);
}

return count;
}

private readonly record struct FrameInfo(int FrameIndex, StackFrame Frame);
}
}
3 changes: 3 additions & 0 deletions tracer/src/Datadog.Trace/Tracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Datadog.Trace.Agent.DiscoveryService;
using Datadog.Trace.ClrProfiler;
using Datadog.Trace.Configuration;
using Datadog.Trace.Debugger.SpanCodeOrigin;
using Datadog.Trace.Logging.TracerFlare;
using Datadog.Trace.Sampling;
using Datadog.Trace.SourceGenerators;
Expand Down Expand Up @@ -553,6 +554,8 @@ internal Span StartSpan(string operationName, ITags tags = null, ISpanContext pa
// write them directly to the <see cref="TraceChunkModel"/>.
TracerManager.GitMetadataTagsProvider.TryExtractGitMetadata(out _);

SpanCodeOriginManager.Instance.SetCodeOrigin(span);

return span;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,61 @@ public void InvalidUploadFlushInterval_DefaultUsed(string value)

settings.UploadFlushIntervalMilliseconds.Should().Be(0);
}

[Theory]
[InlineData("")]
[InlineData("False")]
[InlineData("false")]
[InlineData("0")]
[InlineData("2")]
[InlineData(null)]
public void CodeOriginEnabled_False(string value)
{
var settings = new DebuggerSettings(
new NameValueConfigurationSource(new() { { ConfigurationKeys.Debugger.CodeOriginForSpansEnabled, value }, }),
NullConfigurationTelemetry.Instance);

settings.CodeOriginForSpansEnabled.Should().BeFalse();
}

[Theory]
[InlineData("True")]
[InlineData("true")]
[InlineData("1")]
public void CodeOriginEnabled_True(string value)
{
var settings = new DebuggerSettings(
new NameValueConfigurationSource(new() { { ConfigurationKeys.Debugger.CodeOriginForSpansEnabled, value }, }),
NullConfigurationTelemetry.Instance);

settings.CodeOriginForSpansEnabled.Should().BeTrue();
}

[Theory]
[InlineData("8")]
[InlineData("1")]
[InlineData("1000")]
public void CodeOriginMaxUserFrames(string value)
{
var settings = new DebuggerSettings(
new NameValueConfigurationSource(new() { { ConfigurationKeys.Debugger.CodeOriginMaxUserFrames, value }, }),
NullConfigurationTelemetry.Instance);

settings.CodeOriginMaxUserFrames.Should().Be(int.Parse(value));
}

[Theory]
[InlineData("-1")]
[InlineData("0")]
[InlineData("")]
[InlineData(null)]
public void InvalidCodeOriginMaxUserFrames_DefaultUsed(string value)
{
var settings = new DebuggerSettings(
new NameValueConfigurationSource(new() { { ConfigurationKeys.Debugger.CodeOriginMaxUserFrames, value }, }),
NullConfigurationTelemetry.Instance);

settings.CodeOriginMaxUserFrames.Should().Be(8);
}
}
}
Loading
Loading