Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
still adding changes
  • Loading branch information
harsimar committed Sep 5, 2025
commit e062c1805e08c9b12d916d964e3089b14c14c88b
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public static TracerProviderBuilder AddAzureMonitorTraceExporter(

builder.AddProcessor(new CompositeProcessor<Activity>(new BaseProcessor<Activity>[]
{
new StandardMetricsExtractionProcessor(new AzureMonitorMetricExporter(exporterOptions)),
new StandardMetricsExtractionProcessor(new AzureMonitorMetricExporter(exporterOptions), new PerfCounterItemCounts()),
new BatchActivityExportProcessor(new AzureMonitorTraceExporter(exporterOptions))
}));
});
Expand All @@ -116,6 +116,7 @@ public static MeterProviderBuilder AddAzureMonitorMetricExporter(
TokenCredential credential = null,
string name = null)
{
// TODO: determine if we want to add support for perf counters here
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
Expand Down Expand Up @@ -199,6 +200,7 @@ public static OpenTelemetryLoggerOptions AddAzureMonitorLogExporter(
options.Credential ??= credential;
}

// TODO: determine if we want to add support for perf counters here
return loggerOptions.AddProcessor(new BatchLogRecordExportProcessor(new AzureMonitorLogExporter(options)));
}

Expand Down Expand Up @@ -267,6 +269,7 @@ public static LoggerProviderBuilder AddAzureMonitorLogExporter(
sp.EnsureNoUseAzureMonitorExporterRegistrations();

// TODO: Do we need provide an option to alter BatchExportLogRecordProcessorOptions?
// TODO: Do we need to support perf counters with this?
return new BatchLogRecordExportProcessor(new AzureMonitorLogExporter(exporterOptions));
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ namespace Azure.Monitor.OpenTelemetry.Exporter
internal sealed class ExporterRegistrationHostedService : IHostedService
{
private readonly IServiceProvider _serviceProvider;

public ExporterRegistrationHostedService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
Expand All @@ -49,6 +48,7 @@ public Task StopAsync(CancellationToken cancellationToken)
private static void Initialize(IServiceProvider serviceProvider)
{
Debug.Assert(serviceProvider != null, "serviceProvider was null");
PerfCounterItemCounts itemCounts = new PerfCounterItemCounts();

var tracerProvider = serviceProvider!.GetService<TracerProvider>();
if (tracerProvider != null)
Expand All @@ -68,7 +68,7 @@ private static void Initialize(IServiceProvider serviceProvider)
// TODO: Add Ai Sampler.
tracerProvider.AddProcessor(new CompositeProcessor<Activity>(new BaseProcessor<Activity>[]
{
new StandardMetricsExtractionProcessor(new AzureMonitorMetricExporter(exporterOptions)),
new StandardMetricsExtractionProcessor(new AzureMonitorMetricExporter(exporterOptions), itemCounts),
new BatchActivityExportProcessor(new AzureMonitorTraceExporter(exporterOptions))
}));
}
Expand All @@ -89,13 +89,18 @@ private static void Initialize(IServiceProvider serviceProvider)

loggerProvider.AddProcessor(new CompositeProcessor<LogRecord>(new BaseProcessor<LogRecord>[]
{
new PerfCounterLogProcessor(itemCounts),
new LiveMetricsLogProcessor(manager),
new BatchLogRecordExportProcessor(exporter)
}));
}
else
{
loggerProvider.AddProcessor(new BatchLogRecordExportProcessor(exporter));
loggerProvider.AddProcessor(new CompositeProcessor<LogRecord>(new BaseProcessor<LogRecord>[]
{
new PerfCounterLogProcessor(itemCounts),
new BatchLogRecordExportProcessor(exporter)
}));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Threading;

namespace Azure.Monitor.OpenTelemetry.Exporter.Internals
{
/// <summary>
/// Thread-safe counters for performance metrics.
/// </summary>
internal sealed class PerfCounterItemCounts
{
private long _totalRequestCount = 0;
private long _totalExceptionCount = 0;

/// <summary>
/// Atomically increments the total request count.
/// </summary>
/// <returns>The new value after incrementing.</returns>
public long IncrementRequestCount()
{
return Interlocked.Increment(ref _totalRequestCount);
Copy link
Contributor

Choose a reason for hiding this comment

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

Request rate should be derived from http.server.request.duration metrics by taking the histogram count and dividing by 60 (the aggregation window) to get requests per second.

}

/// <summary>
/// Atomically increments the total exception count.
/// </summary>
/// <returns>The new value after incrementing.</returns>
public long IncrementExceptionCount()
{
return Interlocked.Increment(ref _totalExceptionCount);
Copy link
Contributor

Choose a reason for hiding this comment

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

Exception rate should be derived from process.runtime.dotnet.exceptions.count metrics from the System.Runtime meter by calculating count / 60 to get exceptions per second.

}

/// <summary>
/// Atomically reads the current total request count.
/// </summary>
/// <returns>The current request count.</returns>
public long ReadRequestCount()
{
return Interlocked.Read(ref _totalRequestCount);
}

/// <summary>
/// Atomically reads the current total exception count.
/// </summary>
/// <returns>The current exception count.</returns>
public long ReadExceptionCount()
{
return Interlocked.Read(ref _totalExceptionCount);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Logs;

namespace Azure.Monitor.OpenTelemetry.Exporter.Internals
{
internal sealed class PerfCounterLogProcessor : BaseProcessor<LogRecord>
{
private bool _disposed;
private readonly PerfCounterItemCounts _itemCounts;

internal PerfCounterLogProcessor(PerfCounterItemCounts itemCounts)
{
_itemCounts = itemCounts;
}

public override void OnEnd(LogRecord data)
{
if (data.Exception is not null)
{
_itemCounts.IncrementExceptionCount();
}
}

protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// cleanup if needed
}
_disposed = true;
}
base.Dispose(disposing);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Azure.Monitor.OpenTelemetry.Exporter.Internals
{
internal sealed class PerformanceCounter
internal sealed class PerformanceCounter : IDisposable
{
private readonly MeterProvider _meterProvider;
private readonly Meter _perfCounterMeter;
Expand All @@ -23,17 +23,19 @@ internal sealed class PerformanceCounter
private readonly int _processorCount = Environment.ProcessorCount;

// state for telemetry item related counts
private long _totalExceptionCount = 0;
private readonly PerfCounterItemCounts _itemCounts;
private long _lastExceptionRateCount = 0;
private DateTimeOffset _lastExceptionRateTime = DateTimeOffset.UtcNow;
private long _totalRequestCount = 0;
private long _lastRequestRateCount = 0;
private DateTimeOffset _lastRequestRateTime = DateTimeOffset.UtcNow;

public PerformanceCounter(MeterProvider meterProvider)
private bool _disposed;

public PerformanceCounter(MeterProvider meterProvider, PerfCounterItemCounts itemCounts)
{
_meterProvider = meterProvider;
_perfCounterMeter = new Meter(PerfCounterConstants.PerfCounterMeterName);
_itemCounts = itemCounts;

// Create observable gauges for perf counter
_exceptionRateGauge = _perfCounterMeter.CreateObservableGauge(
Expand Down Expand Up @@ -62,21 +64,11 @@ public PerformanceCounter(MeterProvider meterProvider)
description: "Process private bytes gauge");
}

public void IncrementExceptionCount()
{
Interlocked.Increment(ref _totalExceptionCount);
}

public void IncrementRequestCount()
{
Interlocked.Increment(ref _totalRequestCount);
}

// Placeholder methods for gauge callbacks
private double GetExceptionRate()
{
var currentTime = DateTimeOffset.UtcNow;
var totalExceptions = Interlocked.Read(ref _totalExceptionCount);
var totalExceptions = _itemCounts.ReadExceptionCount();
var intervalData = totalExceptions - _lastExceptionRateCount;
var elapsedSeconds = (currentTime - _lastExceptionRateTime).TotalSeconds;

Expand All @@ -94,7 +86,7 @@ private double GetExceptionRate()
private double GetRequestRate()
{
var currentTime = DateTimeOffset.UtcNow;
var totalRequests = Interlocked.Read(ref _totalRequestCount);
var totalRequests = _itemCounts.ReadRequestCount();
var intervalRequests = totalRequests - _lastRequestRateCount;
var elapsedSec = (currentTime - _lastRequestRateTime).TotalSeconds;

Expand Down Expand Up @@ -174,5 +166,16 @@ private bool TryCalculateProcessCpu(out double calculatedValue)
calculatedValue = diff * 100.0 / period;
return true;
}

public void Dispose()
{
if (_disposed) return;
try
{
_perfCounterMeter?.Dispose(); // Do NOT dispose _meterProvider (shared elsewhere)
}
catch { }
_disposed = true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ internal sealed class StandardMetricsExtractionProcessor : BaseProcessor<Activit
private readonly Meter _meter;
private readonly Histogram<double> _requestDuration;
private readonly Histogram<double> _dependencyDuration;
private readonly PerformanceCounter _performanceCounter;
private readonly PerfCounterItemCounts _itemCounts;

internal static readonly IReadOnlyDictionary<string, string> s_standardMetricNameMapping = new Dictionary<string, string>()
{
Expand All @@ -28,7 +30,7 @@ internal sealed class StandardMetricsExtractionProcessor : BaseProcessor<Activit

internal AzureMonitorResource? StandardMetricResource => _resource ??= ParentProvider?.GetResource().CreateAzureMonitorResource();

internal StandardMetricsExtractionProcessor(AzureMonitorMetricExporter metricExporter)
internal StandardMetricsExtractionProcessor(AzureMonitorMetricExporter metricExporter, PerfCounterItemCounts itemCounts)
{
_meterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter(StandardMetricConstants.StandardMetricMeterName)
Expand All @@ -37,8 +39,11 @@ internal StandardMetricsExtractionProcessor(AzureMonitorMetricExporter metricExp
{ TemporalityPreference = MetricReaderTemporalityPreference.Delta })
.Build();
_meter = new Meter(StandardMetricConstants.StandardMetricMeterName);
_itemCounts = itemCounts;
_performanceCounter = new PerformanceCounter(_meterProvider, _itemCounts);
_requestDuration = _meter.CreateHistogram<double>(StandardMetricConstants.RequestDurationInstrumentName);
_dependencyDuration = _meter.CreateHistogram<double>(StandardMetricConstants.DependencyDurationInstrumentName);

}

public override void OnEnd(Activity activity)
Expand All @@ -59,6 +64,16 @@ public override void OnEnd(Activity activity)
ReportDependencyDurationMetric(activity);
}
}
if (activity.Events != null)
{
foreach (ref readonly var @event in activity.EnumerateEvents())
{
if (@event.Name == SemanticConventions.AttributeExceptionEventName)
{
_itemCounts.IncrementExceptionCount();
}
}
}
}

private void ReportRequestDurationMetric(Activity activity)
Expand All @@ -83,6 +98,7 @@ private void ReportRequestDurationMetric(Activity activity)

// Report metric
_requestDuration.Record(activity.Duration.TotalMilliseconds, tags);
_itemCounts.IncrementRequestCount();
}

private void ReportDependencyDurationMetric(Activity activity)
Expand Down Expand Up @@ -144,6 +160,7 @@ protected override void Dispose(bool disposing)
{
_meterProvider?.Dispose();
_meter?.Dispose();
_performanceCounter?.Dispose();
}
catch (Exception)
{
Expand Down