Skip to content

Commit

Permalink
[AzureMonitorLiveMetrics] poc refactor metrics (#40669)
Browse files Browse the repository at this point in the history
* refactor doublebuffer

* refactor to remove OTel Metrics API.

* revert change to DocumentBuffer & DoubleBuffer

* refactor to use Document to track metrics

* cleanup

* struct
  • Loading branch information
TimothyMothra authored Dec 11, 2023
1 parent d810209 commit 5235895
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 312 deletions.
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary> Additional properties used to calculate metrics. </summary>
internal partial class DocumentIngress
{
public bool Extension_IsSuccess { get; set; }
public double Extension_Duration { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.Json;
using System.Threading;
using Azure.Core;
Expand Down Expand Up @@ -57,8 +58,13 @@ public ResponseWithHeaders<object, QuickPulseSDKClientAPIsPingHeaders> 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<object, QuickPulseSDKClientAPIsPingHeaders>(value, headers, message.Response);
}
default:
Expand Down Expand Up @@ -104,8 +110,13 @@ public ResponseWithHeaders<object, QuickPulseSDKClientAPIsPostHeaders> 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<object, QuickPulseSDKClientAPIsPostHeaders>(value, headers, message.Response);
}
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,6 @@ namespace Azure.Monitor.OpenTelemetry.LiveMetrics.Internals
{
internal static class LiveMetricConstants
{
internal const string LiveMetricMeterName = "LiveMetricMeterName";

/// <summary>
/// These values are used for Metric Instruments.
/// </summary>
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";
}

/// <summary>
/// These are the values that are sent to Application Insights Live Metrics.
/// </summary>
Expand All @@ -58,31 +31,5 @@ internal static class MetricId
internal const string MemoryCommittedBytesMetricIdValue = @"\Memory\Committed Bytes";
internal const string ProcessorTimeMetricIdValue = @"\Processor(_Total)\% Processor Time";
}

/// <summary>
/// This dictionary maps Instrumentation-Safe names (key)
/// to Application Insights Live Metrics names (value).
/// </summary>
internal static readonly IReadOnlyDictionary<string, string> InstrumentNameToMetricId = new Dictionary<string, string>()
{
// 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,
};
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// This class encapsulates metrics sent to Live Metrics.
/// </summary>
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<Models.MetricPoint> 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
};
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// This partial class encapsulates all the metrics that are tracked and reported to the Live Metrics service.
/// </summary>
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<Models.MetricPoint> 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
};
}
}
}
Loading

0 comments on commit 5235895

Please sign in to comment.