From 067753fce8d3bc0307e5d3576fbbb48a451c349a Mon Sep 17 00:00:00 2001
From: Ganesh Jangir <ganesh.jangir@datadoghq.com>
Date: Mon, 16 Dec 2024 17:13:36 +0100
Subject: [PATCH] feat: setup boilerplate for data-pipeline integration

---
 tracer/build/_build/Build.Steps.cs            |  28 ++--
 tracer/missing-nullability-files.csv          |  11 ++
 tracer/src/Datadog.Trace/Agent/AgentWriter.cs |   8 +
 .../Configuration/ConfigurationKeys.cs        |   6 +
 .../Configuration/TracerSettings.cs           |  11 ++
 tracer/src/Datadog.Trace/Datadog.Trace.csproj |   1 +
 .../src/Datadog.Trace/LibDatadog/ByteSlice.cs |  26 ++++
 .../src/Datadog.Trace/LibDatadog/CharSlice.cs |  53 +++++++
 .../src/Datadog.Trace/LibDatadog/ErrorCode.cs |  32 ++++
 .../Datadog.Trace/LibDatadog/ErrorHandle.cs   |  44 ++++++
 .../Datadog.Trace/LibDatadog/TraceExporter.cs |  69 +++++++++
 .../LibDatadog/TraceExporterConfiguration.cs  | 139 ++++++++++++++++++
 .../LibDatadog/TraceExporterError.cs          |  27 ++++
 .../LibDatadog/TraceExporterException.cs      |  23 +++
 .../LibDatadog/TraceExporterInputFormat.cs    |  22 +++
 .../LibDatadog/TraceExporterNative.cs         |  61 ++++++++
 .../LibDatadog/TraceExporterOutputFormat.cs   |  22 +++
 .../src/Datadog.Trace/TracerManagerFactory.cs |  25 +++-
 .../LibDatadog/TraceExporterTests.cs          |  63 ++++++++
 .../StatsTests.cs                             |   3 +
 20 files changed, 659 insertions(+), 15 deletions(-)
 create mode 100644 tracer/src/Datadog.Trace/LibDatadog/ByteSlice.cs
 create mode 100644 tracer/src/Datadog.Trace/LibDatadog/CharSlice.cs
 create mode 100644 tracer/src/Datadog.Trace/LibDatadog/ErrorCode.cs
 create mode 100644 tracer/src/Datadog.Trace/LibDatadog/ErrorHandle.cs
 create mode 100644 tracer/src/Datadog.Trace/LibDatadog/TraceExporter.cs
 create mode 100644 tracer/src/Datadog.Trace/LibDatadog/TraceExporterConfiguration.cs
 create mode 100644 tracer/src/Datadog.Trace/LibDatadog/TraceExporterError.cs
 create mode 100644 tracer/src/Datadog.Trace/LibDatadog/TraceExporterException.cs
 create mode 100644 tracer/src/Datadog.Trace/LibDatadog/TraceExporterInputFormat.cs
 create mode 100644 tracer/src/Datadog.Trace/LibDatadog/TraceExporterNative.cs
 create mode 100644 tracer/src/Datadog.Trace/LibDatadog/TraceExporterOutputFormat.cs
 create mode 100644 tracer/test/Datadog.Trace.IntegrationTests/LibDatadog/TraceExporterTests.cs

diff --git a/tracer/build/_build/Build.Steps.cs b/tracer/build/_build/Build.Steps.cs
index fc5d751d6a7a..d4832d583ce6 100644
--- a/tracer/build/_build/Build.Steps.cs
+++ b/tracer/build/_build/Build.Steps.cs
@@ -624,7 +624,7 @@ async Task DownloadWafVersion(string libddwafVersion = null, string uncompressFo
                 .SetConfiguration(BuildConfiguration)
                 .SetTargetPlatformAnyCPU()
                 .EnableNoBuild()
-                .EnableNoRestore()
+                //.EnableNoRestore()
                 .CombineWith(targetFrameworks, (p, framework) => p
                     .SetFramework(framework)
                     .SetOutput(MonitoringHomeDirectory / framework))
@@ -1365,7 +1365,7 @@ _ when path.Contains("dependency-libs") => false,
                     .Where(project => Solution.GetProject(project).GetTargetFrameworks().Contains(Framework))
                 ;
 
-            DotnetBuild(projects, framework: Framework);
+            DotnetBuild(projects, framework: Framework, noRestore: false);
         });
 
     Target CompileSamplesWindows => _ => _
@@ -1567,8 +1567,8 @@ _ when exclude.Contains(x.project.Path) => false,
                     .SetFramework(Framework)
                     //.WithMemoryDumpAfter(timeoutInMinutes: 30)
                     .EnableCrashDumps()
-                    .EnableNoRestore()
-                    .EnableNoBuild()
+                    // .EnableNoRestore()
+                    // .EnableNoBuild()
                     .SetTestTargetPlatform(TargetPlatform)
                     .SetIsDebugRun(isDebugRun)
                     .SetProcessEnvironmentVariable("MonitoringHomeDirectory", MonitoringHomeDirectory)
@@ -1590,8 +1590,8 @@ _ when exclude.Contains(x.project.Path) => false,
                     .SetTargetPlatformAnyCPU()
                     .SetFramework(Framework)
                     //.WithMemoryDumpAfter(timeoutInMinutes: 30)
-                    .EnableNoRestore()
-                    .EnableNoBuild()
+                    // .EnableNoRestore()
+                    // .EnableNoBuild()
                     .SetFilter(string.IsNullOrWhiteSpace(Filter) ? "(RunOnWindows=True)&(LoadFromGAC!=True)&(IIS!=True)&(Category!=AzureFunctions)&(SkipInCI!=True)" : Filter)
                     .SetTestTargetPlatform(TargetPlatform)
                     .SetIsDebugRun(isDebugRun)
@@ -2104,8 +2104,8 @@ var name when multiPackageProjects.Contains(name) => false,
                 // Run these ones in parallel
                 DotNetTest(config => config
                         .SetConfiguration(BuildConfiguration)
-                        .EnableNoRestore()
-                        .EnableNoBuild()
+                        // .EnableNoRestore()
+                        // .EnableNoBuild()
                         .SetFramework(Framework)
                         //.WithMemoryDumpAfter(timeoutInMinutes: 30)
                         .EnableCrashDumps()
@@ -2127,8 +2127,8 @@ var name when multiPackageProjects.Contains(name) => false,
                 // Run this one separately so we can tail output
                 DotNetTest(config => config
                     .SetConfiguration(BuildConfiguration)
-                    .EnableNoRestore()
-                    .EnableNoBuild()
+                    // .EnableNoRestore()
+                    // .EnableNoBuild()
                     .SetFramework(Framework)
                     //.WithMemoryDumpAfter(timeoutInMinutes: 30)
                     .EnableCrashDumps()
@@ -2185,8 +2185,8 @@ var name when multiPackageProjects.Contains(name) => false,
                 // Run these ones in parallel
                 DotNetTest(config => config
                         .SetConfiguration(BuildConfiguration)
-                        .EnableNoRestore()
-                        .EnableNoBuild()
+                        // .EnableNoRestore()
+                        // .EnableNoBuild()
                         .SetFramework(Framework)
                         //.WithMemoryDumpAfter(timeoutInMinutes: 30)
                         .EnableCrashDumps()
@@ -2209,8 +2209,8 @@ var name when multiPackageProjects.Contains(name) => false,
                 // Run this one separately so we can tail output
                 DotNetTest(config => config
                     .SetConfiguration(BuildConfiguration)
-                    .EnableNoRestore()
-                    .EnableNoBuild()
+                    // .EnableNoRestore()
+                    // .EnableNoBuild()
                     .SetFramework(Framework)
                     //.WithMemoryDumpAfter(timeoutInMinutes: 30)
                     .EnableCrashDumps()
diff --git a/tracer/missing-nullability-files.csv b/tracer/missing-nullability-files.csv
index 7d88c7389565..1746fd34caac 100644
--- a/tracer/missing-nullability-files.csv
+++ b/tracer/missing-nullability-files.csv
@@ -141,6 +141,17 @@ src/Datadog.Trace/HttpOverStreams/IHttpContent.cs
 src/Datadog.Trace/Iast/Iast.cs
 src/Datadog.Trace/Iast/ITaintedMap.cs
 src/Datadog.Trace/Iast/SourceType.cs
+src/Datadog.Trace/LibDatadog/ByteSlice.cs
+src/Datadog.Trace/LibDatadog/CharSlice.cs
+src/Datadog.Trace/LibDatadog/ErrorCode.cs
+src/Datadog.Trace/LibDatadog/ErrorHandle.cs
+src/Datadog.Trace/LibDatadog/TraceExporter.cs
+src/Datadog.Trace/LibDatadog/TraceExporterConfiguration.cs
+src/Datadog.Trace/LibDatadog/TraceExporterError.cs
+src/Datadog.Trace/LibDatadog/TraceExporterException.cs
+src/Datadog.Trace/LibDatadog/TraceExporterInputFormat.cs
+src/Datadog.Trace/LibDatadog/TraceExporterNative.cs
+src/Datadog.Trace/LibDatadog/TraceExporterOutputFormat.cs
 src/Datadog.Trace/PlatformHelpers/AspNetCoreHttpRequestHandler.cs
 src/Datadog.Trace/PlatformHelpers/AzureContext.cs
 src/Datadog.Trace/PlatformHelpers/ContainerMetadata.cs
diff --git a/tracer/src/Datadog.Trace/Agent/AgentWriter.cs b/tracer/src/Datadog.Trace/Agent/AgentWriter.cs
index b72bf50d3a07..ea0693ddad74 100644
--- a/tracer/src/Datadog.Trace/Agent/AgentWriter.cs
+++ b/tracer/src/Datadog.Trace/Agent/AgentWriter.cs
@@ -97,6 +97,14 @@ internal AgentWriter(IApi api, IStatsAggregator statsAggregator, IDogStatsd stat
             _appsecStandaloneEnabled = appsecStandaloneEnabled;
         }
 
+        ~AgentWriter()
+        {
+            if (_api is IDisposable disposableApi)
+            {
+                disposableApi.Dispose();
+            }
+        }
+
         internal event Action Flushed;
 
         internal SpanBuffer ActiveBuffer => _activeBuffer;
diff --git a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.cs b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.cs
index e45f522b1273..eadd6de250d2 100644
--- a/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.cs
+++ b/tracer/src/Datadog.Trace/Configuration/ConfigurationKeys.cs
@@ -253,6 +253,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 DataPipelineEnabled = "DD_DATA_PIPELINE_ENABLED";
+
         /// <summary>
         /// Configuration key for when a standalone instance of the Trace Agent needs to be started.
         /// </summary>
diff --git a/tracer/src/Datadog.Trace/Configuration/TracerSettings.cs b/tracer/src/Datadog.Trace/Configuration/TracerSettings.cs
index 8abdf9894606..e1b14e31ff22 100644
--- a/tracer/src/Datadog.Trace/Configuration/TracerSettings.cs
+++ b/tracer/src/Datadog.Trace/Configuration/TracerSettings.cs
@@ -52,6 +52,7 @@ public record TracerSettings
         private readonly double? _globalSamplingRate;
         private readonly bool _runtimeMetricsEnabled;
         private readonly string? _customSamplingRules;
+        private readonly bool _dataPipelineEnabled;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="TracerSettings"/> class with default values.
@@ -331,6 +332,10 @@ _ when x.ToBoolean() is { } boolean => boolean,
                                    .AsBoolResult()
                                    .OverrideWith(in otelRuntimeMetricsEnabled, ErrorLog, defaultValue: false);
 
+            _dataPipelineEnabled = config
+                                  .WithKeys(ConfigurationKeys.DataPipelineEnabled)
+                                  .AsBool(defaultValue: true);
+
             // 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
 
@@ -919,6 +924,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 => _dataPipelineEnabled;
+
         /// <summary>
         /// Gets the comma separated list of url patterns to skip tracing.
         /// </summary>
diff --git a/tracer/src/Datadog.Trace/Datadog.Trace.csproj b/tracer/src/Datadog.Trace/Datadog.Trace.csproj
index 5ea13405ed2d..9514c9379af7 100644
--- a/tracer/src/Datadog.Trace/Datadog.Trace.csproj
+++ b/tracer/src/Datadog.Trace/Datadog.Trace.csproj
@@ -54,6 +54,7 @@
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
+    <PackageReference Include="ganeshnj.libdatadog" Version="14.3.1-ci.771.163" />
     <PackageReference Include="InlineIL.Fody" Version="1.8.0" PrivateAssets="All" />
   </ItemGroup>
 
diff --git a/tracer/src/Datadog.Trace/LibDatadog/ByteSlice.cs b/tracer/src/Datadog.Trace/LibDatadog/ByteSlice.cs
new file mode 100644
index 000000000000..208def42e4af
--- /dev/null
+++ b/tracer/src/Datadog.Trace/LibDatadog/ByteSlice.cs
@@ -0,0 +1,26 @@
+// <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>
+
+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 IntPtr Ptr;
+
+    /// <summary>
+    /// Length of the slice.
+    /// </summary>
+    internal UIntPtr Len;
+}
diff --git a/tracer/src/Datadog.Trace/LibDatadog/CharSlice.cs b/tracer/src/Datadog.Trace/LibDatadog/CharSlice.cs
new file mode 100644
index 000000000000..b1450a0569ce
--- /dev/null
+++ b/tracer/src/Datadog.Trace/LibDatadog/CharSlice.cs
@@ -0,0 +1,53 @@
+// <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>
+
+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
+{
+    /// <summary>
+    /// Represents an empty slice.
+    /// </summary>
+    internal static CharSlice Empty = new(string.Empty);
+
+    /// <summary>
+    /// Pointer to the start of the slice.
+    /// </summary>
+    internal IntPtr Ptr;
+
+    /// <summary>
+    /// Length of the slice.
+    /// </summary>
+    internal UIntPtr 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)
+    {
+        // copy over str to unmanaged memory
+        if (str == null)
+        {
+            Ptr = IntPtr.Zero;
+            Len = UIntPtr.Zero;
+        }
+        else
+        {
+            var bytes = System.Text.Encoding.UTF8.GetBytes(str);
+            Ptr = Marshal.AllocHGlobal(bytes.Length);
+            Marshal.Copy(bytes, 0, Ptr, bytes.Length);
+            Len = (UIntPtr)bytes.Length;
+        }
+    }
+}
diff --git a/tracer/src/Datadog.Trace/LibDatadog/ErrorCode.cs b/tracer/src/Datadog.Trace/LibDatadog/ErrorCode.cs
new file mode 100644
index 000000000000..644493d4e675
--- /dev/null
+++ b/tracer/src/Datadog.Trace/LibDatadog/ErrorCode.cs
@@ -0,0 +1,32 @@
+// <copyright file="ErrorCode.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>
+
+namespace Datadog.Trace.LibDatadog;
+
+/// <summary>
+/// Represents error codes that can occur when exporting traces.
+/// </summary>
+internal enum ErrorCode
+{
+    AddressInUse = 0,
+    ConnectionAborted = 1,
+    ConnectionRefused = 2,
+    ConnectionReset = 3,
+    HttpBodyFormat = 4,
+    HttpBodyTooLong = 5,
+    HttpClient = 6,
+    HttpParse = 7,
+    HttpServer = 8,
+    HttpUnknown = 9,
+    HttpWrongStatus = 10,
+    InvalidArgument = 11,
+    InvalidData = 12,
+    InvalidInput = 13,
+    InvalidUrl = 14,
+    IoError = 15,
+    NetworkUnknown = 16,
+    Serde = 17,
+    TimedOut = 18,
+}
diff --git a/tracer/src/Datadog.Trace/LibDatadog/ErrorHandle.cs b/tracer/src/Datadog.Trace/LibDatadog/ErrorHandle.cs
new file mode 100644
index 000000000000..3435dbc4cc51
--- /dev/null
+++ b/tracer/src/Datadog.Trace/LibDatadog/ErrorHandle.cs
@@ -0,0 +1,44 @@
+// <copyright file="ErrorHandle.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>
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Datadog.Trace.LibDatadog;
+
+internal class ErrorHandle : SafeHandle
+{
+    public ErrorHandle()
+        : base(IntPtr.Zero, true)
+    {
+    }
+
+    public ErrorHandle(IntPtr handle)
+        : base(handle, true)
+    {
+        SetHandle(handle);
+    }
+
+    public override bool IsInvalid => handle == IntPtr.Zero;
+
+    protected override bool ReleaseHandle()
+    {
+        TraceExporterNative.ddog_trace_exporter_error_free(handle);
+        return true;
+    }
+
+    public TraceExporterException ToException()
+    {
+        return new TraceExporterException(Marshal.PtrToStructure<TraceExporterError>(handle));
+    }
+
+    public void ThrowIfError()
+    {
+        if (!IsInvalid)
+        {
+            throw ToException();
+        }
+    }
+}
diff --git a/tracer/src/Datadog.Trace/LibDatadog/TraceExporter.cs b/tracer/src/Datadog.Trace/LibDatadog/TraceExporter.cs
new file mode 100644
index 000000000000..15612e9c84ce
--- /dev/null
+++ b/tracer/src/Datadog.Trace/LibDatadog/TraceExporter.cs
@@ -0,0 +1,69 @@
+// <copyright file="TraceExporter.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>
+
+using System;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Datadog.Trace.Agent;
+using Datadog.Trace.Logging;
+
+namespace Datadog.Trace.LibDatadog;
+
+internal class TraceExporter : SafeHandle, IApi
+{
+    private static readonly IDatadogLogger StaticLog = DatadogLogging.GetLoggerFor<TraceExporter>();
+
+    private readonly TraceExporterConfiguration _configuration;
+    private readonly IDatadogLogger _log;
+
+    public TraceExporter(
+        TraceExporterConfiguration configuration,
+        IDatadogLogger log = null)
+        : base(IntPtr.Zero, true)
+    {
+        _log = log ?? StaticLog;
+        _configuration = configuration;
+
+        _log.Debug("Creating new TraceExporter");
+        var errPtr = TraceExporterNative.ddog_trace_exporter_new(out var ptr, _configuration);
+        errPtr.ThrowIfError();
+        SetHandle(ptr);
+    }
+
+    public override bool IsInvalid => handle == IntPtr.Zero;
+
+    public Task<bool> SendTracesAsync(ArraySegment<byte> traces, int numberOfTraces, bool statsComputationEnabled, long numberOfDroppedP0Traces, long numberOfDroppedP0Spans, bool appsecStandaloneEnabled)
+    {
+        _log.Debug<int>("Sending {Count} traces to the Datadog Agent.", numberOfTraces);
+
+        // Pin the array to get a pointer to the data
+        // This is recommended if using UnsafeAddrOfPinnedArrayElement to avoid the GC moving the array
+        var tracesHandle = GCHandle.Alloc(traces.Array, GCHandleType.Pinned);
+        var tracesSlice = new ByteSlice
+        {
+            Ptr = Marshal.UnsafeAddrOfPinnedArrayElement(traces.Array, traces.Offset),
+            Len = (UIntPtr)traces.Count
+        };
+
+        var responsePtr = IntPtr.Zero;
+        using var error = TraceExporterNative.ddog_trace_exporter_send(this, tracesSlice, (UIntPtr)numberOfTraces, ref responsePtr);
+        tracesHandle.Free();
+        error.ThrowIfError();
+
+        return Task.FromResult(true);
+    }
+
+    public Task<bool> SendStatsAsync(StatsBuffer stats, long bucketDuration)
+    {
+        _log.Debug("No-op: stats computation happens in the data pipeline.");
+        return Task.FromResult(true);
+    }
+
+    protected override bool ReleaseHandle()
+    {
+        TraceExporterNative.ddog_trace_exporter_free(handle);
+        return true;
+    }
+}
diff --git a/tracer/src/Datadog.Trace/LibDatadog/TraceExporterConfiguration.cs b/tracer/src/Datadog.Trace/LibDatadog/TraceExporterConfiguration.cs
new file mode 100644
index 000000000000..5f8c2602d9dc
--- /dev/null
+++ b/tracer/src/Datadog.Trace/LibDatadog/TraceExporterConfiguration.cs
@@ -0,0 +1,139 @@
+// <copyright file="TraceExporterConfiguration.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>
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Datadog.Trace.LibDatadog;
+
+/// <summary>
+/// Represents a configuration for the trace exporter.
+/// </summary>
+internal class TraceExporterConfiguration : SafeHandle
+{
+    private string _url;
+    private string _traceVersion;
+    private string _language;
+    private string _languageVersion;
+    private string _languageInterpreter;
+    private string _hostname;
+    private string _env;
+    private string _version;
+    private string _service;
+
+    public TraceExporterConfiguration()
+        : base(IntPtr.Zero, true)
+    {
+        TraceExporterNative.ddog_trace_exporter_config_new(out var ptr);
+        SetHandle(ptr);
+    }
+
+    public override bool IsInvalid => handle == IntPtr.Zero;
+
+    public string Url
+    {
+        get => _url;
+        set
+        {
+            _url = value;
+            using var error = TraceExporterNative.ddog_trace_exporter_config_set_url(this, new CharSlice(value));
+            error.ThrowIfError();
+        }
+    }
+
+    public string TraceVersion
+    {
+        get => _traceVersion;
+        set
+        {
+            _traceVersion = value;
+            using var error = TraceExporterNative.ddog_trace_exporter_config_set_tracer_version(this, new CharSlice(value));
+            error.ThrowIfError();
+        }
+    }
+
+    public string Language
+    {
+        get => _language;
+        set
+        {
+            _language = value;
+            using var error = TraceExporterNative.ddog_trace_exporter_config_set_language(this, new CharSlice(value));
+            error.ThrowIfError();
+        }
+    }
+
+    public string LanguageVersion
+    {
+        get => _languageVersion;
+        set
+        {
+            _languageVersion = value;
+            using var error = TraceExporterNative.ddog_trace_exporter_config_set_lang_version(this, new CharSlice(value));
+            error.ThrowIfError();
+        }
+    }
+
+    public string LanguageInterpreter
+    {
+        get => _languageInterpreter;
+        set
+        {
+            _languageInterpreter = value;
+            using var error = TraceExporterNative.ddog_trace_exporter_config_set_lang_interpreter(this, new CharSlice(value));
+            error.ThrowIfError();
+        }
+    }
+
+    public string Hostname
+    {
+        get => _hostname;
+        set
+        {
+            _hostname = value;
+            using var error = TraceExporterNative.ddog_trace_exporter_config_set_hostname(this, new CharSlice(value));
+            error.ThrowIfError();
+        }
+    }
+
+    public string Env
+    {
+        get => _env;
+        set
+        {
+            _env = value;
+            using var error = TraceExporterNative.ddog_trace_exporter_config_set_env(this, new CharSlice(value));
+            error.ThrowIfError();
+        }
+    }
+
+    public string Version
+    {
+        get => _version;
+        set
+        {
+            _version = value;
+            using var error = TraceExporterNative.ddog_trace_exporter_config_set_version(this, new CharSlice(value));
+            error.ThrowIfError();
+        }
+    }
+
+    public string Service
+    {
+        get => _service;
+        set
+        {
+            _service = value;
+            using var error = TraceExporterNative.ddog_trace_exporter_config_set_service(this, new CharSlice(value));
+            error.ThrowIfError();
+        }
+    }
+
+    protected override bool ReleaseHandle()
+    {
+        TraceExporterNative.ddog_trace_exporter_config_free(handle);
+        return true;
+    }
+}
diff --git a/tracer/src/Datadog.Trace/LibDatadog/TraceExporterError.cs b/tracer/src/Datadog.Trace/LibDatadog/TraceExporterError.cs
new file mode 100644
index 000000000000..e0d1406ba076
--- /dev/null
+++ b/tracer/src/Datadog.Trace/LibDatadog/TraceExporterError.cs
@@ -0,0 +1,27 @@
+// <copyright file="TraceExporterError.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>
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Datadog.Trace.LibDatadog;
+
+/// <summary>
+/// Represents errors that can occur when exporting traces.
+/// </summary>
+[StructLayout(LayoutKind.Sequential)]
+internal struct TraceExporterError
+{
+    /// <summary>
+    /// The error code representing the domain of the error.
+    /// Consumers can use this to determine how to handle the error.
+    /// </summary>
+    internal ErrorCode Code;
+
+    /// <summary>
+    /// Human-readable error message describing the error.
+    /// </summary>
+    internal IntPtr Msg;
+}
diff --git a/tracer/src/Datadog.Trace/LibDatadog/TraceExporterException.cs b/tracer/src/Datadog.Trace/LibDatadog/TraceExporterException.cs
new file mode 100644
index 000000000000..4e2497b86955
--- /dev/null
+++ b/tracer/src/Datadog.Trace/LibDatadog/TraceExporterException.cs
@@ -0,0 +1,23 @@
+// <copyright file="TraceExporterException.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>
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Datadog.Trace.LibDatadog;
+
+/// <summary>
+/// Represents an exception thrown by the libdatadog library.
+/// </summary>
+internal class TraceExporterException : Exception
+{
+    public TraceExporterException(TraceExporterError exporterError)
+        : base(Marshal.PtrToStringAnsi(exporterError.Msg))
+    {
+        ErrorCode = exporterError.Code;
+    }
+
+    public ErrorCode ErrorCode { get; }
+}
diff --git a/tracer/src/Datadog.Trace/LibDatadog/TraceExporterInputFormat.cs b/tracer/src/Datadog.Trace/LibDatadog/TraceExporterInputFormat.cs
new file mode 100644
index 000000000000..25c4baf6dd04
--- /dev/null
+++ b/tracer/src/Datadog.Trace/LibDatadog/TraceExporterInputFormat.cs
@@ -0,0 +1,22 @@
+// <copyright file="TraceExporterInputFormat.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>
+
+namespace Datadog.Trace.LibDatadog;
+
+/// <summary>
+/// Represents the format of the input traces, as expected by the trace exporter.
+/// </summary>
+internal enum TraceExporterInputFormat
+{
+    /// <summary>
+    /// Used when the traces are sent to the agent without processing. The whole payload is sent as is to the agent.
+    /// </summary>
+    Proxy = 0,
+
+    /// <summary>
+    /// Version 0.4 of the trace exporter format.
+    /// </summary>
+    V04 = 1,
+}
diff --git a/tracer/src/Datadog.Trace/LibDatadog/TraceExporterNative.cs b/tracer/src/Datadog.Trace/LibDatadog/TraceExporterNative.cs
new file mode 100644
index 000000000000..0dda346653ab
--- /dev/null
+++ b/tracer/src/Datadog.Trace/LibDatadog/TraceExporterNative.cs
@@ -0,0 +1,61 @@
+// <copyright file="TraceExporterNative.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>
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Datadog.Trace.LibDatadog;
+
+#pragma warning disable SA1300
+internal class TraceExporterNative
+{
+    private const string DllName = "datadog_profiling_ffi";
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern ErrorHandle ddog_trace_exporter_new(out IntPtr outHandle, SafeHandle config);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern void ddog_trace_exporter_error_free(IntPtr error);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern void ddog_trace_exporter_free(IntPtr handle);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern ErrorHandle ddog_trace_exporter_send(SafeHandle handle, ByteSlice trace, UIntPtr traceCount, ref IntPtr response);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern void ddog_trace_exporter_config_new(out IntPtr outHandle);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern void ddog_trace_exporter_config_free(IntPtr handle);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern ErrorHandle ddog_trace_exporter_config_set_url(SafeHandle config, CharSlice url);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern ErrorHandle ddog_trace_exporter_config_set_tracer_version(SafeHandle config, CharSlice version);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern ErrorHandle ddog_trace_exporter_config_set_language(SafeHandle config, CharSlice lang);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern ErrorHandle ddog_trace_exporter_config_set_lang_version(SafeHandle config, CharSlice version);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern ErrorHandle ddog_trace_exporter_config_set_lang_interpreter(SafeHandle config, CharSlice interpreter);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern ErrorHandle ddog_trace_exporter_config_set_hostname(SafeHandle config, CharSlice hostname);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern ErrorHandle ddog_trace_exporter_config_set_env(SafeHandle config, CharSlice env);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern ErrorHandle ddog_trace_exporter_config_set_version(SafeHandle config, CharSlice version);
+
+    [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
+    internal static extern ErrorHandle ddog_trace_exporter_config_set_service(SafeHandle config, CharSlice service);
+}
+#pragma warning restore SA1300
diff --git a/tracer/src/Datadog.Trace/LibDatadog/TraceExporterOutputFormat.cs b/tracer/src/Datadog.Trace/LibDatadog/TraceExporterOutputFormat.cs
new file mode 100644
index 000000000000..3cee7f3dd2e9
--- /dev/null
+++ b/tracer/src/Datadog.Trace/LibDatadog/TraceExporterOutputFormat.cs
@@ -0,0 +1,22 @@
+// <copyright file="TraceExporterOutputFormat.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>
+
+namespace Datadog.Trace.LibDatadog;
+
+/// <summary>
+/// Represents the format of the output traces, as expected by the trace exporter.
+/// </summary>
+internal enum TraceExporterOutputFormat
+{
+    /// <summary>
+    /// Version 0.4 of the trace exporter format.
+    /// </summary>
+    V04 = 0,
+
+    /// <summary>
+    /// Version 0.7 of the trace exporter format.
+    /// </summary>
+    V07 = 1,
+}
diff --git a/tracer/src/Datadog.Trace/TracerManagerFactory.cs b/tracer/src/Datadog.Trace/TracerManagerFactory.cs
index 74cb7cbc0b5f..b1910564b8ac 100644
--- a/tracer/src/Datadog.Trace/TracerManagerFactory.cs
+++ b/tracer/src/Datadog.Trace/TracerManagerFactory.cs
@@ -14,6 +14,7 @@
 using Datadog.Trace.ContinuousProfiler;
 using Datadog.Trace.DataStreamsMonitoring;
 using Datadog.Trace.DogStatsd;
+using Datadog.Trace.LibDatadog;
 using Datadog.Trace.Logging;
 using Datadog.Trace.Logging.DirectSubmission;
 using Datadog.Trace.Logging.TracerFlare;
@@ -342,13 +343,35 @@ protected virtual ISpanSampler GetSpanSampler(TracerSettings settings)
         protected virtual IAgentWriter GetAgentWriter(TracerSettings settings, IDogStatsd statsd, Action<Dictionary<string, float>> updateSampleRates, IDiscoveryService discoveryService)
         {
             var apiRequestFactory = TracesTransportStrategy.Get(settings.Exporter);
-            var api = new Api(apiRequestFactory, statsd, updateSampleRates, settings.Exporter.PartialFlushEnabled);
+            var api = GetApi(settings, statsd, updateSampleRates, apiRequestFactory, settings.Exporter.PartialFlushEnabled);
 
             var statsAggregator = StatsAggregator.Create(api, settings, discoveryService);
 
             return new AgentWriter(api, statsAggregator, statsd, maxBufferSize: settings.TraceBufferSize, batchInterval: settings.TraceBatchInterval, appsecStandaloneEnabled: settings.AppsecStandaloneEnabledInternal);
         }
 
+        private IApi GetApi(TracerSettings settings, IDogStatsd statsd, Action<Dictionary<string, float>> updateSampleRates, IApiRequestFactory apiRequestFactory, bool partialFlushEnabled)
+        {
+            if (settings.DataPipelineEnabled)
+            {
+                var configuration = new TraceExporterConfiguration
+                {
+                    Url = settings.Exporter.AgentUri.ToString(),
+                    TraceVersion = TracerConstants.AssemblyVersion,
+                    Env = settings.Environment,
+                    Version = settings.ServiceVersion,
+                    Service = settings.ServiceName,
+                    Hostname = settings.Exporter.AgentUri.ToString(),
+                    Language = ".NET",
+                    LanguageVersion = FrameworkDescription.Instance.ProductVersion,
+                    LanguageInterpreter = ".NET"
+                };
+                return new TraceExporter(configuration);
+            }
+
+            return new Api(apiRequestFactory, statsd, updateSampleRates, partialFlushEnabled);
+        }
+
         protected virtual IDiscoveryService GetDiscoveryService(TracerSettings settings)
             => DiscoveryService.Create(settings.Exporter);
 
diff --git a/tracer/test/Datadog.Trace.IntegrationTests/LibDatadog/TraceExporterTests.cs b/tracer/test/Datadog.Trace.IntegrationTests/LibDatadog/TraceExporterTests.cs
new file mode 100644
index 000000000000..3b253681178f
--- /dev/null
+++ b/tracer/test/Datadog.Trace.IntegrationTests/LibDatadog/TraceExporterTests.cs
@@ -0,0 +1,63 @@
+// <copyright file="TraceExporterTests.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>
+
+using System;
+using System.Threading.Tasks;
+using Datadog.Trace.Agent.DiscoveryService;
+using Datadog.Trace.Configuration;
+using Datadog.Trace.TestHelpers;
+using Xunit;
+
+namespace Datadog.Trace.IntegrationTests.LibDatadog;
+
+public class TraceExporterTests
+{
+    [Fact]
+    public async Task SendsTracesUsingDataPipeline()
+    {
+        using var agent = MockTracerAgent.Create(null, TcpPortProvider.GetOpenPort());
+
+        agent.CustomResponses[MockTracerResponseType.Traces] = new MockTracerResponse
+        {
+            StatusCode = 200,
+            ContentType = "application/msgpack",
+            Response = """
+                       {
+                           "rate_by_service": {
+                               "service:default-service,env:test": 1.0,
+                               "service:,env:": 0.8
+                           }
+                       }
+                       """
+        };
+
+        var settings = TracerSettings.Create(new()
+        {
+            { ConfigurationKeys.StatsComputationEnabled, true },
+            { ConfigurationKeys.ServiceName, "default-service" },
+            { ConfigurationKeys.ServiceVersion, "v1" },
+            { ConfigurationKeys.Environment, "test" },
+            { ConfigurationKeys.AgentUri, $"http://localhost:{agent.Port}" },
+            { ConfigurationKeys.DataPipelineEnabled, "true" },
+        });
+
+        var discovery = DiscoveryService.Create(settings.Exporter);
+        var tracer = new Tracer(settings, agentWriter: null, sampler: null, scopeManager: null, statsd: null, discoveryService: discovery);
+
+        using var span = tracer.StartSpan("operationName");
+        span.ResourceName = "resourceName";
+        span.Type = "test";
+        span.Finish();
+
+        await tracer.TracerManager.ShutdownAsync();
+        var recordedSpans = agent.WaitForSpans(1);
+        Assert.Equal(1, recordedSpans.Count);
+
+        var recordedSpan = recordedSpans[0];
+        Assert.Equal("operationName", recordedSpan.Name);
+        Assert.Equal("resourceName", recordedSpan.Resource);
+        Assert.Equal("default-service", recordedSpan.Service);
+    }
+}
diff --git a/tracer/test/Datadog.Trace.IntegrationTests/StatsTests.cs b/tracer/test/Datadog.Trace.IntegrationTests/StatsTests.cs
index 9f80ad3aa5d0..33b7628c3a31 100644
--- a/tracer/test/Datadog.Trace.IntegrationTests/StatsTests.cs
+++ b/tracer/test/Datadog.Trace.IntegrationTests/StatsTests.cs
@@ -54,6 +54,7 @@ public async Task SendsStatsWithProcessing_Normalizer()
                 { ConfigurationKeys.ServiceVersion, "v1" },
                 { ConfigurationKeys.Environment, "test" },
                 { ConfigurationKeys.AgentUri, $"http://localhost:{agent.Port}" },
+                { ConfigurationKeys.DataPipelineEnabled, "false" },
             });
 
             var discovery = DiscoveryService.Create(settings.Exporter);
@@ -198,6 +199,7 @@ public async Task SendsStatsWithProcessing_Obfuscator()
                 { ConfigurationKeys.ServiceVersion, "v1" },
                 { ConfigurationKeys.Environment, "test" },
                 { ConfigurationKeys.AgentUri, $"http://localhost:{agent.Port}" },
+                { ConfigurationKeys.DataPipelineEnabled, "false" },
             });
 
             var discovery = DiscoveryService.Create(settings.Exporter);
@@ -356,6 +358,7 @@ private async Task SendStatsHelper(bool statsComputationEnabled, bool expectStat
                         { ConfigurationKeys.ServiceVersion, "V" },
                         { ConfigurationKeys.Environment, "Test" },
                         { ConfigurationKeys.AgentUri, $"http://localhost:{agent.Port}" },
+                        { ConfigurationKeys.DataPipelineEnabled, "false" },
                     }));
 
             var discovery = DiscoveryService.Create(settings.Exporter);