Skip to content

Commit

Permalink
[AzureMonitorLiveMetrics] POC (#40001)
Browse files Browse the repository at this point in the history
* working POC

* update public api
  • Loading branch information
TimothyMothra authored Nov 15, 2023
1 parent 663c0f4 commit dd563c7
Show file tree
Hide file tree
Showing 14 changed files with 708 additions and 15 deletions.
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
{
public LiveMetricsExporterOptions() { }
public string ConnectionString { get { throw null; } set { } }
public Azure.Core.TokenCredential Credential { get { throw null; } set { } }
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" />
</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);
}
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

0 comments on commit dd563c7

Please sign in to comment.