Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AzureMonitorLiveMetrics] POC #40001

Merged
merged 3 commits into from
Nov 15, 2023
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
@@ -1,3 +1,22 @@
namespace Azure.Monitor.OpenTelemetry.LiveMetrics
{
public partial class LiveMetricsExporterOptions : Azure.Core.ClientOptions
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
{
public LiveMetricsExporterOptions() { }
public string ConnectionString { get { throw null; } set { } }
public Azure.Core.TokenCredential Credential { get { throw null; } set { } }
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
public bool EnableLiveMetrics { get { throw null; } set { } }
}
public static partial class LiveMetricsExtensions
{
public static OpenTelemetry.Trace.TracerProviderBuilder AddLiveMetrics(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action<Azure.Monitor.OpenTelemetry.LiveMetrics.LiveMetricsExporterOptions> configure = null, string name = null) { throw null; }
}
public partial class LiveMetricsTraceExporter : OpenTelemetry.BaseExporter<System.Diagnostics.Activity>
{
public LiveMetricsTraceExporter(Azure.Monitor.OpenTelemetry.LiveMetrics.LiveMetricsExporterOptions options) { }
public override OpenTelemetry.ExportResult Export(in OpenTelemetry.Batch<System.Diagnostics.Activity> batch) { throw null; }
}
}
namespace Azure.Monitor.OpenTelemetry.LiveMetrics.Models
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PackageReference Include="Azure.Core" />
<PackageReference Include="OpenTelemetry" />
<PackageReference Include="OpenTelemetry.Exporter.Console" VersionOverride="1.6.0" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" VersionOverride="7.0.0" />
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is needed to read Performance Counters (CPU & Memory) to enable POC. Will work on removing this in the future.

Copy link
Contributor

@rajkumar-rangaraj rajkumar-rangaraj Nov 15, 2023

Choose a reason for hiding this comment

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

We may need to keep this one forever, there is no good way to get this value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm fine with this dependency, as System.Diagnostic is a core dependency of OTel.
Application Insights is able to read perf counters without an extra dependency. I need more time to investigate how that works. For short term, this is fine. :)

Copy link
Contributor

Choose a reason for hiding this comment

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

</ItemGroup>

<!-- Shared sorce from Azure.Monitor.OpenTelemetry.Exporter -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#nullable disable

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading;
using Azure.Core;
using Azure.Monitor.OpenTelemetry.LiveMetrics.Models;

namespace Azure.Monitor.OpenTelemetry.LiveMetrics
{
internal partial class QuickPulseSDKClientAPIsRestClient
{
/// <summary> SDK ping. </summary>
/// <param name="ikey"> The ikey of the target Application Insights component that displays server info sent by /QuickPulseService.svc/ping. </param>
/// <param name="apikey"> Deprecated. An alternative way to pass api key. Use AAD auth instead. </param>
/// <param name="xMsQpsTransmissionTime"> Timestamp when SDK transmits the metrics and documents to QuickPulse. A 8-byte long type of ticks. </param>
/// <param name="xMsQpsMachineName"> Computer name where AI SDK lives. QuickPulse uses machine name with instance name as a backup. </param>
/// <param name="xMsQpsInstanceName"> Service instance name where AI SDK lives. QuickPulse uses machine name with instance name as a backup. </param>
/// <param name="xMsQpsStreamId"> Identifies an AI SDK as trusted agent to report metrics and documents. </param>
/// <param name="xMsQpsRoleName"> Cloud role name for which SDK reports metrics and documents. </param>
/// <param name="xMsQpsInvariantVersion"> Version/generation of the data contract (MonitoringDataPoint) between SDK and QuickPulse. </param>
/// <param name="xMsQpsConfigurationEtag"> An encoded string that indicates whether the collection configuration is changed. </param>
/// <param name="monitoringDataPoint"> Data contract between SDK and QuickPulse. /QuickPulseService.svc/ping uses this as a backup source of machine name, instance name and invariant version. </param>
/// <param name="cancellationToken"> The cancellation token to use. </param>
/// <exception cref="ArgumentNullException"> <paramref name="ikey"/> is null. </exception>
public ResponseWithHeaders<object, QuickPulseSDKClientAPIsPingHeaders> PingCustom(string ikey, string apikey = null, int? xMsQpsTransmissionTime = null, string xMsQpsMachineName = null, string xMsQpsInstanceName = null, string xMsQpsStreamId = null, string xMsQpsRoleName = null, string xMsQpsInvariantVersion = null, string xMsQpsConfigurationEtag = null, MonitoringDataPoint monitoringDataPoint = null, CancellationToken cancellationToken = default)
{
if (ikey == null)
{
throw new ArgumentNullException(nameof(ikey));
}

using var message = CreatePingRequest(ikey, apikey, xMsQpsTransmissionTime, xMsQpsMachineName, xMsQpsInstanceName, xMsQpsStreamId, xMsQpsRoleName, xMsQpsInvariantVersion, xMsQpsConfigurationEtag, monitoringDataPoint);
_pipeline.Send(message, cancellationToken);
var headers = new QuickPulseSDKClientAPIsPingHeaders(message.Response);
switch (message.Response.Status)
{
case 200:
{
CollectionConfigurationInfo value = default;
if (message.Response.Headers.ContentLength != 0)
{
using var document = JsonDocument.Parse(message.Response.ContentStream);
value = CollectionConfigurationInfo.DeserializeCollectionConfigurationInfo(document.RootElement);
}
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
return ResponseWithHeaders.FromValue<object, QuickPulseSDKClientAPIsPingHeaders>(value, headers, message.Response);
}
case 400:
case 401:
case 403:
case 404:
case 500:
case 503:
{
ServiceError value = default;
using var document = JsonDocument.Parse(message.Response.ContentStream);
value = ServiceError.DeserializeServiceError(document.RootElement);
return ResponseWithHeaders.FromValue<object, QuickPulseSDKClientAPIsPingHeaders>(value, headers, message.Response);
}
default:
throw new RequestFailedException(message.Response);
}
}

/// <summary> SDK post. </summary>
/// <param name="ikey"> The ikey of the target Application Insights component that displays metrics and documents sent by /QuickPulseService.svc/post. </param>
/// <param name="apikey"> An alternative way to pass api key. Deprecated. Use AAD authentication instead. </param>
/// <param name="xMsQpsConfigurationEtag"> An encoded string that indicates whether the collection configuration is changed. </param>
/// <param name="xMsQpsTransmissionTime"> Timestamp when SDK transmits the metrics and documents to QuickPulse. A 8-byte long type of ticks. </param>
/// <param name="monitoringDataPoints"> Data contract between SDK and QuickPulse. /QuickPulseService.svc/post uses this to publish metrics and documents to the backend QuickPulse server. </param>
/// <param name="cancellationToken"> The cancellation token to use. </param>
/// <exception cref="ArgumentNullException"> <paramref name="ikey"/> is null. </exception>
public ResponseWithHeaders<object, QuickPulseSDKClientAPIsPostHeaders> PostCustom(string ikey, string apikey = null, string xMsQpsConfigurationEtag = null, int? xMsQpsTransmissionTime = null, IEnumerable<MonitoringDataPoint> monitoringDataPoints = null, CancellationToken cancellationToken = default)
{
if (ikey == null)
{
throw new ArgumentNullException(nameof(ikey));
}

using var message = CreatePostRequest(ikey, apikey, xMsQpsConfigurationEtag, xMsQpsTransmissionTime, monitoringDataPoints);
_pipeline.Send(message, cancellationToken);
var headers = new QuickPulseSDKClientAPIsPostHeaders(message.Response);
switch (message.Response.Status)
{
case 200:
{
CollectionConfigurationInfo value = default;
if (message.Response.Headers.ContentLength != 0)
{
using var document = JsonDocument.Parse(message.Response.ContentStream);
value = CollectionConfigurationInfo.DeserializeCollectionConfigurationInfo(document.RootElement);
}
return ResponseWithHeaders.FromValue<object, QuickPulseSDKClientAPIsPostHeaders>(value, headers, message.Response);
}
case 400:
case 401:
case 403:
case 404:
case 500:
case 503:
{
ServiceError value = default;
using var document = JsonDocument.Parse(message.Response.ContentStream);
value = ServiceError.DeserializeServiceError(document.RootElement);
return ResponseWithHeaders.FromValue<object, QuickPulseSDKClientAPIsPostHeaders>(value, headers, message.Response);
}
default:
throw new RequestFailedException(message.Response);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,8 @@ public void ErrorInitializingPartOfSdkVersion(string typeName, Exception ex)

[Event(6, Message = "Failed to get Type version while initialize SDK version due to an exception. Not user actionable. Type: {0}. {1}", Level = EventLevel.Warning)]
public void ErrorInitializingPartOfSdkVersion(string typeName, string exceptionMessage) => WriteEvent(6, typeName, exceptionMessage);

[Event(7, Message = "HttpPipelineBuilder is built with AAD Credentials. TokenCredential: {0} Scope: {1}", Level = EventLevel.Informational)]
public void SetAADCredentialsToPipeline(string credentialTypeName, string scope) => WriteEvent(7, credentialTypeName, scope);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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;
using OpenTelemetry.Metrics;
using System.Collections.Concurrent;

namespace Azure.Monitor.OpenTelemetry.LiveMetrics.Internals
{
internal partial class Manager
{
private Meter? _meter;
private MeterProvider? _meterProvider;
private BaseExportingMetricReader? _metricReader;
private readonly ConcurrentQueue<List<Models.MetricPoint>> _queue = 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");

private Instrument? _myObservableGauge1;
private Instrument? _myObservableGauge2;

private void InitializeMetrics()
{
var uniqueTestId = Guid.NewGuid();

//var meterName = $"meterName{uniqueTestId}";
var meterName = LiveMetricConstants.LiveMetricMeterName;
_meter = new Meter(meterName, "1.0");

_myObservableGauge1 = _meter.CreateObservableGauge(LiveMetricConstants.MemoryCommittedBytesInstrumentName, () =>
{
return new Measurement<float>(value: _performanceCounter_CommittedBytes.NextValue());
});

_myObservableGauge2 = _meter.CreateObservableGauge(LiveMetricConstants.ProcessorTimeInstrumentName, () =>
{
return new Measurement<float>(value: _performanceCounter_ProcessorTime.NextValue());
});

_metricReader = new BaseExportingMetricReader(new LiveMetricsMetricExporter(_queue));

var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
.AddMeter(meterName)
.AddReader(_metricReader);

_meterProvider = meterProviderBuilder.Build();
}

private IEnumerable<Models.MetricPoint> CollectMetrics()
{
_metricReader?.Collect();

if (_queue.TryDequeue(out var metricPoint))
{
return metricPoint;
}
else
{
return Array.Empty<Models.MetricPoint>();
}
}
}
}
Loading