Skip to content
Open
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 @@ -43,6 +43,19 @@ public void ShouldNotCrashWhenNullOrEmptyThreadName(string appName, string frame
return (l.Name == "thread name");
}).Value;

// thread name must always be set
if (string.IsNullOrEmpty(threadName))
{
var threadId = labels.FirstOrDefault((l) =>
{
return (l.Name == "thread id");
}).Value;
// thread id is always set
Assert.Fail($"Thread name is null or empty for thread {threadId}");
return;
}

// only care about nameless threads
if (threadName.Contains("Managed thread (name unknown) ["))
{
var stackTrace = sample.StackTrace(profile);
Expand All @@ -52,14 +65,6 @@ public void ShouldNotCrashWhenNullOrEmptyThreadName(string appName, string frame
return;
}
}
else if (threadName.Contains(".NET Long Running Task ["))
{
// expected task that is waiting for nameless threads to join
}
else
{
threadWithNameFound = true;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,22 @@ public void ShouldContentionProfilerBeEnabledByDefault(string appName, string fr
{
var runner = new TestApplicationRunner(appName, framework, appAssembly, _output, commandLine: ScenarioContention);

// disable default profilers except contention
// disabled all default profiles except contention and wall time
// thread lifetime profiler enabled <= this one will help us gathering
// an accurate view on the threads that are alive during the profiling
runner.Environment.SetVariable(EnvironmentVariables.WallTimeProfilerEnabled, "0");
runner.Environment.SetVariable(EnvironmentVariables.CpuProfilerEnabled, "0");
runner.Environment.SetVariable(EnvironmentVariables.GarbageCollectionProfilerEnabled, "0");
runner.Environment.SetVariable(EnvironmentVariables.ExceptionProfilerEnabled, "0");
runner.Environment.SetVariable(EnvironmentVariables.GcThreadsCpuTimeEnabled, "0");
runner.Environment.SetVariable(EnvironmentVariables.ThreadLifetimeEnabled, "0");
runner.Environment.SetVariable(EnvironmentVariables.ThreadLifetimeEnabled, "1");

using var agent = MockDatadogAgent.CreateHttpAgent(runner.XUnitLogger);

runner.Run(agent);

// only contention profiler enabled so should see 2 value per sample
SamplesHelper.CheckSamplesValueCount(runner.Environment.PprofDir, 2);
// only contention profiler enabled so should see 3 value per sample
SamplesHelper.CheckSamplesValueCount(runner.Environment.PprofDir, 3);
Assert.NotEqual(0, SamplesHelper.GetSamplesCount(runner.Environment.PprofDir));

if (framework == "net8.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

namespace Datadog.Trace.ClrProfiler.Managed.Loader
{
Expand Down Expand Up @@ -62,6 +63,23 @@ internal static string ComputeTfmDirectory(string tracerHomeDirectory)
return fullPath;
}

internal static string GetProfilerPathEnvVarNameForArch()
{
return RuntimeInformation.ProcessArchitecture switch
{
Architecture.X64 => "CORECLR_PROFILER_PATH_64",
Architecture.X86 => "CORECLR_PROFILER_PATH_32",
Architecture.Arm64 => "CORECLR_PROFILER_PATH_ARM64",
Architecture.Arm => "CORECLR_PROFILER_PATH_ARM",
_ => throw new ArgumentOutOfRangeException(nameof(RuntimeInformation.ProcessArchitecture), RuntimeInformation.ProcessArchitecture, "Unsupported architecture")
};
}

internal static string GetProfilerPathEnvVarNameFallback()
{
return "CORECLR_PROFILER_PATH";
}

private static Assembly? AssemblyResolve_ManagedProfilerDependencies(object sender, ResolveEventArgs args)
{
return ResolveAssembly(args.Name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ internal static string ComputeTfmDirectory(string tracerHomeDirectory)
return Path.Combine(Path.GetFullPath(tracerHomeDirectory), "net461");
}

internal static string GetProfilerPathEnvVarNameForArch()
{
return Environment.Is64BitProcess ? "COR_PROFILER_PATH_64" : "COR_PROFILER_PATH_32";
}

internal static string GetProfilerPathEnvVarNameFallback()
{
return "COR_PROFILER_PATH";
}

private static Assembly? AssemblyResolve_ManagedProfilerDependencies(object sender, ResolveEventArgs args)
{
try
Expand Down
122 changes: 113 additions & 9 deletions tracer/src/Datadog.Trace.ClrProfiler.Managed.Loader/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
Expand All @@ -21,6 +22,21 @@ public partial class Startup
private const string AzureAppServicesSiteExtensionKey = "DD_AZURE_APP_SERVICES"; // only set when using the AAS site extension
private const string TracerHomePathKey = "DD_DOTNET_TRACER_HOME";

private static readonly HashSet<string> ArchitectureDirectories = new(StringComparer.OrdinalIgnoreCase)
{
"win-x64",
"win-x86",
#if NETCOREAPP
"linux-x64",
"linux-arm64",
"linux-musl-x64",
"linux-musl-arm64",
"osx",
"osx-arm64",
"osx-x64"
#endif
};

private static int _startupCtorInitialized;

/// <summary>
Expand All @@ -34,10 +50,9 @@ static Startup()
// Startup() was already called before in the same AppDomain, this can happen because the profiler rewrites
// methods before the jitting to inject the loader. This is done until the profiler detects that the loader
// has been initialized.
// The piece of code injected already includes an Interlocked condition but, because the static variable is emitted
// in a custom type inside the running assembly, others assemblies will also have a different type with a different static
// variable, so, we still can hit an scenario where multiple loaders initialize.
// With this we prevent this scenario.
// The piece of code injected already includes an Interlocked condition. However, because the static variable is emitted
// in a custom type inside the running assembly, other assemblies will also have a different type with a different static
// variable, so we still can hit a scenario where multiple loaders initialize. This prevents this scenario.
return;
}

Expand All @@ -58,23 +73,36 @@ static Startup()
#endif

var envVars = new EnvironmentVariableProvider(logErrors: true);
var tracerHomeDirectory = envVars.GetEnvironmentVariable(TracerHomePathKey);
var tracerHomeDirectory = GetTracerHomePath(envVars);

if (tracerHomeDirectory is null)
{
StartupLogger.Log("{0} not set. Datadog SDK will be disabled.", TracerHomePathKey);
// Provide a specific error message based on what was configured
var explicitTracerHome = envVars.GetEnvironmentVariable(TracerHomePathKey);

if (string.IsNullOrWhiteSpace(explicitTracerHome))
{
// DD_DOTNET_TRACER_HOME was not set and automatic detection from profiler path failed
StartupLogger.Log("{0} is not set and the tracer home directory could not be determined automatically. Datadog SDK will be disabled. To resolve this issue, set environment variable {0}.", TracerHomePathKey);
}
else
{
// DD_DOTNET_TRACER_HOME was set but resulted in null (shouldn't happen, but just in case)
StartupLogger.Log("{0} is set to '{1}' but could not be used. Datadog SDK will be disabled.", TracerHomePathKey, explicitTracerHome);
}

return;
}

ManagedProfilerDirectory = ComputeTfmDirectory(tracerHomeDirectory);

if (!Directory.Exists(ManagedProfilerDirectory))
{
StartupLogger.Log("Datadog.Trace.dll TFM directory not found at '{0}'. Datadog SDK will be disabled.", ManagedProfilerDirectory);
StartupLogger.Log("Datadog.Trace.dll directory not found at '{0}'. Datadog SDK will be disabled.", ManagedProfilerDirectory);
return;
}

StartupLogger.Debug("Resolved Datadog.Trace.dll TFM directory to: {0}", ManagedProfilerDirectory);
StartupLogger.Debug("Resolved Datadog.Trace.dll directory to: {0}", ManagedProfilerDirectory);

try
{
Expand Down Expand Up @@ -125,13 +153,89 @@ static Startup()
// Nothing to do here.
}

// If the logger fails, throw the original exception. The profiler emits code to log it.
// If the logger fails, throw the original exception. The native library emits code to log it.
throw;
}
}

internal static string? ManagedProfilerDirectory { get; }

internal static string? GetTracerHomePath<TEnvVars>(TEnvVars envVars)
where TEnvVars : IEnvironmentVariableProvider
{
// allow override with DD_DOTNET_TRACER_HOME
var tracerHomeDirectory = envVars.GetEnvironmentVariable(TracerHomePathKey);

if (!string.IsNullOrWhiteSpace(tracerHomeDirectory))
{
// Safe to use ! here because we just checked !string.IsNullOrWhiteSpace above
var trimmedPath = tracerHomeDirectory!.Trim();
StartupLogger.Debug("Using tracer home from {0}=\"{1}\"", TracerHomePathKey, trimmedPath);
return trimmedPath;
}

// try to compute the path from the architecture-specific "COR_PROFILER_PATH_*" or "CORECLR_PROFILER_PATH_*"
var archEnvVarName = GetProfilerPathEnvVarNameForArch();

if (ComputeTracerHomePathFromProfilerPath(envVars, archEnvVarName) is { } archTracerHomePath)
{
StartupLogger.Debug("Derived tracer home from {0}=\"{1}\"", archEnvVarName, archTracerHomePath);
return archTracerHomePath;
}

// try to compute the path from "COR_PROFILER_PATH" or "CORECLR_PROFILER_PATH" (no architecture)
var fallbackEnvVarName = GetProfilerPathEnvVarNameFallback();

if (ComputeTracerHomePathFromProfilerPath(envVars, fallbackEnvVarName) is { } fallbackTracerHomePath)
{
StartupLogger.Debug("Derived tracer home from {0}=\"{1}\"", fallbackEnvVarName, fallbackTracerHomePath);
return fallbackTracerHomePath;
}

return null;
}

internal static string? ComputeTracerHomePathFromProfilerPath<TEnvVars>(TEnvVars envVars, string envVarName)
where TEnvVars : IEnvironmentVariableProvider
{
var envVarValue = envVars.GetEnvironmentVariable(envVarName)?.Trim();

if (string.IsNullOrWhiteSpace(envVarValue))
{
return null;
}

try
{
var directory = Directory.GetParent(envVarValue);

if (directory is null)
{
StartupLogger.Log("Unable to determine tracer home directory from {0}={1}", envVarName, envVarValue);
return null;
}

// if the directory name is one of the well-known "os-arch" child directories (e.g. "win-x64"), go one level higher
if (ArchitectureDirectories.Contains(directory.Name))
{
directory = directory.Parent;

if (directory is null)
{
StartupLogger.Log("Unable to determine tracer home directory from {0}={1}", envVarName, envVarValue);
return null;
}
}

return directory.FullName;
}
catch (Exception ex)
{
StartupLogger.Log(ex, "Error resolving tracer home directory from {0}={1}", envVarName, envVarValue);
return null;
}
}

private static void TryInvokeManagedMethod(string typeName, string methodName, string? loaderHelperTypeName = null)
{
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,25 @@ public async Task WhenUsingPathWithDotsInInTracerHome_InstrumentsApp()
agent.Telemetry.Should().NotBeEmpty();
}

[SkippableFact]
[Trait("RunOnWindows", "True")]
public async Task WhenOmittingTracerHome_InstrumentsApp()
{
// Verify that DD_DOTNET_TRACER_HOME is not set to ensure we're actually testing the fallback behavior
EnvironmentHelper.CustomEnvironmentVariables.Should().NotContainKey("DD_DOTNET_TRACER_HOME");
Environment.GetEnvironmentVariable("DD_DOTNET_TRACER_HOME").Should().BeNullOrEmpty();

SetLogDirectory();

// DD_DOTNET_TRACER_HOME is not set, so the tracer should derive it from the profiler path
Output.WriteLine("DD_DOTNET_TRACER_HOME not set, relying on profiler path environment variables");

using var agent = EnvironmentHelper.GetMockAgent(useTelemetry: true);
using var processResult = await RunSampleAndWaitForExit(agent, "traces 1");
agent.Spans.Should().NotBeEmpty();
agent.Telemetry.Should().NotBeEmpty();
}

[SkippableTheory]
[CombinatorialData]
[Trait("RunOnWindows", "True")]
Expand Down Expand Up @@ -426,7 +445,7 @@ public async Task OnEolFrameworkInSsi_WhenForwarderPathExists_CallsForwarderWith

var pointsJson = """
[{
"name": "library_entrypoint.abort",
"name": "library_entrypoint.abort",
"tags": ["reason:eol_runtime"]
},{
"name": "library_entrypoint.abort.runtime"
Expand Down Expand Up @@ -459,7 +478,7 @@ public async Task OnEolFrameworkInSsi_WhenOverriden_CallsForwarderWithExpectedTe

var pointsJson = """
[{
"name": "library_entrypoint.complete",
"name": "library_entrypoint.complete",
"tags": ["injection_forced:true"]
}]
""";
Expand Down Expand Up @@ -498,7 +517,7 @@ public async Task OnPreviewFrameworkInSsi_CallsForwarderWithExpectedTelemetry()

var pointsJson = """
[{
"name": "library_entrypoint.complete",
"name": "library_entrypoint.complete",
"tags": ["injection_forced:true"]
}]
""";
Expand Down Expand Up @@ -529,7 +548,7 @@ public async Task OnPreviewFrameworkInSsi_WhenForwarderPathExists_CallsForwarder

var pointsJson = """
[{
"name": "library_entrypoint.abort",
"name": "library_entrypoint.abort",
"tags": ["reason:incompatible_runtime"]
},{
"name": "library_entrypoint.abort.runtime"
Expand Down Expand Up @@ -588,7 +607,7 @@ public async Task OnSupportedFrameworkInSsi_CallsForwarderWithExpectedTelemetry(

var pointsJson = """
[{
"name": "library_entrypoint.complete",
"name": "library_entrypoint.complete",
"tags": ["injection_forced:false"]
}]
""";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ protected SmokeTestBase(
this.GetType(),
output,
samplesDirectory: "test/test-applications/regression",
prependSamplesToAppName: false);
prependSamplesToAppName: false)
{
// Don't set DD_DOTNET_TRACER_HOME in smoke tests to verify the fallback logic works
SetTracerHomeEnvironmentVariable = false
};
}

protected ITestOutputHelper Output { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public EnvironmentHelper(

public bool DebugModeEnabled { get; set; }

public bool SetTracerHomeEnvironmentVariable { get; set; } = true;

public Dictionary<string, string> CustomEnvironmentVariables { get; set; } = new Dictionary<string, string>();

public string SampleName { get; }
Expand Down Expand Up @@ -196,7 +198,11 @@ public void SetEnvironmentVariables(
bool ignoreProfilerProcessesVar = false)
{
string profilerEnabled = AutomaticInstrumentationEnabled ? "1" : "0";
environmentVariables["DD_DOTNET_TRACER_HOME"] = MonitoringHome;

if (SetTracerHomeEnvironmentVariable)
{
environmentVariables["DD_DOTNET_TRACER_HOME"] = MonitoringHome;
}

// see https://github.com/DataDog/dd-trace-dotnet/pull/3579
environmentVariables["DD_INTERNAL_WORKAROUND_77973_ENABLED"] = "1";
Expand Down
Loading
Loading