Skip to content
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 @@ -239,6 +239,10 @@
<assembly fullname="System.Diagnostics.DiagnosticSource">
<type fullname="System.Diagnostics.DiagnosticListener" />
<type fullname="System.Diagnostics.DistributedContextPropagator" />
<type fullname="System.Diagnostics.Metrics.Instrument" />
<type fullname="System.Diagnostics.Metrics.MeasurementCallback`1" />
<type fullname="System.Diagnostics.Metrics.Meter" />
<type fullname="System.Diagnostics.Metrics.MeterListener" />
</assembly>
<assembly fullname="System.Diagnostics.Process">
<type fullname="System.Diagnostics.Process" />
Expand Down
12 changes: 5 additions & 7 deletions tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,25 +326,23 @@ internal static void InitializeNoNativeParts(Stopwatch sw = null)
{
if (Tracer.Instance.Settings.OpenTelemetryMetricsEnabled)
{
Log.Debug("Initializing OTel Metrics Exporter.");
if (Tracer.Instance.Settings.OpenTelemetryMeterNames.Length > 0)
{
Log.Debug("Initializing OTel Metrics Exporter.");
OTelMetrics.OtlpMetricsExporter.Initialize();
}
else
{
Log.Debug("No meters were found for DD_METRICS_OTEL_METER_NAMES, OTel Metrics Exporter won't be initialized.");
Log.Debug("Initializing OTel Metrics Reader.");
OTelMetrics.MetricReader.Initialize();
}
}
}
catch (Exception ex)
{
Log.Error(ex, "Error initializing OTel Metrics Exporter");
Log.Error(ex, "Error initializing OTel Metrics Reader");
}
#else
if (Tracer.Instance.Settings.OpenTelemetryMetricsEnabled)
{
Log.Information("Unable to initialize OTel Metrics collection, this is only available starting with .NET 6.0..");
Log.Information("Unable to initialize OTel Metrics collection, this is only available starting with .NET 6.0.");
}
#endif

Expand Down
27 changes: 27 additions & 0 deletions tracer/src/Datadog.Trace/OTelMetrics/AggregationTemporality.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// <copyright file="AggregationTemporality.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>

#if NET6_0_OR_GREATER

#nullable enable

namespace Datadog.Trace.OTelMetrics;

/// <summary>
/// Represents the aggregation temporality of a metric.
/// </summary>
public enum AggregationTemporality
{
/// <summary>
/// Delta temporality, representing changes since the last measurement.
/// </summary>
Delta = 0,

/// <summary>
/// Cumulative temporality, representing the total value since the start.
/// </summary>
Cumulative = 1,
}
#endif
52 changes: 52 additions & 0 deletions tracer/src/Datadog.Trace/OTelMetrics/InstrumentType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// <copyright file="InstrumentType.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>

#if NET6_0_OR_GREATER

#nullable enable

namespace Datadog.Trace.OTelMetrics;

/// <summary>
/// Represents the type of OpenTelemetry instrument
/// </summary>
internal enum InstrumentType
{
/// <summary>
/// Counter instrument - monotonic, additive
/// </summary>
Counter = 0,

/// <summary>
/// UpDownCounter instrument - non-monotonic, additive
/// </summary>
UpDownCounter = 1,

/// <summary>
/// Histogram instrument - statistical distribution
/// </summary>
Histogram = 2,

/// <summary>
/// Asynchronous Counter instrument - monotonic, additive, callback-based
/// </summary>
ObservableCounter = 3,

/// <summary>
/// Asynchronous UpDownCounter instrument - non-monotonic, additive, callback-based
/// </summary>
ObservableUpDownCounter = 4,

/// <summary>
/// Gauge instrument - non-additive, last value
/// </summary>
Gauge = 5,

/// <summary>
/// Asynchronous Gauge instrument - non-additive, last value, callback-based
/// </summary>
ObservableGauge = 6
}
#endif
90 changes: 90 additions & 0 deletions tracer/src/Datadog.Trace/OTelMetrics/MetricPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// <copyright file="MetricPoint.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>

#if NET6_0_OR_GREATER

#nullable enable

using System;
using System.Collections.Generic;
using System.Threading;

namespace Datadog.Trace.OTelMetrics;

internal class MetricPoint(string instrumentName, string meterName, InstrumentType instrumentType, AggregationTemporality? temporality, Dictionary<string, object?> tags)
{
internal static readonly double[] DefaultHistogramBounds = [0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000];
private readonly long[] _runningBucketCounts = instrumentType == InstrumentType.Histogram ? new long[DefaultHistogramBounds.Length + 1] : [];
private readonly object _histogramLock = new();
private long _runningCountValue;
private double _runningDoubleValue;
private double _runningMin = double.PositiveInfinity;
private double _runningMax = double.NegativeInfinity;

public string InstrumentName { get; } = instrumentName;

public string MeterName { get; } = meterName;

public InstrumentType InstrumentType { get; } = instrumentType;

public AggregationTemporality? AggregationTemporality { get; } = temporality;

public Dictionary<string, object?> Tags { get; } = tags;

internal long RunningCount => _runningCountValue;

internal double RunningSum => _runningDoubleValue;

internal double RunningMin => _runningMin;

internal double RunningMax => _runningMax;

internal long[] RunningBucketCounts => _runningBucketCounts;

internal void UpdateCounter(double value)
{
lock (_histogramLock)
{
_runningDoubleValue += value;
}
}

internal void UpdateGauge(double value)
{
Interlocked.Exchange(ref _runningDoubleValue, value);
}

internal void UpdateHistogram(double value)
{
var bucketIndex = FindBucketIndex(value);

lock (_histogramLock)
{
unchecked
{
_runningCountValue++;
_runningDoubleValue += value;
_runningBucketCounts[bucketIndex]++;
}

_runningMin = Math.Min(_runningMin, value);
_runningMax = Math.Max(_runningMax, value);
}
}

private static int FindBucketIndex(double value)
{
for (var i = 0; i < DefaultHistogramBounds.Length; i++)
{
if (value <= DefaultHistogramBounds[i])
{
return i;
}
}

return DefaultHistogramBounds.Length;
}
}
#endif
88 changes: 88 additions & 0 deletions tracer/src/Datadog.Trace/OTelMetrics/MetricReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// <copyright file="MetricReader.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>

#if NET6_0_OR_GREATER

#nullable enable

using System;
using System.Threading;
using Datadog.Trace.Logging;

namespace Datadog.Trace.OTelMetrics;

internal static class MetricReader
{
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(MetricReader));

private static System.Diagnostics.Metrics.MeterListener? _meterListenerInstance;
private static int _initialized;
private static int _stopped;

public static bool IsRunning =>
Interlocked.CompareExchange(ref _initialized, 1, 1) == 1 &&
Interlocked.CompareExchange(ref _stopped, 0, 0) == 0;

public static void Initialize()
{
if (Interlocked.CompareExchange(ref _initialized, 1, 0) == 1)
{
return;
}

var meterListener = new System.Diagnostics.Metrics.MeterListener();

#if NET6_0 || NET7_0 || NET8_0
// Ensures instruments are fully de-registered on Dispose() for 6–8
// Static lambda => no captures/allocations
meterListener.MeasurementsCompleted = static (_, __) => { };
#endif

meterListener.InstrumentPublished = MetricReaderHandler.OnInstrumentPublished;

meterListener.SetMeasurementEventCallback<byte>(MetricReaderHandler.OnMeasurementRecordedByte);
meterListener.SetMeasurementEventCallback<short>(MetricReaderHandler.OnMeasurementRecordedShort);
meterListener.SetMeasurementEventCallback<int>(MetricReaderHandler.OnMeasurementRecordedInt);
meterListener.SetMeasurementEventCallback<long>(MetricReaderHandler.OnMeasurementRecordedLong);
meterListener.SetMeasurementEventCallback<float>(MetricReaderHandler.OnMeasurementRecordedFloat);
meterListener.SetMeasurementEventCallback<double>(MetricReaderHandler.OnMeasurementRecordedDouble);

meterListener.Start();

Interlocked.Exchange(ref _meterListenerInstance, meterListener);
Interlocked.Exchange(ref _stopped, 0);

Log.Debug("MeterListener initialized successfully.");
}

public static void Stop()
{
var listener = Interlocked.Exchange(ref _meterListenerInstance, null);
if (listener is IDisposable disposableListener)
{
disposableListener.Dispose();
Interlocked.Exchange(ref _stopped, 1);
Interlocked.Exchange(ref _initialized, 0);
Log.Debug("MeterListener stopped.");
}
}

internal static void CollectObservableInstruments()
{
if (_meterListenerInstance != null)
{
try
{
_meterListenerInstance.RecordObservableInstruments();
}
catch (Exception ex)
{
Log.Warning(ex, "Error collecting observable instruments.");
}
}
}
}
#endif

Loading
Loading