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

Refactor statsbeat #34443

Merged
merged 10 commits into from
Feb 23, 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
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
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved

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;
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved

_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()
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved
{
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));
}
}
}