Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d26940d
feat(trace-exporter): introduce native trace exporter
May 27, 2025
a8c7904
fix: enable debug tel and cleanup
May 28, 2025
6d6145e
feat(trace-exporter): handle exceptions when creating Trace Exporter …
May 30, 2025
b7cf051
fix: update skip exception messages for transport compatibility
Jun 3, 2025
3de4ab7
test: enhance assertions in TraceExporterTests for clarity
Jun 3, 2025
b09362d
cleanup
Jun 3, 2025
b493a05
fix: replace IntPtr with nint for pointer representation in ByteSlice…
Jun 3, 2025
c8bbdf5
fix: update CharSlice length type from UIntPtr to nuint for consistency
Jun 3, 2025
ede3d4a
fix: add comment to clarify exclusion of libdatadog in debug info ext…
Jun 3, 2025
635949c
fix: remove parameter from GetWinArchitectureAndExtension for clarity
Jun 3, 2025
b38f13b
fix: rename loop variable from 'fmk' to 'framework' for clarity
Jun 3, 2025
db88db4
fix: add warning log for conflicting configuration settings in tracer
Jun 3, 2025
f8f3a32
fix: format
Jun 3, 2025
7ad96e9
fix: CopyNativeFilesForTests for osx
Jun 3, 2025
fde7edb
fix: update transport checks for Unix Domain Sockets support
Jun 3, 2025
2da2b69
cleanup
Jun 3, 2025
fe2d531
fix: refactor span handling in TraceExporterTests
Jun 3, 2025
22c06df
fix: update Windows architecture handling in Build steps
Jun 3, 2025
d83d22d
cleanup
Jun 3, 2025
74c9f35
Try fix x86 support
andrewlock Jun 3, 2025
1959aff
Fix typo
andrewlock Jun 3, 2025
e2cad01
fix: remove unnecessary blank line in SendsTracesUsingDataPipeline test
Jun 3, 2025
c18b698
Only copy single file
andrewlock Jun 3, 2025
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
56 changes: 47 additions & 9 deletions tracer/build/_build/Build.Steps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ partial class Build

AbsolutePath TempDirectory => (AbsolutePath)(IsWin ? Path.GetTempPath() : "/tmp/");

readonly string[] WafWindowsArchitectureFolders = { "win-x86", "win-x64" };
readonly string[] WindowsArchitectureFolders = { "win-x86", "win-x64" };
Project NativeTracerProject => Solution.GetProject(Projects.ClrProfilerNative);
Project NativeTracerTestsProject => Solution.GetProject(Projects.NativeTracerNativeTests);
Project NativeLoaderProject => Solution.GetProject(Projects.NativeLoader);
Expand Down Expand Up @@ -546,7 +546,7 @@ async Task DownloadWafVersion(string libddwafVersion = null, string uncompressFo
{
if (IsWin)
{
foreach (var architecture in WafWindowsArchitectureFolders)
foreach (var architecture in WindowsArchitectureFolders)
{
var source = LibDdwafDirectory() / "runtimes" / architecture / "native" / "ddwaf.dll";
var dest = MonitoringHomeDirectory / architecture;
Expand Down Expand Up @@ -642,6 +642,41 @@ async Task DownloadWafVersion(string libddwafVersion = null, string uncompressFo
}
});

Target CopyNativeFilesForTests => _ => _
.Unlisted()
.After(Clean)
.After(BuildTracerHome)
.Executes(() =>
{
foreach(var projectName in Projects.NativeFilesDependentTests)
{
var project = Solution.GetProject(projectName);
var testDir = project.Directory;
var frameworks = project.GetTargetFrameworks();
var testBinFolder = testDir / "bin" / BuildConfiguration;

if (IsWin)
{
foreach (var framework in frameworks)
{
var source = MonitoringHomeDirectory / $"win-{TargetPlatform}" / "datadog_profiling_ffi.dll";
var dest = testBinFolder / framework / "LibDatadog.dll";
CopyFile(source, dest, FileExistsPolicy.Overwrite);
}
}
else
{
var (arch, ext) = GetUnixArchitectureAndExtension();
var source = MonitoringHomeDirectory / arch / $"libdatadog_profiling.{ext}";
foreach (var framework in frameworks)
{
var dest = testBinFolder / framework / $"LibDatadog.{ext}";
CopyFile(source, dest, FileExistsPolicy.Overwrite);
}
}
}
});

Target CopyNativeFilesForAppSecUnitTests => _ => _
.Unlisted()
.After(Clean)
Expand All @@ -662,13 +697,13 @@ async Task DownloadWafVersion(string libddwafVersion = null, string uncompressFo
{
var oldVersionTempPath = TempDirectory / $"libddwaf.{olderLibDdwafVersion}";
await DownloadWafVersion(olderLibDdwafVersion, oldVersionTempPath);
foreach (var arch in WafWindowsArchitectureFolders)
foreach (var arch in WindowsArchitectureFolders)
{
var oldVersionPath = oldVersionTempPath / "runtimes" / arch / "native" / "ddwaf.dll";
var source = MonitoringHomeDirectory / arch;
foreach (var fmk in frameworks)
foreach (var framework in frameworks)
{
var dest = testBinFolder / fmk / arch;
var dest = testBinFolder / framework / arch;
CopyDirectoryRecursively(source, dest, DirectoryExistsPolicy.Merge, FileExistsPolicy.Overwrite);
CopyFile(oldVersionPath, dest / $"ddwaf-{olderLibDdwafVersion}.dll", FileExistsPolicy.Overwrite);
}
Expand All @@ -687,7 +722,7 @@ async Task DownloadWafVersion(string libddwafVersion = null, string uncompressFo
var oldVersionPath = oldVersionTempPath / "runtimes" / patchedArchWaf / "native" / $"libddwaf.{ext}";
await DownloadWafVersion(olderLibDdwafVersion, oldVersionTempPath);
{
foreach (var fmk in frameworks)
foreach (var framework in frameworks)
{
// We have to copy into the _root_ test bin folder here, not the arch sub-folder.
// This is because these tests try to load the WAF.
Expand All @@ -696,7 +731,7 @@ async Task DownloadWafVersion(string libddwafVersion = null, string uncompressFo
// - The native tracer must be side-by-side with the running dll
// As this is a managed-only unit test, the native tracer _must_ be in the root folder
// For simplicity, we just copy all the native dlls there
var dest = testBinFolder / fmk;
var dest = testBinFolder / framework;

// use the files from the monitoring native folder
CopyDirectoryRecursively(MonitoringHomeDirectory / (IsOsx ? "osx" : arch), dest, DirectoryExistsPolicy.Merge, FileExistsPolicy.Overwrite);
Expand Down Expand Up @@ -938,7 +973,9 @@ async Task DownloadWafVersion(string libddwafVersion = null, string uncompressFo
.Executes(() =>
{
// extract debug info from everything in monitoring home and copy it to the linux symbols directory
var files = MonitoringHomeDirectory.GlobFiles("linux-*/*.so");
// except libdatadog since debug symbols are already stripped as part of libdatadog release.
var files = MonitoringHomeDirectory.GlobFiles("linux-*/*.so")
.Where(file => !Path.GetFileName(file).Contains("libdatadog_profiling.so"));

foreach (var file in files)
{
Expand Down Expand Up @@ -1263,6 +1300,7 @@ void PrepareMonitoringHomeLinuxForPackaging(AbsolutePath assetsDirectory, string
.After(CompileManagedSrc)
.After(BuildRunnerTool)
.DependsOn(CopyNativeFilesForAppSecUnitTests)
.DependsOn(CopyNativeFilesForTests)
.DependsOn(CompileManagedTestHelpers)
.Executes(() =>
{
Expand Down Expand Up @@ -2841,7 +2879,7 @@ private void DotnetBuild(
.CombineWith(projPaths, (settings, projPath) => settings.SetProjectFile(projPath)));
}


private async Task<string> GetVcpkg()
{
var vcpkgFilePath = string.Empty;
Expand Down
11 changes: 7 additions & 4 deletions tracer/build/_build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ partial class Build : NukeBuild

[Parameter("Enable or Disable fast developer loop")]
readonly bool FastDevLoop;

[Parameter("The directory containing the tool .nupkg file")]
readonly AbsolutePath ToolSource;

Expand All @@ -108,11 +108,11 @@ partial class Build : NukeBuild

[Parameter("Should we build native binaries as Universal. Default to false, so we can still build native libs outside of docker.")]
readonly bool AsUniversal = false;

[Parameter("RuntimeIdentifier sets the target platform for ReadyToRun assemblies in 'PublishManagedTracerR2R'." +
"See https://learn.microsoft.com/en-us/dotnet/core/rid-catalog")]
string RuntimeIdentifier { get; }

public Build()
{
RuntimeIdentifier = GetDefaultRuntimeIdentifier(IsAlpine);
Expand Down Expand Up @@ -218,7 +218,7 @@ void DeleteReparsePoints(string path)
.DependsOn(CreateMissingNullabilityFile)
.DependsOn(CreateTrimmingFile)
.DependsOn(RegenerateSolutions);

Target BuildManagedTracerHomeR2R => _ => _
.Unlisted()
.Description("Builds the native and managed src, and publishes the tracer home directory")
Expand Down Expand Up @@ -286,6 +286,7 @@ void DeleteReparsePoints(string path)
.Description("Builds the integration tests for Windows")
.DependsOn(CompileManagedTestHelpers)
.DependsOn(CompileIntegrationTests)
.DependsOn(CopyNativeFilesForTests)
.DependsOn(BuildRunnerTool);

Target BuildAspNetIntegrationTests => _ => _
Expand Down Expand Up @@ -334,6 +335,7 @@ void DeleteReparsePoints(string path)
.DependsOn(CompileLinuxOrOsxIntegrationTests)
.DependsOn(CompileLinuxDdDotnetIntegrationTests)
.DependsOn(BuildRunnerTool)
.DependsOn(CopyNativeFilesForTests)
.DependsOn(CopyServerlessArtifacts);

Target BuildAndRunLinuxIntegrationTests => _ => _
Expand All @@ -349,6 +351,7 @@ void DeleteReparsePoints(string path)
.DependsOn(CompileManagedTestHelpers)
.DependsOn(CompileLinuxOrOsxIntegrationTests)
.DependsOn(BuildRunnerTool)
.DependsOn(CopyNativeFilesForTests)
.DependsOn(CopyServerlessArtifacts);

Target BuildAndRunOsxIntegrationTests => _ => _
Expand Down
9 changes: 9 additions & 0 deletions tracer/build/_build/Projects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ public static class Projects
public const string BenchmarksOpenTelemetryApi = "Benchmarks.OpenTelemetry.Api";
public const string BenchmarksOpenTelemetryInstrumentedApi = "Benchmarks.OpenTelemetry.InstrumentedApi";

public const string TraceTests = "Datadog.Trace.Tests";
public const string TraceIntegrationTests = "Datadog.Trace.IntegrationTests";
public const string AppSecUnitTests = "Datadog.Trace.Security.Unit.Tests";
public const string AppSecIntegrationTests = "Datadog.Trace.Security.IntegrationTests";
public const string ClrProfilerIntegrationTests = "Datadog.Trace.ClrProfiler.IntegrationTests";
public const string ClrProfilerManagedTests = "Datadog.Trace.ClrProfiler.Managed.Tests";
public const string DdTraceIntegrationTests = "Datadog.Trace.Tools.Runner.IntegrationTests";
public const string DdTraceArtifactsTests = "Datadog.Trace.Tools.Runner.ArtifactTests";
public const string DdDotnetIntegrationTests = "Datadog.Trace.Tools.dd_dotnet.IntegrationTests";
Expand All @@ -39,6 +41,13 @@ public static class Projects
public const string DebuggerUnreferencedExternal = "Samples.Probes.Unreferenced.External";

public const string RazorPages = "Samples.AspNetCoreRazorPages";

public static readonly string[] NativeFilesDependentTests = {
AppSecUnitTests,
ClrProfilerManagedTests,
TraceIntegrationTests,
TraceTests
};
}

public static class FileNames
Expand Down
5 changes: 5 additions & 0 deletions tracer/src/Datadog.Trace/Agent/AgentWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ await Task.WhenAny(_flushTask, Task.Delay(TimeSpan.FromSeconds(20)))
await _statsAggregator.DisposeAsync().ConfigureAwait(false);
}

if (_api is IDisposable disposableApi)
{
disposableApi.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure AgentWriter should dispose an IApi it doesn't own (it's passed in via ctor). A caller could potentially re-use the IApi.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, yes, we shouldn't be doing it this way, but the current code architecture doesn't allow for a better solution. Both @andrewlock and I came to the conclusion that this is the best place to dispose of the native component.

This is especially important in cases where there can be multiple AgentWriter instances (one winding down while another is starting). Historically, the .NET SDK was designed to re-initialize the pipeline when major configuration changes occur that affect the AgentWriter.

Hence, coupling the TraceExporter with AgentWriter is ideal for us, as it allows different configurations for different pipelines while ensuring proper disposal of resources.

}

if (!success)
{
Log.Warning("Could not flush all traces before process exit");
Expand Down
6 changes: 6 additions & 0 deletions tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,12 @@ internal static partial class ConfigurationKeys
/// </summary>
public const string RuntimeMetricsEnabled = "DD_RUNTIME_METRICS_ENABLED";

/// <summary>
/// Use libdatadog data pipeline to send traces.
/// Default value is <c>false</c> (disabled).
/// </summary>
public const string TraceDataPipelineEnabled = "DD_TRACE_DATA_PIPELINE_ENABLED";

/// <summary>
/// Configuration key for when a standalone instance of the Trace Agent needs to be started.
/// </summary>
Expand Down
39 changes: 39 additions & 0 deletions tracer/src/Datadog.Trace/Configuration/TracerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Datadog.Trace.Agent;
using Datadog.Trace.Ci;
using Datadog.Trace.Ci.CiEnvironment;
using Datadog.Trace.ClrProfiler;
Expand Down Expand Up @@ -413,6 +414,38 @@ _ when x.ToBoolean() is { } boolean => boolean,
.AsBoolResult()
.OverrideWith(in otelRuntimeMetricsEnabled, ErrorLog, defaultValue: false);

DataPipelineEnabled = config
.WithKeys(ConfigurationKeys.TraceDataPipelineEnabled)
.AsBool(defaultValue: true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a reminder to set this false before the final merge 🙂

Suggested change
.AsBool(defaultValue: true);
.AsBool(defaultValue: false);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So are we or are we not changing the default to false (for now)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So are we or are we not changing the default to false (for now)?

We removed it from that PR, so we should reinstated it here, right @ganeshnj?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was talking offline, we can merge with enabled by default

I will add a different PR to disable and title it MERGE BEFORE RELEASE

this will allow us to exercise these changes for all PR and merges. But we have to 100% sure that we don't accidentally do a release without merging the default disable PR.


if (DataPipelineEnabled)
{
// Due to missing quantization and obfuscation in native side, we can't enable the native trace exporter
// as it may lead to different stats results than the managed one.
if (StatsComputationEnabled)
{
DataPipelineEnabled = false;
Log.Warning(
"{ConfigurationKey} is enabled, but {StatsComputationEnabled} is enabled. Disabling {TraceDataPipelineEnabled}.",
ConfigurationKeys.TraceDataPipelineEnabled,
ConfigurationKeys.StatsComputationEnabled,
ConfigurationKeys.TraceDataPipelineEnabled);
_telemetry.Record(ConfigurationKeys.TraceDataPipelineEnabled, false, ConfigurationOrigins.Calculated);
}

// Windows supports UnixDomainSocket https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/
// but tokio hasn't added support for it yet https://github.com/tokio-rs/tokio/issues/2201
if (Exporter.TracesTransport == TracesTransportType.UnixDomainSocket && FrameworkDescription.Instance.IsWindows())
{
DataPipelineEnabled = false;
Log.Warning(
"{ConfigurationKey} is enabled, but TracesTransport is set to UnixDomainSocket which is not supported on Windows. Disabling {TraceDataPipelineEnabled}.",
ConfigurationKeys.TraceDataPipelineEnabled,
ConfigurationKeys.TraceDataPipelineEnabled);
_telemetry.Record(ConfigurationKeys.TraceDataPipelineEnabled, false, ConfigurationOrigins.Calculated);
}
}

// We should also be writing telemetry for OTEL_LOGS_EXPORTER similar to OTEL_METRICS_EXPORTER, but we don't have a corresponding Datadog config
// When we do, we can insert that here

Expand Down Expand Up @@ -1061,6 +1094,12 @@ public bool DiagnosticSourceEnabled
/// </summary>
internal bool RuntimeMetricsEnabled => DynamicSettings.RuntimeMetricsEnabled ?? _runtimeMetricsEnabled;

/// <summary>
/// Gets a value indicating whether libdatadog data pipeline
/// is enabled.
/// </summary>
internal bool DataPipelineEnabled { get; }

/// <summary>
/// Gets the comma separated list of url patterns to skip tracing.
/// </summary>
Expand Down
28 changes: 28 additions & 0 deletions tracer/src/Datadog.Trace/LibDatadog/ByteSlice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// <copyright file="ByteSlice.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.Runtime.InteropServices;

namespace Datadog.Trace.LibDatadog;

/// <summary>
/// Represents a slice of a byte array in memory.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct ByteSlice
{
/// <summary>
/// Pointer to the start of the slice.
/// </summary>
internal nint Ptr;

/// <summary>
/// Length of the slice.
/// </summary>
internal nuint Len;
}
55 changes: 55 additions & 0 deletions tracer/src/Datadog.Trace/LibDatadog/CharSlice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// <copyright file="CharSlice.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.Runtime.InteropServices;

namespace Datadog.Trace.LibDatadog;

/// <summary>
/// Represents a slice of a UTF-8 encoded string in memory.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct CharSlice : IDisposable
{
/// <summary>
/// Pointer to the start of the slice.
/// </summary>
internal nint Ptr;

/// <summary>
/// Length of the slice.
/// </summary>
internal nuint Len;

/// <summary>
/// Initializes a new instance of the <see cref="CharSlice"/> struct.
/// This can be further optimized if we can avoid copying the string to unmanaged memory.
/// </summary>
/// <param name="str">The string to copy into memory.</param>
internal CharSlice(string? str)
{
if (str == null)
{
Ptr = IntPtr.Zero;
Len = UIntPtr.Zero;
}
else
{
// copy over str to unmanaged memory
var bytes = System.Text.Encoding.UTF8.GetBytes(str);
Ptr = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, Ptr, bytes.Length);
Len = (nuint)bytes.Length;
}
}

public void Dispose()
{
Marshal.FreeHGlobal(Ptr);
}
}
Loading
Loading