From 5235895a22dc11d7a9fd855defde775127e79e9c Mon Sep 17 00:00:00 2001 From: Timothy Mothra Date: Mon, 11 Dec 2023 10:06:55 -0800 Subject: [PATCH] [AzureMonitorLiveMetrics] poc refactor metrics (#40669) * refactor doublebuffer * refactor to remove OTel Metrics API. * revert change to DocumentBuffer & DoubleBuffer * refactor to use Document to track metrics * cleanup * struct --- .../Customizations/Models/DocumentIngress.cs | 14 ++ .../QuickPulseSDKClientAPIsRestClient.cs | 19 ++- .../src/Internals/LiveMetricConstants.cs | 53 ------- .../src/Internals/LiveMetricsBuffer.cs | 139 ++++++++++++++++++ .../src/Internals/Manager.Metrics.cs | 120 +++++++++++++++ .../src/Internals/Manager.cs | 36 +---- .../src/Internals/MetricsContainer.cs | 94 ------------ .../src/LiveMetricsExtractionProcessor.cs | 54 ++----- .../src/LiveMetricsMetricExporter.cs | 85 ----------- 9 files changed, 302 insertions(+), 312 deletions(-) create mode 100644 sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Customizations/Models/DocumentIngress.cs create mode 100644 sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/LiveMetricsBuffer.cs create mode 100644 sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/Manager.Metrics.cs delete mode 100644 sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/MetricsContainer.cs delete mode 100644 sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsMetricExporter.cs diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Customizations/Models/DocumentIngress.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Customizations/Models/DocumentIngress.cs new file mode 100644 index 000000000000..3828103fc177 --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Customizations/Models/DocumentIngress.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics; + +namespace Azure.Monitor.OpenTelemetry.LiveMetrics.Models +{ + /// Additional properties used to calculate metrics. + internal partial class DocumentIngress + { + public bool Extension_IsSuccess { get; set; } + public double Extension_Duration { get; set; } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Customizations/QuickPulseSDKClientAPIsRestClient.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Customizations/QuickPulseSDKClientAPIsRestClient.cs index d7788cfd9c85..c4f8444e70e8 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Customizations/QuickPulseSDKClientAPIsRestClient.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Customizations/QuickPulseSDKClientAPIsRestClient.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text.Json; using System.Threading; using Azure.Core; @@ -57,8 +58,13 @@ public ResponseWithHeaders PingCusto case 503: { ServiceError value = default; - using var document = JsonDocument.Parse(message.Response.ContentStream); - value = ServiceError.DeserializeServiceError(document.RootElement); + if (message.Response.Headers.ContentLength != 0) + { + using var document = JsonDocument.Parse(message.Response.ContentStream); + value = ServiceError.DeserializeServiceError(document.RootElement); + } + + Debug.WriteLine($"{DateTime.Now}: Ping FAILED: {message.Response.Status} {message.Response.ReasonPhrase}."); return ResponseWithHeaders.FromValue(value, headers, message.Response); } default: @@ -104,8 +110,13 @@ public ResponseWithHeaders PostCusto case 503: { ServiceError value = default; - using var document = JsonDocument.Parse(message.Response.ContentStream); - value = ServiceError.DeserializeServiceError(document.RootElement); + if (message.Response.Headers.ContentLength != 0) + { + using var document = JsonDocument.Parse(message.Response.ContentStream); + value = ServiceError.DeserializeServiceError(document.RootElement); + } + + Debug.WriteLine($"{DateTime.Now}: Post FAILED: {message.Response.Status} {message.Response.ReasonPhrase}."); return ResponseWithHeaders.FromValue(value, headers, message.Response); } default: diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/LiveMetricConstants.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/LiveMetricConstants.cs index bd4c0faaaab8..04047bf254e3 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/LiveMetricConstants.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/LiveMetricConstants.cs @@ -7,33 +7,6 @@ namespace Azure.Monitor.OpenTelemetry.LiveMetrics.Internals { internal static class LiveMetricConstants { - internal const string LiveMetricMeterName = "LiveMetricMeterName"; - - /// - /// These values are used for Metric Instruments. - /// - internal static class InstrumentName - { - // REQUESTS - internal const string RequestDurationInstrumentName = "RequestDurationLiveMetric"; - internal const string RequestsInstrumentName = "RequestsLiveMetric"; - internal const string RequestsSucceededPerSecondInstrumentName = "RequestsSucceededPerSecondLiveMetric"; - internal const string RequestsFailedPerSecondInstrumentName = "RequestsFailedPerSecondLiveMetric"; - - // DEPENDENCIES - internal const string DependencyDurationInstrumentName = "DependencyDurationLiveMetric"; - internal const string DependencyInstrumentName = "DependencyLiveMetric"; - internal const string DependencySucceededPerSecondInstrumentName = "DependencySucceededPerSecondLiveMetric"; - internal const string DependencyFailedPerSecondInstrumentName = "DependencyFailedPerSecondLiveMetric"; - - // EXCEPTIONS - internal const string ExceptionsPerSecondInstrumentName = "ExceptionsPerSecondLiveMetric"; - - // PERFORMANCE COUNTERS - internal const string MemoryCommittedBytesInstrumentName = "CommittedBytesLiveMetric"; - internal const string ProcessorTimeInstrumentName = "ProcessorTimeBytesLiveMetric"; - } - /// /// These are the values that are sent to Application Insights Live Metrics. /// @@ -58,31 +31,5 @@ internal static class MetricId internal const string MemoryCommittedBytesMetricIdValue = @"\Memory\Committed Bytes"; internal const string ProcessorTimeMetricIdValue = @"\Processor(_Total)\% Processor Time"; } - - /// - /// This dictionary maps Instrumentation-Safe names (key) - /// to Application Insights Live Metrics names (value). - /// - internal static readonly IReadOnlyDictionary InstrumentNameToMetricId = new Dictionary() - { - // REQUESTS - [InstrumentName.RequestDurationInstrumentName] = MetricId.RequestDurationMetricIdValue, - [InstrumentName.RequestsInstrumentName] = MetricId.RequestsPerSecondMetricIdValue, - [InstrumentName.RequestsSucceededPerSecondInstrumentName] = MetricId.RequestsSucceededPerSecondMetricIdValue, - [InstrumentName.RequestsFailedPerSecondInstrumentName] = MetricId.RequestsFailedPerSecondMetricIdValue, - - // DEPENDENCIES - [InstrumentName.DependencyDurationInstrumentName] = MetricId.DependencyDurationMetricIdValue, - [InstrumentName.DependencyInstrumentName] = MetricId.DependenciesPerSecondMetricIdValue, - [InstrumentName.DependencySucceededPerSecondInstrumentName] = MetricId.DependencySucceededPerSecondMetricIdValue, - [InstrumentName.DependencyFailedPerSecondInstrumentName] = MetricId.DependencyFailedPerSecondMetricIdValue, - - // EXCEPTIONS - [InstrumentName.ExceptionsPerSecondInstrumentName] = MetricId.ExceptionsPerSecondMetricIdValue, - - // PERFORMANCE COUNTERS - [InstrumentName.MemoryCommittedBytesInstrumentName] = MetricId.MemoryCommittedBytesMetricIdValue, - [InstrumentName.ProcessorTimeInstrumentName] = MetricId.ProcessorTimeMetricIdValue, - }; } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/LiveMetricsBuffer.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/LiveMetricsBuffer.cs new file mode 100644 index 000000000000..854216455e5b --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/LiveMetricsBuffer.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Azure.Monitor.OpenTelemetry.LiveMetrics.Internals +{ + /// + /// This class encapsulates metrics sent to Live Metrics. + /// + internal struct LiveMetricsBuffer + { + // REQUEST + internal long RequestsCount; + internal double RequestsDuration; + internal long RequestsSuccededCount; + internal long RequestsFailedCount; + + // DEPENDENCY + internal long DependenciesCount; + internal double DependenciesDuration; + internal long DependenciesSuccededCount; + internal long DependenciesFailedCount; + + // EXCEPTIONS + internal long ExceptionsCount; + + public void RecordRequestSucceeded(double duration) + { + RequestsCount++; + RequestsDuration += duration; + RequestsSuccededCount++; + } + + public void RecordRequestFailed(double duration) + { + RequestsCount++; + RequestsDuration += duration; + RequestsFailedCount++; + } + + public void RecordDependencySucceeded(double duration) + { + DependenciesCount++; + DependenciesDuration += duration; + DependenciesSuccededCount++; + } + + public void RecordDependencyFailed(double duration) + { + DependenciesCount++; + DependenciesDuration += duration; + DependenciesFailedCount++; + } + + public void RecordException() + { + ExceptionsCount++; + } + + public IEnumerable GetMetricPoints() + { + // REQUESTS + if (RequestsCount > 0) + { + yield return new Models.MetricPoint + { + Name = LiveMetricConstants.MetricId.RequestsPerSecondMetricIdValue, + Value = RequestsCount, + Weight = 1 + }; + + yield return new Models.MetricPoint + { + Name = LiveMetricConstants.MetricId.RequestDurationMetricIdValue, + Value = (float)(RequestsDuration / RequestsCount), + Weight = 1 + }; + + yield return new Models.MetricPoint + { + Name = LiveMetricConstants.MetricId.RequestsSucceededPerSecondMetricIdValue, + Value = RequestsSuccededCount, + Weight = 1 + }; + + yield return new Models.MetricPoint + { + Name = LiveMetricConstants.MetricId.RequestsFailedPerSecondMetricIdValue, + Value = RequestsFailedCount, + Weight = 1 + }; + } + + // DEPENDENCIES + if (DependenciesCount > 0) + { + yield return new Models.MetricPoint + { + Name = LiveMetricConstants.MetricId.DependenciesPerSecondMetricIdValue, + Value = DependenciesCount, + Weight = 1 + }; + + yield return new Models.MetricPoint + { + Name = LiveMetricConstants.MetricId.DependencyDurationMetricIdValue, + Value = (float)(DependenciesDuration / DependenciesCount), + Weight = 1 + }; + + yield return new Models.MetricPoint + { + Name = LiveMetricConstants.MetricId.DependencySucceededPerSecondMetricIdValue, + Value = DependenciesSuccededCount, + Weight = 1 + }; + + yield return new Models.MetricPoint + { + Name = LiveMetricConstants.MetricId.DependencyFailedPerSecondMetricIdValue, + Value = DependenciesFailedCount, + Weight = 1 + }; + } + + // EXCEPTIONS + if (ExceptionsCount > 0) + { + yield return new Models.MetricPoint + { + Name = LiveMetricConstants.MetricId.ExceptionsPerSecondMetricIdValue, + Value = ExceptionsCount, + Weight = 1 + }; + } + } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/Manager.Metrics.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/Manager.Metrics.cs new file mode 100644 index 000000000000..70702c9d3bf8 --- /dev/null +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/Manager.Metrics.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Azure.Monitor.OpenTelemetry.Exporter.Internals; +using Azure.Monitor.OpenTelemetry.LiveMetrics.Models; + +namespace Azure.Monitor.OpenTelemetry.LiveMetrics.Internals +{ + /// + /// This partial class encapsulates all the metrics that are tracked and reported to the Live Metrics service. + /// + internal partial class Manager + { + internal readonly DoubleBuffer _documentBuffer = new(); + + private PerformanceCounter _performanceCounter_ProcessorTime = new PerformanceCounter(categoryName: "Processor", counterName: "% Processor Time", instanceName: "_Total"); + private PerformanceCounter _performanceCounter_CommittedBytes = new PerformanceCounter(categoryName: "Memory", counterName: "Committed Bytes"); + + public MonitoringDataPoint GetDataPoint() + { + var dataPoint = new MonitoringDataPoint + { + Version = SdkVersionUtils.s_sdkVersion.Truncate(SchemaConstants.Tags_AiInternalSdkVersion_MaxLength), + InvariantVersion = 5, + Instance = liveMetricsResource?.RoleInstance ?? "UNKNOWN_INSTANCE", + RoleName = liveMetricsResource?.RoleName, + MachineName = Environment.MachineName, // TODO: MOVE TO PLATFORM + // TODO: Is the Stream ID expected to be unique per post? Testing suggests this is not necessary. + StreamId = _streamId, + Timestamp = DateTime.UtcNow, + //TODO: Provide feedback to service team to get this removed, it not a part of AI SDK. + TransmissionTime = DateTime.UtcNow, + IsWebApp = IsWebAppRunningInAzure(), + PerformanceCollectionSupported = true, + // AI SDK relies on PerformanceCounter to collect CPU and Memory metrics. + // Follow up with service team to get this removed for OTEL based live metrics. + // TopCpuProcesses = null, + // TODO: Configuration errors are thrown when filter is applied. + // CollectionConfigurationErrors = null, + }; + + LiveMetricsBuffer liveMetricsBuffer = new(); + DocumentBuffer filledBuffer = _documentBuffer.FlipDocumentBuffers(); + foreach (var item in filledBuffer.ReadAllAndClear()) + { + dataPoint.Documents.Add(item); + + if (item.DocumentType == DocumentIngressDocumentType.Request) + { + // Export needs to divide by count to get the average. + // this.AIRequestDurationAveInMs = requestCount > 0 ? (double)requestDurationInTicks / TimeSpan.TicksPerMillisecond / requestCount : 0; + if (item.Extension_IsSuccess) + { + liveMetricsBuffer.RecordRequestSucceeded(item.Extension_Duration); + } + else + { + liveMetricsBuffer.RecordRequestFailed(item.Extension_Duration); + } + } + else if (item.DocumentType == DocumentIngressDocumentType.RemoteDependency) + { + // Export needs to divide by count to get the average. + // this.AIDependencyCallDurationAveInMs = dependencyCount > 0 ? (double)dependencyDurationInTicks / TimeSpan.TicksPerMillisecond / dependencyCount : 0; + // Export DependencyDurationLiveMetric, Meter: LiveMetricMeterName + // (2023 - 11 - 03T23: 20:56.0282406Z, 2023 - 11 - 03T23: 21:00.9830153Z] Histogram Value: Sum: 798.9463000000001 Count: 7 Min: 4.9172 Max: 651.8997 + if (item.Extension_IsSuccess) + { + liveMetricsBuffer.RecordDependencySucceeded(item.Extension_Duration); + } + else + { + liveMetricsBuffer.RecordDependencyFailed(item.Extension_Duration); + } + } + else if (item.DocumentType == DocumentIngressDocumentType.Exception) + { + liveMetricsBuffer.RecordException(); + } + else + { + Debug.WriteLine($"Unknown DocumentType: {item.DocumentType}"); + } + } + + foreach (var metricPoint in liveMetricsBuffer.GetMetricPoints()) + { + dataPoint.Metrics.Add(metricPoint); + } + + foreach (var metricPoint in CollectPerfCounters()) + { + dataPoint.Metrics.Add(metricPoint); + } + + return dataPoint; + } + + public IEnumerable CollectPerfCounters() + { + // PERFORMANCE COUNTERS + yield return new Models.MetricPoint + { + Name = LiveMetricConstants.MetricId.MemoryCommittedBytesMetricIdValue, + Value = _performanceCounter_CommittedBytes.NextValue(), + Weight = 1 + }; + + yield return new Models.MetricPoint + { + Name = LiveMetricConstants.MetricId.ProcessorTimeMetricIdValue, + Value = _performanceCounter_ProcessorTime.NextValue(), + Weight = 1 + }; + } + } +} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/Manager.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/Manager.cs index a95046292ba7..7b8bcbc28c8e 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/Manager.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/Manager.cs @@ -26,7 +26,6 @@ internal partial class Manager internal static bool? s_isAzureWebApp = null; internal readonly State _state = new(); - internal readonly MetricsContainer _metricsContainer; // TODO: WE COULD REMOVE THE READONLY AND DISPOSE THIS WHENEVER LIVEMETRICS IS OFF. public Manager(LiveMetricsExporterOptions options, IPlatform platform) { @@ -37,8 +36,6 @@ public Manager(LiveMetricsExporterOptions options, IPlatform platform) _timer = new Timer(callback: OnCallback, state: null, dueTime: Timeout.Infinite, period: Timeout.Infinite); - _metricsContainer = new MetricsContainer(); - if (options.EnableLiveMetrics) { SetPingTimer(); @@ -167,38 +164,7 @@ private void OnPost(object state) { Debug.WriteLine($"{DateTime.Now}: OnPost invoked."); - var dataPoint = new MonitoringDataPoint - { - Version = SdkVersionUtils.s_sdkVersion.Truncate(SchemaConstants.Tags_AiInternalSdkVersion_MaxLength), - InvariantVersion = 5, - Instance = liveMetricsResource?.RoleInstance ?? "UNKNOWN_INSTANCE", - RoleName = liveMetricsResource?.RoleName, - MachineName = Environment.MachineName, // TODO: MOVE TO PLATFORM - // TODO: Is the Stream ID expected to be unique per post? Testing suggests this is not necessary. - StreamId = _streamId, - Timestamp = DateTime.UtcNow, - //TODO: Provide feedback to service team to get this removed, it not a part of AI SDK. - TransmissionTime = DateTime.UtcNow, - IsWebApp = IsWebAppRunningInAzure(), - PerformanceCollectionSupported = true, - // AI SDK relies on PerformanceCounter to collect CPU and Memory metrics. - // Follow up with service team to get this removed for OTEL based live metrics. - // TopCpuProcesses = null, - // TODO: Configuration errors are thrown when filter is applied. - // CollectionConfigurationErrors = null, - }; - - DocumentBuffer filledBuffer = _metricsContainer._doubleBuffer.FlipDocumentBuffers(); - foreach (var item in filledBuffer.ReadAllAndClear()) - { - dataPoint.Documents.Add(item); - } - - var metricPoints = _metricsContainer.Collect(); - foreach (var metricPoint in metricPoints) - { - dataPoint.Metrics.Add(metricPoint); - } + var dataPoint = GetDataPoint(); var response = _quickPulseSDKClientAPIsRestClient.PostCustom( ikey: _connectionVars.InstrumentationKey, diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/MetricsContainer.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/MetricsContainer.cs deleted file mode 100644 index 4d2affcf8b79..000000000000 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/Internals/MetricsContainer.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Diagnostics.Metrics; -using System.Diagnostics; -using OpenTelemetry.Metrics; -using OpenTelemetry; -using System.Collections.Concurrent; - -namespace Azure.Monitor.OpenTelemetry.LiveMetrics.Internals -{ - /// - /// This class encapsulates all the metrics that are tracked and reported to th eLive Metrics service. - /// - internal class MetricsContainer - { - private readonly ConcurrentQueue> _queue = new(); - - private Meter? _meter; - private string _meterName; - private MeterProvider? _meterProvider; - private BaseExportingMetricReader _metricReader; - - internal readonly DoubleBuffer _doubleBuffer = new(); - - private PerformanceCounter _performanceCounter_ProcessorTime = new PerformanceCounter(categoryName: "Processor", counterName: "% Processor Time", instanceName: "_Total"); - private PerformanceCounter _performanceCounter_CommittedBytes = new PerformanceCounter(categoryName: "Memory", counterName: "Committed Bytes"); - - internal readonly Counter _requests; - internal readonly Histogram _requestDuration; - internal readonly Counter _requestSucceededPerSecond; - internal readonly Counter _requestFailedPerSecond; - internal readonly Counter _dependency; - internal readonly Histogram _dependencyDuration; - internal readonly Counter _dependencySucceededPerSecond; - internal readonly Counter _dependencyFailedPerSecond; - internal readonly Counter _exceptionsPerSecond; - - private readonly Instrument _myObservableGauge1; - private readonly Instrument _myObservableGauge2; - - public MetricsContainer() - { - var uniqueId = Guid.NewGuid(); - _meterName = $"{LiveMetricConstants.LiveMetricMeterName}{uniqueId}"; - _meter = new Meter(_meterName, "1.0"); - - // REQUEST - _requests = _meter.CreateCounter(LiveMetricConstants.InstrumentName.RequestsInstrumentName); - _requestDuration = _meter.CreateHistogram(LiveMetricConstants.InstrumentName.RequestDurationInstrumentName); - _requestSucceededPerSecond = _meter.CreateCounter(LiveMetricConstants.InstrumentName.RequestsSucceededPerSecondInstrumentName); - _requestFailedPerSecond = _meter.CreateCounter(LiveMetricConstants.InstrumentName.RequestsFailedPerSecondInstrumentName); - - // DEPENDENCY - _dependency = _meter.CreateCounter(LiveMetricConstants.InstrumentName.DependencyInstrumentName); - _dependencyDuration = _meter.CreateHistogram(LiveMetricConstants.InstrumentName.DependencyDurationInstrumentName); - _dependencySucceededPerSecond = _meter.CreateCounter(LiveMetricConstants.InstrumentName.DependencySucceededPerSecondInstrumentName); - _dependencyFailedPerSecond = _meter.CreateCounter(LiveMetricConstants.InstrumentName.DependencyFailedPerSecondInstrumentName); - - // EXCEPTIONS - _exceptionsPerSecond = _meter.CreateCounter(LiveMetricConstants.InstrumentName.ExceptionsPerSecondInstrumentName); - - // PERFORMANCE COUNTERS - _myObservableGauge1 = _meter.CreateObservableGauge(LiveMetricConstants.InstrumentName.MemoryCommittedBytesInstrumentName, () => - { - return new Measurement(value: _performanceCounter_CommittedBytes.NextValue()); - }); - - _myObservableGauge2 = _meter.CreateObservableGauge(LiveMetricConstants.InstrumentName.ProcessorTimeInstrumentName, () => - { - return new Measurement(value: _performanceCounter_ProcessorTime.NextValue()); - }); - - // INITIALIZE METRICS SDK - _metricReader = new BaseExportingMetricReader(new LiveMetricsMetricExporter(_queue)); - - _meterProvider = Sdk.CreateMeterProviderBuilder() - .AddMeter(_meterName) - .AddReader(_metricReader) - .Build(); - } - - public IEnumerable Collect() - { - _metricReader.Collect(); - - return _queue.TryDequeue(out var metricPoint) - ? metricPoint - : Array.Empty(); - } - } -} diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsExtractionProcessor.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsExtractionProcessor.cs index 5ddb33831e66..dcaf288a53fa 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsExtractionProcessor.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsExtractionProcessor.cs @@ -17,7 +17,6 @@ internal sealed class LiveMetricsExtractionProcessor : BaseProcessor private bool _disposed; private LiveMetricsResource? _resource; private readonly Manager _manager; - //private readonly DoubleBuffer _doubleBuffer; internal LiveMetricsResource? LiveMetricsResource => _resource ??= ParentProvider?.GetResource().CreateAzureMonitorResource(); @@ -52,50 +51,17 @@ public override void OnEnd(Activity activity) if (activity.Kind == ActivityKind.Server || activity.Kind == ActivityKind.Consumer) { - _manager._metricsContainer._requests.Add(1); - // Export needs to divide by count to get the average. - // this.AIRequestDurationAveInMs = requestCount > 0 ? (double)requestDurationInTicks / TimeSpan.TicksPerMillisecond / requestCount : 0; - _manager._metricsContainer._requestDuration.Record(activity.Duration.TotalMilliseconds); - if (IsSuccess(activity, statusCodeAttributeValue)) - { - _manager._metricsContainer._requestSucceededPerSecond.Add(1); - } - else - { - _manager._metricsContainer._requestFailedPerSecond.Add(1); - } - AddRequestDocument(activity, statusCodeAttributeValue); } else { - _manager._metricsContainer._dependency.Add(1); - // Export needs to divide by count to get the average. - // this.AIDependencyCallDurationAveInMs = dependencyCount > 0 ? (double)dependencyDurationInTicks / TimeSpan.TicksPerMillisecond / dependencyCount : 0; - // Export DependencyDurationLiveMetric, Meter: LiveMetricMeterName - // (2023 - 11 - 03T23: 20:56.0282406Z, 2023 - 11 - 03T23: 21:00.9830153Z] Histogram Value: Sum: 798.9463000000001 Count: 7 Min: 4.9172 Max: 651.8997 - _manager._metricsContainer._dependencyDuration.Record(activity.Duration.TotalMilliseconds); - if (IsSuccess(activity, statusCodeAttributeValue)) - { - _manager._metricsContainer._dependencySucceededPerSecond.Add(1); - } - else - { - _manager._metricsContainer._dependencyFailedPerSecond.Add(1); - } - - AddRemoteDependencyDocument(activity); + AddRemoteDependencyDocument(activity, statusCodeAttributeValue); } if (activity.Events != null) { foreach (ref readonly var @event in activity.EnumerateEvents()) { - if (@event.Name == SemanticConventions.AttributeExceptionEventName) - { - _manager._metricsContainer._exceptionsPerSecond.Add(1); - } - string? exceptionType = null; string? exceptionMessage = null; @@ -145,15 +111,15 @@ private void AddExceptionDocument(string? exceptionType, string? exceptionMessag { ExceptionType = exceptionType, ExceptionMessage = exceptionMessage, - DocumentType = DocumentIngressDocumentType.Request, + DocumentType = DocumentIngressDocumentType.Exception, // TODO: DocumentStreamIds = new List(), // TODO: Properties = new Dictionary(), - Validate with UX team if this is needed. }; - _manager._metricsContainer._doubleBuffer.WriteDocument(exceptionDocumentIngress); + _manager._documentBuffer.WriteDocument(exceptionDocumentIngress); } - private void AddRemoteDependencyDocument(Activity activity) + private void AddRemoteDependencyDocument(Activity activity, string? statusCodeAttributeValue) { RemoteDependencyDocumentIngress remoteDependencyDocumentIngress = new() { @@ -166,12 +132,15 @@ private void AddRemoteDependencyDocument(Activity activity) Duration = activity.Duration < SchemaConstants.RequestData_Duration_LessThanDays ? activity.Duration.ToString("c", CultureInfo.InvariantCulture) : SchemaConstants.Duration_MaxValue, - DocumentType = DocumentIngressDocumentType.Request, + DocumentType = DocumentIngressDocumentType.RemoteDependency, // TODO: DocumentStreamIds = new List(), // TODO: Properties = new Dictionary(), - Validate with UX team if this is needed. + + Extension_IsSuccess = IsSuccess(activity, statusCodeAttributeValue), + Extension_Duration = activity.Duration.TotalMilliseconds, }; - _manager._metricsContainer._doubleBuffer.WriteDocument(remoteDependencyDocumentIngress); + _manager._documentBuffer.WriteDocument(remoteDependencyDocumentIngress); } private void AddRequestDocument(Activity activity, string? statusCodeAttributeValue) @@ -189,9 +158,12 @@ private void AddRequestDocument(Activity activity, string? statusCodeAttributeVa DocumentType = DocumentIngressDocumentType.Request, // TODO: DocumentStreamIds = new List(), // TODO: Properties = new Dictionary(), - Validate with UX team if this is needed. + + Extension_IsSuccess = IsSuccess(activity, statusCodeAttributeValue), + Extension_Duration = activity.Duration.TotalMilliseconds, }; - _manager._metricsContainer._doubleBuffer.WriteDocument(requestDocumentIngress); + _manager._documentBuffer.WriteDocument(requestDocumentIngress); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsMetricExporter.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsMetricExporter.cs deleted file mode 100644 index 29505300905e..000000000000 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.LiveMetrics/src/LiveMetricsMetricExporter.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using Azure.Monitor.OpenTelemetry.LiveMetrics.Internals; -using OpenTelemetry; -using OpenTelemetry.Metrics; - -namespace Azure.Monitor.OpenTelemetry.LiveMetrics -{ - /// - /// Converts OTel Metrics to Azure Monitor Metrics. - /// - internal class LiveMetricsMetricExporter : BaseExporter - { - private readonly ConcurrentQueue> _metricPoints; - - /// - /// TODO. - /// - public LiveMetricsMetricExporter(ConcurrentQueue> metricPoints) - { - _metricPoints = metricPoints; - } - - /// - public override ExportResult Export(in Batch batch) - { - var list = new List(capacity: (int)batch.Count); // TODO: POSSIBLE OVERFLOW EXCEPTION (long -> int) - - foreach (var metric in batch) - { - foreach (ref readonly var metricPoint in metric.GetMetricPoints()) - { - //Debug.WriteLine(LiveMetricConstants.Mappings[metric.Name]); - - switch (metric.MetricType) - { - case MetricType.LongSum: - list.Add(new Models.MetricPoint - { - Name = LiveMetricConstants.InstrumentNameToMetricId[metric.Name], - // potential for minor precision loss implicitly going from long->double - // see: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/numeric-conversions#implicit-numeric-conversions - Value = metricPoint.GetSumLong(), - Weight = 1 - }); - break; - case MetricType.Histogram: - long histogramCount = metricPoint.GetHistogramCount(); - - list.Add(new Models.MetricPoint - { - Name = LiveMetricConstants.InstrumentNameToMetricId[metric.Name], - // When you convert double to float, the double value is rounded to the nearest float value. - // If the double value is too small or too large to fit into the float type, the result is zero or infinity. - // see: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/numeric-conversions#explicit-numeric-conversions - Value = (float)(metricPoint.GetHistogramSum() / histogramCount), - Weight = histogramCount <= int.MaxValue ? (int?)histogramCount : null // TODO: POSSIBLE OVERFLOW EXCEPTION (long -> int) - }); - break; - case MetricType.DoubleGauge: - list.Add(new Models.MetricPoint - { - Name = LiveMetricConstants.InstrumentNameToMetricId[metric.Name], - Value = (float)metricPoint.GetGaugeLastValueDouble(), - Weight = 1 - }); - break; - default: - Debug.WriteLine($"Unsupported Metric Type {metric.MetricType} {metric.Name}"); - break; - } - } - } - - _metricPoints.Enqueue(list); - Debug.Write($"Enqueue {_metricPoints.Count}. Count {list.Count}\n"); - - return ExportResult.Success; - } - } -}