Skip to content

Commit

Permalink
Refactor statsbeat (Azure#34443)
Browse files Browse the repository at this point in the history
* refactor statsbeat

* clean up

* fix statsbeat issue

* disable

* refactor transmitter

* rename

* resolve PR comments

* rmv using

* missed update

* Update sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Statsbeat/Statsbeat.cs

Co-authored-by: Timothy Mothra <tilee@microsoft.com>

---------

Co-authored-by: Timothy Mothra <tilee@microsoft.com>
  • Loading branch information
vishweshbankwar and TimothyMothra authored Feb 23, 2023
1 parent 8569230 commit 9c4203d
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ public AzureMonitorTransmitter(AzureMonitorExporterOptions options, TokenCredent
_applicationInsightsRestClient = InitializeRestClient(options, _connectionVars, credential);

_fileBlobProvider = InitializeOfflineStorage(options);

// TODO: uncomment following line for enablement.
// InitializeStatsbeat(_connectionVars);
}

private static void InitializeStatsbeat(ConnectionVars connectionVars)
{
try
{
// Do not initialize statsbeat for statsbeat.
if (connectionVars != null && connectionVars.InstrumentationKey != ConnectionStringParser.GetValues(Statsbeat.Statsbeat_ConnectionString_EU).InstrumentationKey && connectionVars.InstrumentationKey != ConnectionStringParser.GetValues(Statsbeat.Statsbeat_ConnectionString_NonEU).InstrumentationKey)
{
// TODO: Implement IDisposable for transmitter and dispose statsbeat.
_ = new Statsbeat(connectionVars);
}
}
catch (Exception ex)
{
AzureMonitorExporterEventSource.Log.WriteWarning($"ErrorInitializingStatsBeatfor:{connectionVars.InstrumentationKey}", ex);
}
}

public string InstrumentationKey => _connectionVars.InstrumentationKey;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#nullable disable // TODO: remove and fix errors

using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
Expand All @@ -17,39 +15,37 @@

namespace Azure.Monitor.OpenTelemetry.Exporter.Internals
{
internal static class Statsbeat
internal sealed class Statsbeat : IDisposable
{
internal const string StatsBeat_ConnectionString_NonEU = "<Non-EU-ConnectionString>";
internal const string Statsbeat_ConnectionString_NonEU = "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://NonEU.in.applicationinsights.azure.com/";

internal const string StatsBeat_ConnectionString_EU = "EU-ConnectionString";
internal const string Statsbeat_ConnectionString_EU = "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://EU.in.applicationinsights.azure.com/";

private const string AMS_Url = "http://169.254.169.254/metadata/instance/compute?api-version=2017-08-01&format=json";

internal const int AttachStatsBeatInterval = 86400000;

private static readonly Meter s_myMeter = new("AttachStatsBeatMeter", "1.0");
internal const int AttachStatsbeatInterval = 86400000;

private static bool s_isEnabled = true;
private static readonly Meter s_myMeter = new("AttachStatsbeatMeter", "1.0");

internal static string s_statsBeat_ConnectionString;
internal string? _statsbeat_ConnectionString;

private static string s_resourceProviderId;
private string? _resourceProviderId;

private static string s_resourceProvider;
private string? _resourceProvider;

private static string s_runtimeVersion => SdkVersionUtils.GetVersion(typeof(object));
private static string? s_runtimeVersion => SdkVersionUtils.GetVersion(typeof(object));

private static string s_sdkVersion => SdkVersionUtils.GetVersion(typeof(AzureMonitorTraceExporter));
private static string? s_sdkVersion => SdkVersionUtils.GetVersion(typeof(AzureMonitorTraceExporter));

private static string s_operatingSystem = GetOS();

private static string s_customer_Ikey;
private readonly string? _customer_Ikey;

internal static MeterProvider s_attachStatsBeatMeterProvider;
internal MeterProvider? _attachStatsbeatMeterProvider;

internal static Regex s_endpoint_pattern = new("^https?://(?:www\\.)?([^/.-]+)");
internal static Regex s_endpoint_pattern => new("^https?://(?:www\\.)?([^/.-]+)");

internal static readonly HashSet<string> EU_Endpoints = new()
internal static readonly HashSet<string> s_EU_Endpoints = new()
{
"francecentral",
"francesouth",
Expand All @@ -64,7 +60,7 @@ internal static class Statsbeat
"westeurope",
};

internal static readonly HashSet<string> Non_EU_Endpoints = new()
internal static readonly HashSet<string> s_non_EU_Endpoints = new()
{
"australiacentral",
"australiacentral2",
Expand Down Expand Up @@ -101,6 +97,35 @@ internal static class Statsbeat
"westus3",
};

internal Statsbeat(ConnectionVars connectionStringVars)
{
_statsbeat_ConnectionString = GetStatsbeatConnectionString(connectionStringVars?.IngestionEndpoint);

// Initialize only if we are able to determine the correct region to send the data to.
if (_statsbeat_ConnectionString == null)
{
throw new InvalidOperationException("Cannot initialize statsbeat");
}

_customer_Ikey = connectionStringVars?.InstrumentationKey;

s_myMeter.CreateObservableGauge("AttachStatsbeat", () => GetAttachStatsbeat());

// Configure for attach statsbeat which has collection
// schedule of 24 hrs == 86400000 milliseconds.
// TODO: Follow up in spec to confirm the behavior
// in case if the app exits before 24hrs duration.
var exporterOptions = new AzureMonitorExporterOptions();
exporterOptions.DisableOfflineStorage = true;
exporterOptions.ConnectionString = _statsbeat_ConnectionString;

_attachStatsbeatMeterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter("AttachStatsbeatMeter")
.AddReader(new PeriodicExportingMetricReader(new AzureMonitorMetricExporter(exporterOptions), AttachStatsbeatInterval)
{ TemporalityPreference = MetricReaderTemporalityPreference.Delta })
.Build();
}

private static string GetOS()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
Expand All @@ -119,90 +144,54 @@ private static string GetOS()
return "unknown";
}

internal static void InitializeAttachStatsbeat(string connectionString)
internal static string? GetStatsbeatConnectionString(string? ingestionEndpoint)
{
// check whether it is disabled or already initialized.
if (s_isEnabled && s_attachStatsBeatMeterProvider == null)
var patternMatch = s_endpoint_pattern.Match(ingestionEndpoint);
string? statsbeatConnectionString = null;
if (patternMatch.Success)
{
if (s_statsBeat_ConnectionString == null)
var endpoint = patternMatch.Groups[1].Value;
if (s_EU_Endpoints.Contains(endpoint))
{
var parsedConectionString = ConnectionStringParser.GetValues(connectionString);
SetCustomerIkey(parsedConectionString.InstrumentationKey);
SetStatsbeatConnectionString(parsedConectionString.IngestionEndpoint);
statsbeatConnectionString = Statsbeat_ConnectionString_EU;
}

if (!s_isEnabled)
else if (s_non_EU_Endpoints.Contains(endpoint))
{
// TODO: log
return;
statsbeatConnectionString = Statsbeat_ConnectionString_NonEU;
}

s_myMeter.CreateObservableGauge("AttachStatsBeat", () => GetAttachStatsBeat());

// Configure for attach statsbeat which has collection
// schedule of 24 hrs == 86400000 milliseconds.
// TODO: Follow up in spec to confirm the behavior
// in case if the app exits before 24hrs duration.
var exporterOptions = new AzureMonitorExporterOptions();
exporterOptions.DisableOfflineStorage = true;
exporterOptions.ConnectionString = s_statsBeat_ConnectionString;

s_attachStatsBeatMeterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter("AttachStatsBeatMeter")
.AddReader(new PeriodicExportingMetricReader(new AzureMonitorMetricExporter(exporterOptions), AttachStatsBeatInterval)
{ TemporalityPreference = MetricReaderTemporalityPreference.Delta })
.Build();
}
}

internal static void SetCustomerIkey(string instrumentationKey)
{
s_customer_Ikey = instrumentationKey;
return statsbeatConnectionString;
}

internal static void SetStatsbeatConnectionString(string ingestionEndpoint)
private Measurement<int> GetAttachStatsbeat()
{
var patternMatch = s_endpoint_pattern.Match(ingestionEndpoint);
if (patternMatch.Success)
try
{
var endpoint = patternMatch.Groups[1].Value;
if (EU_Endpoints.Contains(endpoint))
{
s_statsBeat_ConnectionString = StatsBeat_ConnectionString_EU;
}
else if (Non_EU_Endpoints.Contains(endpoint))
{
s_statsBeat_ConnectionString = StatsBeat_ConnectionString_NonEU;
}
else
if (_resourceProvider == null)
{
// disable statsbeat
s_isEnabled = false;
SetResourceProviderDetails();
}
}
}

private static Measurement<int> GetAttachStatsBeat()
{
if (s_resourceProvider == null)
return
new Measurement<int>(1,
new("rp", _resourceProvider),
new("rpId", _resourceProviderId),
new("attach", "sdk"),
new("cikey", _customer_Ikey),
new("runtimeVersion", s_runtimeVersion),
new("language", "dotnet"),
new("version", s_sdkVersion),
new("os", s_operatingSystem));
}
catch (Exception ex)
{
SetResourceProviderDetails();
AzureMonitorExporterEventSource.Log.WriteWarning("ErrorGettingStatsbeatData", ex);
return new Measurement<int>();
}

// TODO: Add os to the list
return
new Measurement<int>(1,
new("rp", s_resourceProvider),
new("rpId", s_resourceProviderId),
new("attach", "sdk"),
new("cikey", s_customer_Ikey),
new("runtimeVersion", s_runtimeVersion),
new("language", "dotnet"),
new("version", s_sdkVersion),
new("os", s_operatingSystem));
}

private static VmMetadataResponse GetVmMetadataResponse()
private static VmMetadataResponse? GetVmMetadataResponse()
{
try
{
Expand All @@ -222,17 +211,17 @@ private static VmMetadataResponse GetVmMetadataResponse()
}
}

private static void SetResourceProviderDetails()
private void SetResourceProviderDetails()
{
var appSvcWebsiteName = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME");
if (appSvcWebsiteName != null)
{
s_resourceProvider = "appsvc";
s_resourceProviderId = appSvcWebsiteName;
_resourceProvider = "appsvc";
_resourceProviderId = appSvcWebsiteName;
var appSvcWebsiteHostName = Environment.GetEnvironmentVariable("WEBSITE_HOME_STAMPNAME");
if (!string.IsNullOrEmpty(appSvcWebsiteHostName))
{
s_resourceProviderId += "/" + appSvcWebsiteHostName;
_resourceProviderId += "/" + appSvcWebsiteHostName;
}

return;
Expand All @@ -241,8 +230,8 @@ private static void SetResourceProviderDetails()
var functionsWorkerRuntime = Environment.GetEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME");
if (functionsWorkerRuntime != null)
{
s_resourceProvider = "functions";
s_resourceProviderId = Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME");
_resourceProvider = "functions";
_resourceProviderId = Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME");

return;
}
Expand All @@ -251,17 +240,22 @@ private static void SetResourceProviderDetails()

if (vmMetadata != null)
{
s_resourceProvider = "vm";
s_resourceProviderId = s_resourceProviderId = vmMetadata.vmId + "/" + vmMetadata.subscriptionId;
_resourceProvider = "vm";
_resourceProviderId = _resourceProviderId = vmMetadata.vmId + "/" + vmMetadata.subscriptionId;

// osType takes precedence.
s_operatingSystem = vmMetadata.osType.ToLower(CultureInfo.InvariantCulture);

return;
}

s_resourceProvider = "unknown";
s_resourceProviderId = "unknown";
_resourceProvider = "unknown";
_resourceProviderId = "unknown";
}

public void Dispose()
{
_attachStatsbeatMeterProvider?.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Monitor.OpenTelemetry.Exporter.Internals;
using Azure.Monitor.OpenTelemetry.Exporter.Internals.ConnectionString;
using Xunit;
Expand All @@ -29,15 +24,11 @@ public class StatsbeatTests
[InlineData("westeurope")]
public void StatsbeatConnectionStringIsSetBasedOnCustomersConnectionStringEndpointInEU(string euEndpoint)
{
var customer_ConnectionString = $"InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://{euEndpoint}.in.applicationinsights.azure.com/";
var parsedConectionString = ConnectionStringParser.GetValues(customer_ConnectionString);
var customer_ConnectionString = $"InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://{euEndpoint}.in.applicationinsights.azure.com/";
var connectionStringVars = ConnectionStringParser.GetValues(customer_ConnectionString);
var statsBeatInstance = new Statsbeat(connectionStringVars);

Statsbeat.SetStatsbeatConnectionString(parsedConectionString.IngestionEndpoint);

Assert.Equal(Statsbeat.StatsBeat_ConnectionString_EU, Statsbeat.s_statsBeat_ConnectionString);

// Reset Statsbeat Connection String
Statsbeat.s_statsBeat_ConnectionString = null;
Assert.Equal(Statsbeat.Statsbeat_ConnectionString_EU, statsBeatInstance._statsbeat_ConnectionString);
}

[Theory]
Expand Down Expand Up @@ -76,29 +67,20 @@ public void StatsbeatConnectionStringIsSetBasedOnCustomersConnectionStringEndpoi
[InlineData("westus3")]
public void StatsbeatConnectionStringIsSetBasedOnCustomersConnectionStringEndpointInNonEU(string nonEUEndpoint)
{
var customer_ConnectionString = $"InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://{nonEUEndpoint}.in.applicationinsights.azure.com/";
var parsedConectionString = ConnectionStringParser.GetValues(customer_ConnectionString);

Statsbeat.SetStatsbeatConnectionString(parsedConectionString.IngestionEndpoint);
var customer_ConnectionString = $"InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://{nonEUEndpoint}.in.applicationinsights.azure.com/";
var connectionStringVars = ConnectionStringParser.GetValues(customer_ConnectionString);
var statsBeatInstance = new Statsbeat(connectionStringVars);

Assert.Equal(Statsbeat.StatsBeat_ConnectionString_NonEU, Statsbeat.s_statsBeat_ConnectionString);

// Reset Statsbeat Connection String
Statsbeat.s_statsBeat_ConnectionString = null;
Assert.Equal(Statsbeat.Statsbeat_ConnectionString_NonEU, statsBeatInstance._statsbeat_ConnectionString);
}

[Fact]
public void StatsbeatIsNotInitializedForUnknownRegions()
{
var customer_ConnectionString = "InstrumentationKey=1aa11111-bbbb-1ccc-8ddd-eeeeffff3333;IngestionEndpoint=https://foo.in.applicationinsights.azure.com/";
var parsedConectionString = ConnectionStringParser.GetValues(customer_ConnectionString);

Statsbeat.SetStatsbeatConnectionString(parsedConectionString.IngestionEndpoint);

Assert.Null(Statsbeat.s_statsBeat_ConnectionString);
var customer_ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://foo.in.applicationinsights.azure.com/";

// Reset Statsbeat Connection String
Statsbeat.s_statsBeat_ConnectionString = null;
var connectionStringVars = ConnectionStringParser.GetValues(customer_ConnectionString);
Assert.Throws<InvalidOperationException>(() => new Statsbeat(connectionStringVars));
}
}
}

0 comments on commit 9c4203d

Please sign in to comment.