ClientTelemetry : Adds logic to call client config in every 10 minutes (#4071)

* first draft

* fix tests

* fixes

* fix tests

* remove consoles

* added exception

* remove comment

* fix tests

* fix test

* rev comments

* rev comments

* refactor code

* remove log from api exception
This commit is contained in:
Sourabh Jain 2023-09-15 21:07:47 +05:30 коммит произвёл GitHub
Родитель 0a0e5ae69d
Коммит 52d0436104
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 385 добавлений и 34 удалений

Просмотреть файл

@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.Handler
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Documents.Rntbd;
using Microsoft.Azure.Cosmos.Core.Trace;
@ -21,16 +22,14 @@ namespace Microsoft.Azure.Cosmos.Handler
private const string Telemetrykey = "telemetry";
public static readonly TimeSpan DiagnosticsRefreshInterval = TimeSpan.FromSeconds(10);
private static readonly SystemUsageRecorder DiagnosticSystemUsageRecorder = new SystemUsageRecorder(
private static readonly TimeSpan ClientTelemetryRefreshInterval = TimeSpan.FromSeconds(5);
// Need to reset it in Tests hence kept it non-readonly.
private static SystemUsageRecorder DiagnosticSystemUsageRecorder = new SystemUsageRecorder(
identifier: Diagnostickey,
historyLength: 6,
refreshInterval: DiagnosticsHandlerHelper.DiagnosticsRefreshInterval);
private static readonly TimeSpan ClientTelemetryRefreshInterval = TimeSpan.FromSeconds(5);
private static readonly SystemUsageRecorder TelemetrySystemUsageRecorder = new SystemUsageRecorder(
identifier: Telemetrykey,
historyLength: 120,
refreshInterval: DiagnosticsHandlerHelper.ClientTelemetryRefreshInterval);
private static SystemUsageRecorder TelemetrySystemUsageRecorder = null;
/// <summary>
/// Singleton to make sure only one instance of DiagnosticHandlerHelper is there.
@ -62,13 +61,13 @@ namespace Microsoft.Azure.Cosmos.Handler
{
if (isClientTelemetryEnabled != DiagnosticsHandlerHelper.isTelemetryMonitoringEnabled)
{
DiagnosticsHandlerHelper.Instance.StopSystemMonitor();
DiagnosticsHandlerHelper tempInstance = DiagnosticsHandlerHelper.Instance;
// Update telemetry flag
DiagnosticsHandlerHelper.isTelemetryMonitoringEnabled = isClientTelemetryEnabled;
// Create new instance, it will start a new system monitor job
DiagnosticsHandlerHelper.Instance = new DiagnosticsHandlerHelper();
// Stopping the monitor is a blocking call so we do it in a separate thread
_ = Task.Run(() => tempInstance.StopSystemMonitor());
}
}
@ -80,7 +79,7 @@ namespace Microsoft.Azure.Cosmos.Handler
}
catch (ObjectDisposedException ex)
{
DefaultTrace.TraceError($"Error while stopping system usage monitor. {0} ", ex);
DefaultTrace.TraceError("Error while stopping system usage monitor. {0} ", ex);
}
}
@ -102,8 +101,18 @@ namespace Microsoft.Azure.Cosmos.Handler
if (DiagnosticsHandlerHelper.isTelemetryMonitoringEnabled)
{
// re-initialize a fresh telemetry recorder when feature is switched on
DiagnosticsHandlerHelper.TelemetrySystemUsageRecorder = new SystemUsageRecorder(
identifier: Telemetrykey,
historyLength: 120,
refreshInterval: DiagnosticsHandlerHelper.ClientTelemetryRefreshInterval);
recorders.Add(DiagnosticsHandlerHelper.TelemetrySystemUsageRecorder);
}
else
{
DiagnosticsHandlerHelper.TelemetrySystemUsageRecorder = null;
}
this.systemUsageMonitor = SystemUsageMonitor.CreateAndStart(recorders);
@ -154,7 +163,7 @@ namespace Microsoft.Azure.Cosmos.Handler
try
{
return DiagnosticsHandlerHelper.TelemetrySystemUsageRecorder.Data;
return DiagnosticsHandlerHelper.TelemetrySystemUsageRecorder?.Data;
}
catch (Exception ex)
{

Просмотреть файл

@ -21,7 +21,8 @@ namespace Microsoft.Azure.Cosmos.Telemetry
{
private ITelemetryCollector collector = new TelemetryCollectorNoOp();
internal static int DefaultBackgroundRefreshClientConfigTimeIntervalInMS = (int)TimeSpan.FromMinutes(10).TotalMilliseconds;
internal static TimeSpan DefaultBackgroundRefreshClientConfigTimeInterval
= TimeSpan.FromMinutes(10);
private readonly AuthorizationTokenProvider cosmosAuthorization;
private readonly CosmosHttpClient httpClient;
@ -73,7 +74,13 @@ namespace Microsoft.Azure.Cosmos.Telemetry
}
TelemetryToServiceHelper helper = new TelemetryToServiceHelper(
clientId, connectionPolicy, cosmosAuthorization, httpClient, serviceEndpoint, globalEndpointManager, cancellationTokenSource);
clientId: clientId,
connectionPolicy: connectionPolicy,
cosmosAuthorization: cosmosAuthorization,
httpClient: httpClient,
serviceEndpoint: serviceEndpoint,
globalEndpointManager: globalEndpointManager,
cancellationTokenSource: cancellationTokenSource);
_ = helper.RetrieveConfigAndInitiateTelemetryAsync(); // Let it run in backgroud
@ -86,20 +93,36 @@ namespace Microsoft.Azure.Cosmos.Telemetry
try
{
Uri serviceEndpointWithPath = new Uri(this.serviceEnpoint + Paths.ClientConfigPathSegment);
while (!this.cancellationTokenSource.IsCancellationRequested)
{
TryCatch<AccountClientConfiguration> databaseAccountClientConfigs = await this.GetDatabaseAccountClientConfigAsync(
cosmosAuthorization: this.cosmosAuthorization,
httpClient: this.httpClient,
clientConfigEndpoint: serviceEndpointWithPath);
TryCatch<AccountClientConfiguration> databaseAccountClientConfigs = await this.GetDatabaseAccountClientConfigAsync(this.cosmosAuthorization, this.httpClient, serviceEndpointWithPath);
if (databaseAccountClientConfigs.Succeeded)
{
this.InitializeClientTelemetry(databaseAccountClientConfigs.Result);
}
else if (!this.cancellationTokenSource.IsCancellationRequested)
{
DefaultTrace.TraceWarning($"Exception while calling client config " + databaseAccountClientConfigs.Exception.ToString());
if (databaseAccountClientConfigs.Succeeded)
{
this.InitializeClientTelemetry(
clientConfig: databaseAccountClientConfigs.Result);
}
else if (databaseAccountClientConfigs.Exception is ObjectDisposedException)
{
DefaultTrace.TraceWarning("Client is being disposed for {0} at {1}", serviceEndpointWithPath, DateTime.UtcNow);
break;
}
else if (!this.cancellationTokenSource.IsCancellationRequested)
{
DefaultTrace.TraceWarning("Exception while calling client config {0} ", databaseAccountClientConfigs.Exception);
}
await Task.Delay(
delay: TelemetryToServiceHelper.DefaultBackgroundRefreshClientConfigTimeInterval,
cancellationToken: this.cancellationTokenSource.Token);
}
}
catch (Exception ex)
{
DefaultTrace.TraceWarning($"Exception while running client config job " + ex.ToString());
DefaultTrace.TraceWarning("Exception while running client config job: {0}", ex);
}
}
@ -125,14 +148,25 @@ namespace Microsoft.Azure.Cosmos.Telemetry
timeoutPolicy: HttpTimeoutPolicyControlPlaneRead.Instance,
clientSideRequestStatistics: null,
cancellationToken: default))
using (DocumentServiceResponse documentServiceResponse = await ClientExtensions.ParseResponseAsync(responseMessage))
{
return TryCatch<AccountClientConfiguration>.FromResult(CosmosResource.FromStream<AccountClientConfiguration>(documentServiceResponse));
// It means feature flag is off at gateway, then log the exception and retry after defined interval.
// If feature flag is OFF at gateway, SDK won't refresh the latest state of the flag.
if (responseMessage.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
string responseFromGateway = await responseMessage.Content.ReadAsStringAsync();
return TryCatch<AccountClientConfiguration>.FromException(
new InvalidOperationException($"Client Config API is not enabled at compute gateway. Response is {responseFromGateway}"));
}
using (DocumentServiceResponse documentServiceResponse = await ClientExtensions.ParseResponseAsync(responseMessage))
{
return TryCatch<AccountClientConfiguration>.FromResult(
CosmosResource.FromStream<AccountClientConfiguration>(documentServiceResponse));
}
}
}
catch (Exception ex)
{
DefaultTrace.TraceWarning($"Exception while calling client config " + ex.StackTrace);
return TryCatch<AccountClientConfiguration>.FromException(ex);
}
}
@ -153,10 +187,16 @@ namespace Microsoft.Azure.Cosmos.Telemetry
/// </summary>
private void InitializeClientTelemetry(AccountClientConfiguration clientConfig)
{
// If state of the job is same as state of the flag, then no need to do anything.
if (clientConfig.IsClientTelemetryEnabled() == this.IsClientTelemetryJobRunning())
{
return;
}
DiagnosticsHandlerHelper.Refresh(clientConfig.IsClientTelemetryEnabled());
if (clientConfig.IsClientTelemetryEnabled())
{
{
try
{
this.clientTelemetry = ClientTelemetry.CreateAndStartBackgroundTelemetry(
@ -180,6 +220,11 @@ namespace Microsoft.Azure.Cosmos.Telemetry
this.connectionPolicy.EnableClientTelemetry = false;
}
}
else
{
this.StopClientTelemetry();
DefaultTrace.TraceVerbose("Client Telemetry Disabled.");
}
}
public void Dispose()
@ -193,10 +238,19 @@ namespace Microsoft.Azure.Cosmos.Telemetry
/// </summary>
private void StopClientTelemetry()
{
this.collector = new TelemetryCollectorNoOp();
try
{
this.collector = new TelemetryCollectorNoOp();
this.clientTelemetry?.Dispose();
this.clientTelemetry = null;
this.clientTelemetry?.Dispose();
this.clientTelemetry = null;
DiagnosticsHandlerHelper.Refresh(isClientTelemetryEnabled: false);
}
catch (Exception ex)
{
DefaultTrace.TraceWarning($"Error While stopping Telemetry Job : {0}", ex);
}
}
}
}

Просмотреть файл

@ -0,0 +1,265 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
{
using System.Net.Http;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Microsoft.Azure.Cosmos.Fluent;
using System;
using Microsoft.Azure.Cosmos.Routing;
using System.Reflection;
using System.Threading;
using Microsoft.Azure.Cosmos.Telemetry;
[TestClass]
public class ClientTelemetryConfigurationTest : BaseCosmosClientHelper
{
private const string EndpointUrl = "http://dummy.test.com/";
private CosmosClientBuilder cosmosClientBuilder;
private readonly TimeSpan OriginalDefaultBackgroundRefreshClientConfigTimeInterval = TelemetryToServiceHelper.DefaultBackgroundRefreshClientConfigTimeInterval;
[TestInitialize]
public void TestInitialize()
{
TelemetryToServiceHelper.DefaultBackgroundRefreshClientConfigTimeInterval
= TimeSpan.FromMilliseconds(100);
this.cosmosClientBuilder = TestCommon.GetDefaultConfiguration();
}
[TestCleanup]
public async Task Cleanup()
{
await base.TestCleanup();
// Resetting time intervals
TelemetryToServiceHelper.DefaultBackgroundRefreshClientConfigTimeInterval
= this.OriginalDefaultBackgroundRefreshClientConfigTimeInterval;
}
[TestMethod]
public async Task Validate_ClientTelemetryJob_Status_if_Disabled_At_Instance_LevelAsync()
{
HttpClientHandlerHelper httpHandler = new HttpClientHandlerHelper
{
RequestCallBack = (request, cancellation) =>
{
if (request.RequestUri.AbsoluteUri.Equals(EndpointUrl))
{
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
return Task.FromResult(result);
}
else if (request.RequestUri.AbsoluteUri.Contains(Documents.Paths.ClientConfigPathSegment))
{
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
AccountClientConfiguration clientConfigProperties = new AccountClientConfiguration
{
ClientTelemetryConfiguration = new ClientTelemetryConfiguration
{
IsEnabled = true,
Endpoint = EndpointUrl
}
};
string payload = JsonConvert.SerializeObject(clientConfigProperties);
result.Content = new StringContent(payload, Encoding.UTF8, "application/json");
return Task.FromResult(result);
}
return null;
}
};
this.cosmosClientBuilder
.WithHttpClientFactory(() => new HttpClient(httpHandler))
.WithTelemetryDisabled();
this.SetClient(this.cosmosClientBuilder.Build());
this.database = await this.GetClient().CreateDatabaseAsync(Guid.NewGuid().ToString());
DocumentClient documentClient = this.GetClient().DocumentClient;
Assert.IsNotNull(documentClient.telemetryToServiceHelper);
Assert.IsFalse(documentClient.telemetryToServiceHelper.IsClientTelemetryJobRunning());
ClientCollectionCache collCache = (ClientCollectionCache)documentClient
.GetType()
.GetField("collectionCache", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
.GetValue(documentClient);
TelemetryToServiceHelper telemetryToServiceHelper = (TelemetryToServiceHelper)collCache
.GetType()
.GetField("telemetryToServiceHelper", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(collCache);
Assert.IsNotNull(telemetryToServiceHelper);
Assert.IsFalse(telemetryToServiceHelper.IsClientTelemetryJobRunning());
}
[TestMethod]
[DataRow(true, HttpStatusCode.OK)]
[DataRow(false, HttpStatusCode.OK)]
[DataRow(false, HttpStatusCode.BadRequest)] // Errored Client Config API
public async Task Validate_ClientTelemetryJob_Status_with_Client_Config_Api_States_Async(bool clientTelemetryFlagEnabled, HttpStatusCode clientConfigApiStatus)
{
HttpClientHandlerHelper httpHandler = new HttpClientHandlerHelper
{
RequestCallBack = (request, cancellation) =>
{
if (request.RequestUri.AbsoluteUri.Equals(EndpointUrl))
{
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
return Task.FromResult(result);
}
else if (request.RequestUri.AbsoluteUri.Contains(Documents.Paths.ClientConfigPathSegment))
{
HttpResponseMessage result = new HttpResponseMessage(clientConfigApiStatus);
AccountClientConfiguration clientConfigProperties = new AccountClientConfiguration
{
ClientTelemetryConfiguration = new ClientTelemetryConfiguration
{
IsEnabled = clientTelemetryFlagEnabled,
Endpoint = clientTelemetryFlagEnabled ? EndpointUrl : null
}
};
string payload = JsonConvert.SerializeObject(clientConfigProperties);
result.Content = new StringContent(payload, Encoding.UTF8, "application/json");
return Task.FromResult(result);
}
return null;
}
};
this.cosmosClientBuilder
.WithHttpClientFactory(() => new HttpClient(httpHandler))
.WithTelemetryEnabled();
this.SetClient(this.cosmosClientBuilder.Build());
this.database = await this.GetClient().CreateDatabaseAsync(Guid.NewGuid().ToString());
DocumentClient documentClient = this.GetClient().DocumentClient;
ClientCollectionCache collCache = (ClientCollectionCache)documentClient
.GetType()
.GetField("collectionCache", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
.GetValue(documentClient);
TelemetryToServiceHelper telemetryToServiceHelperFromCollectionCache = (TelemetryToServiceHelper)collCache
.GetType()
.GetField("telemetryToServiceHelper", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(collCache);
Assert.AreEqual(clientTelemetryFlagEnabled, documentClient.telemetryToServiceHelper.IsClientTelemetryJobRunning());
Assert.AreEqual(clientTelemetryFlagEnabled, telemetryToServiceHelperFromCollectionCache.IsClientTelemetryJobRunning());
}
[TestMethod]
[DataRow(true, false, HttpStatusCode.OK)]
[DataRow(false, true, HttpStatusCode.OK)]
[DataRow(true, false, HttpStatusCode.BadRequest)]
[DataRow(false, true, HttpStatusCode.BadRequest)]
public async Task Validate_ClientTelemetryJob_When_Flag_Is_Switched(bool flagState1, bool flagState2, HttpStatusCode clientConfigApiStatusAfterSwitch)
{
using ManualResetEvent manualResetEvent = new ManualResetEvent(false);
int counter = 0;
HttpClientHandlerHelper httpHandler = new HttpClientHandlerHelper
{
RequestCallBack = (request, cancellation) =>
{
if (request.RequestUri.AbsoluteUri.Contains(Documents.Paths.ClientConfigPathSegment))
{
if (counter == 10)
{
manualResetEvent.Set();
}
counter++;
bool isEnabled = counter < 5 ? flagState1 : flagState2;
HttpStatusCode apiStatusCode = counter < 5 ? HttpStatusCode.OK : clientConfigApiStatusAfterSwitch;
HttpResponseMessage result = new HttpResponseMessage(apiStatusCode);
string payload = JsonConvert.SerializeObject(new AccountClientConfiguration
{
ClientTelemetryConfiguration = new ClientTelemetryConfiguration
{
IsEnabled = isEnabled,
Endpoint = isEnabled ? EndpointUrl : null
}
});
result.Content = new StringContent(payload, Encoding.UTF8, "application/json");
return Task.FromResult(result);
}
return null;
}
};
this.cosmosClientBuilder
.WithHttpClientFactory(() => new HttpClient(httpHandler))
.WithTelemetryEnabled();
this.SetClient(this.cosmosClientBuilder.Build());
this.database = await this.GetClient()
.CreateDatabaseAsync(Guid.NewGuid().ToString());
DocumentClient documentClient = this.GetClient().DocumentClient;
ClientCollectionCache collCache = (ClientCollectionCache)documentClient
.GetType()
.GetField("collectionCache", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
.GetValue(documentClient);
TelemetryToServiceHelper telemetryToServiceHelperFromCollectionCache
= (TelemetryToServiceHelper)collCache
.GetType()
.GetField("telemetryToServiceHelper", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(collCache);
Assert.AreEqual(flagState1, documentClient.telemetryToServiceHelper.IsClientTelemetryJobRunning());
Assert.AreEqual(flagState1, telemetryToServiceHelperFromCollectionCache.IsClientTelemetryJobRunning());
manualResetEvent.WaitOne(TimeSpan.FromSeconds(1));
collCache = (ClientCollectionCache)documentClient
.GetType()
.GetField("collectionCache", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
.GetValue(documentClient);
telemetryToServiceHelperFromCollectionCache = (TelemetryToServiceHelper)collCache
.GetType()
.GetField("telemetryToServiceHelper", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(collCache);
if (clientConfigApiStatusAfterSwitch == HttpStatusCode.OK)
{
Assert.AreEqual(flagState2, documentClient.telemetryToServiceHelper.IsClientTelemetryJobRunning());
Assert.AreEqual(flagState2, telemetryToServiceHelperFromCollectionCache.IsClientTelemetryJobRunning());
}
else
{
// If the client config api errored out, the flag should not be changed
Assert.AreEqual(flagState1, documentClient.telemetryToServiceHelper.IsClientTelemetryJobRunning());
Assert.AreEqual(flagState1, telemetryToServiceHelperFromCollectionCache.IsClientTelemetryJobRunning());
}
}
}
}

Просмотреть файл

@ -25,11 +25,21 @@ namespace Microsoft.Azure.Cosmos.Diagnostics
MethodInfo iMethod = helper.GetType().GetMethod("StopSystemMonitor", BindingFlags.NonPublic | BindingFlags.Instance);
iMethod.Invoke(helper, new object[] { });
//Reset the instance woth original value
//Reset the DiagnosticSystemUsageRecorder with original value
FieldInfo DiagnosticSystemUsageRecorderField = typeof(DiagnosticsHandlerHelper).GetField("DiagnosticSystemUsageRecorder",
BindingFlags.Static |
BindingFlags.NonPublic);
DiagnosticSystemUsageRecorderField.SetValue(null, new Documents.Rntbd.SystemUsageRecorder(
identifier: "diagnostic",
historyLength: 6,
refreshInterval: DiagnosticsHandlerHelper.DiagnosticsRefreshInterval));
//Reset the instance with original value
FieldInfo field = typeof(DiagnosticsHandlerHelper).GetField("Instance",
BindingFlags.Static |
BindingFlags.NonPublic);
field.SetValue(null, Activator.CreateInstance(typeof(DiagnosticsHandlerHelper), true));
}
[TestMethod]
@ -50,14 +60,20 @@ namespace Microsoft.Azure.Cosmos.Diagnostics
DiagnosticsHandlerHelper diagnosticHandlerHelper1 = DiagnosticsHandlerHelper.GetInstance();
await Task.Delay(10000); // warm up
Assert.IsNotNull(diagnosticHandlerHelper1.GetDiagnosticsSystemHistory());
Assert.IsTrue(diagnosticHandlerHelper1.GetDiagnosticsSystemHistory().Values.Count > 0);
int countBeforeRefresh = diagnosticHandlerHelper1.GetDiagnosticsSystemHistory().Values.Count;
FieldInfo TelemetrySystemUsageRecorderField1 = typeof(DiagnosticsHandlerHelper).GetField("TelemetrySystemUsageRecorder",
BindingFlags.Static |
BindingFlags.NonPublic);
Assert.IsNull(TelemetrySystemUsageRecorderField1.GetValue(null));
// Refresh instance of DiagnosticsHandlerHelper with client telemetry enabled
DiagnosticsHandlerHelper.Refresh(true);
await Task.Delay(5000); // warm up again to populate telemetry data
DiagnosticsHandlerHelper diagnosticHandlerHelper2 = DiagnosticsHandlerHelper.GetInstance();
int countAfterRefresh = diagnosticHandlerHelper1.GetDiagnosticsSystemHistory().Values.Count;
Console.WriteLine(countBeforeRefresh + " " + countAfterRefresh);
Assert.IsTrue(countBeforeRefresh <= countAfterRefresh, "After Refresh count should be greater than or equal to before refresh count");
Assert.AreNotEqual(diagnosticHandlerHelper1, diagnosticHandlerHelper2);
@ -65,11 +81,18 @@ namespace Microsoft.Azure.Cosmos.Diagnostics
Assert.IsNotNull(diagnosticHandlerHelper2.GetDiagnosticsSystemHistory());
Assert.IsNotNull(diagnosticHandlerHelper2.GetClientTelemetrySystemHistory());
Assert.IsTrue(diagnosticHandlerHelper2.GetClientTelemetrySystemHistory().Values.Count > 0);
// Refresh instance of DiagnosticsHandlerHelper with client telemetry disabled
DiagnosticsHandlerHelper.Refresh(false);
DiagnosticsHandlerHelper diagnosticHandlerHelper3 = DiagnosticsHandlerHelper.GetInstance();
Assert.IsNotNull(diagnosticHandlerHelper3.GetDiagnosticsSystemHistory());
Assert.IsNull(diagnosticHandlerHelper3.GetClientTelemetrySystemHistory());
FieldInfo TelemetrySystemUsageRecorderField3 = typeof(DiagnosticsHandlerHelper).GetField("TelemetrySystemUsageRecorder",
BindingFlags.Static |
BindingFlags.NonPublic);
Assert.IsNull(TelemetrySystemUsageRecorderField3.GetValue(null));
}
}
}