From cd871a21189dde1965dd6f1d874dbffefa41bf2b Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Mon, 17 Oct 2022 19:50:36 +0530 Subject: [PATCH] Client Telemetry: Adds functionality to collect telemetry for collection cache (#3484) * code refcator * collection cahe changes * collect telemetry of collction cache * remove compilation error * cleint telemetry models * first draft * null check * test fix * fix tests * test fix * move AzureVMMetadata to models folder * remove useless import * refactor code * refactor * fix test * fix tests * review comnts * add db information * fix test * revert VM initilization from constructor to request path * fix test * review comments * diagnostic handler null assignment * fix test * minor change Co-authored-by: Sourabh Jain --- Microsoft.Azure.Cosmos/src/DocumentClient.cs | 70 +++++++++- .../src/Handler/ClientPipelineBuilder.cs | 8 +- .../src/Handler/TelemetryHandler.cs | 4 +- .../src/Resource/ClientContextCore.cs | 38 +----- .../CosmosExceptions/CosmosException.cs | 3 +- .../CosmosNullReferenceException.cs | 3 +- .../CosmosObjectDisposedException.cs | 3 +- .../CosmosOperationCanceledException.cs | 6 +- .../src/Routing/ClientCollectionCache.cs | 34 ++++- .../src/Telemetry/ClientTelemetry.cs | 124 ++++++++++++++---- .../src/Telemetry/ClientTelemetryHelper.cs | 46 +++++-- .../src/Telemetry/ClientTelemetryOptions.cs | 1 + .../Telemetry/{ => Models}/AzureVMMetadata.cs | 2 +- .../src/Telemetry/Models/CacheRefreshInfo.cs | 61 +++++++++ .../{ => Models}/ClientTelemetryProperties.cs | 12 +- .../src/Telemetry/{ => Models}/Compute.cs | 12 +- .../src/Telemetry/{ => Models}/MetricInfo.cs | 19 +-- .../Telemetry/{ => Models}/OperationInfo.cs | 49 +++---- .../src/Telemetry/{ => Models}/SystemInfo.cs | 2 +- .../OpenTelemetryCoreRecorder.cs | 2 +- .../src/Telemetry/TelemetrySystemUsage.cs | 1 + .../src/Telemetry/VmMetadataApiHandler.cs | 1 + .../ClientTelemetryTests.cs | 105 ++++++++++++--- .../RegionContactedInDiagnosticsBenchmark.cs | 2 +- .../Mocks/MockDocumentClient.cs | 2 +- .../ClientTelemetryTests.cs | 20 ++- .../CosmosClientOptionsUnitTests.cs | 2 +- .../GatewayStoreModelTest.cs | 22 ++-- .../HandlerTests.cs | 7 +- .../Tracing/ContactedRegionsTests.cs | 4 +- .../Utils/MockCosmosUtil.cs | 3 + .../Utils/MockDocumentClient.cs | 2 +- .../VmMetadataApiHandlerTest.cs | 1 + 33 files changed, 480 insertions(+), 191 deletions(-) rename Microsoft.Azure.Cosmos/src/Telemetry/{ => Models}/AzureVMMetadata.cs (91%) create mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/Models/CacheRefreshInfo.cs rename Microsoft.Azure.Cosmos/src/Telemetry/{ => Models}/ClientTelemetryProperties.cs (92%) rename Microsoft.Azure.Cosmos/src/Telemetry/{ => Models}/Compute.cs (86%) rename Microsoft.Azure.Cosmos/src/Telemetry/{ => Models}/MetricInfo.cs (89%) rename Microsoft.Azure.Cosmos/src/Telemetry/{ => Models}/OperationInfo.cs (81%) rename Microsoft.Azure.Cosmos/src/Telemetry/{ => Models}/SystemInfo.cs (95%) diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index ba62ca3c8b..1d50ba28b1 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -19,6 +19,7 @@ namespace Microsoft.Azure.Cosmos using global::Azure.Core; using Microsoft.Azure.Cosmos.Common; using Microsoft.Azure.Cosmos.Core.Trace; + using Microsoft.Azure.Cosmos.Handler; using Microsoft.Azure.Cosmos.Query; using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; using Microsoft.Azure.Cosmos.Routing; @@ -132,11 +133,13 @@ internal partial class DocumentClient : IDisposable, IAuthorizationTokenProvider private int rntbdSendHangDetectionTimeSeconds = DefaultRntbdSendHangDetectionTimeSeconds; private bool enableCpuMonitor = DefaultEnableCpuMonitor; private int rntbdMaxConcurrentOpeningConnectionCount = 5; + private string clientId; //Consistency private Documents.ConsistencyLevel? desiredConsistencyLevel; internal CosmosAccountServiceConfiguration accountServiceConfiguration { get; private set; } + internal ClientTelemetry clientTelemetry { get; set; } private ClientCollectionCache collectionCache; @@ -417,6 +420,7 @@ internal DocumentClient(Uri serviceEndpoint, /// Transport client handler factory. /// Factory that creates store clients sharing the same transport client to optimize network resource reuse across multiple document clients in the same process. /// Flag to allow Quorum Read with Eventual Consistency Account + /// /// /// The service endpoint can be obtained from the Azure Management Portal. /// If you are connecting using one of the Master Keys, these can be obtained along with the endpoint from the Azure Management Portal @@ -441,7 +445,8 @@ internal DocumentClient(Uri serviceEndpoint, bool? enableCpuMonitor = null, Func transportClientHandlerFactory = null, IStoreClientFactory storeClientFactory = null, - bool isLocalQuorumConsistency = false) + bool isLocalQuorumConsistency = false, + string cosmosClientId = null) { if (sendingRequestEventArgs != null) { @@ -472,7 +477,8 @@ internal DocumentClient(Uri serviceEndpoint, handler: handler, sessionContainer: sessionContainer, enableCpuMonitor: enableCpuMonitor, - storeClientFactory: storeClientFactory); + storeClientFactory: storeClientFactory, + cosmosClientId: cosmosClientId); } /// @@ -634,7 +640,12 @@ private async Task OpenPrivateAsync(CancellationToken cancellationToken) catch (DocumentClientException ex) { // Clear the caches to ensure that we don't have partial results - this.collectionCache = new ClientCollectionCache(this.sessionContainer, this.GatewayStoreModel, this, this.retryPolicy); + this.collectionCache = new ClientCollectionCache( + sessionContainer: this.sessionContainer, + storeModel: this.GatewayStoreModel, + tokenProvider: this, + retryPolicy: this.retryPolicy, + clientTelemetry: this.clientTelemetry); this.partitionKeyRangeCache = new PartitionKeyRangeCache(this, this.GatewayStoreModel, this.collectionCache); DefaultTrace.TraceWarning("{0} occurred while OpenAsync. Exception Message: {1}", ex.ToString(), ex.Message); @@ -648,13 +659,16 @@ internal virtual void Initialize(Uri serviceEndpoint, ISessionContainer sessionContainer = null, bool? enableCpuMonitor = null, IStoreClientFactory storeClientFactory = null, - TokenCredential tokenCredential = null) + TokenCredential tokenCredential = null, + string cosmosClientId = null) { if (serviceEndpoint == null) { throw new ArgumentNullException("serviceEndpoint"); } + this.clientId = cosmosClientId; + this.queryPartitionProvider = new AsyncLazy(async () => { await this.EnsureValidClientAsync(NoOpTrace.Singleton); @@ -928,6 +942,12 @@ internal virtual void Initialize(Uri serviceEndpoint, // For direct: WFStoreProxy [set in OpenAsync()]. this.eventSource = DocumentClientEventSource.Instance; + // Disable system usage for internal builds. Cosmos DB owns the VMs and already logs + // the system information so no need to track it. +#if !INTERNAL + this.InitializeClientTelemetry(); +#endif + this.initializeTaskFactory = (_) => TaskHelper.InlineIfPossible( () => this.GetInitializationTaskAsync(storeClientFactory: storeClientFactory), new ResourceThrottleRetryPolicy( @@ -984,7 +1004,12 @@ private async Task GetInitializationTaskAsync(IStoreClientFactory storeCli this.GatewayStoreModel = gatewayStoreModel; - this.collectionCache = new ClientCollectionCache(this.sessionContainer, this.GatewayStoreModel, this, this.retryPolicy); + this.collectionCache = new ClientCollectionCache( + sessionContainer: this.sessionContainer, + storeModel: this.GatewayStoreModel, + tokenProvider: this, + retryPolicy: this.retryPolicy, + clientTelemetry: this.clientTelemetry); this.partitionKeyRangeCache = new PartitionKeyRangeCache(this, this.GatewayStoreModel, this.collectionCache); this.ResetSessionTokenRetryPolicy = new ResetSessionTokenRetryPolicyFactory(this.sessionContainer, this.collectionCache, this.retryPolicy); @@ -1002,6 +1027,36 @@ private async Task GetInitializationTaskAsync(IStoreClientFactory storeCli return true; } + private void InitializeClientTelemetry() + { + if (this.ConnectionPolicy.EnableClientTelemetry) + { + try + { + this.clientTelemetry = ClientTelemetry.CreateAndStartBackgroundTelemetry( + clientId: this.clientId, + httpClient: this.httpClient, + userAgent: this.ConnectionPolicy.UserAgentContainer.UserAgent, + connectionMode: this.ConnectionPolicy.ConnectionMode, + authorizationTokenProvider: this.cosmosAuthorization, + diagnosticsHelper: DiagnosticsHandlerHelper.Instance, + preferredRegions: this.ConnectionPolicy.PreferredLocations, + globalEndpointManager: this.GlobalEndpointManager); + + DefaultTrace.TraceInformation("Client Telemetry Enabled."); + } + catch (Exception ex) + { + DefaultTrace.TraceInformation($"Error While starting Telemetry Job : {ex.Message}. Hence disabling Client Telemetry"); + this.ConnectionPolicy.EnableClientTelemetry = false; + } + } + else + { + DefaultTrace.TraceInformation("Client Telemetry Disabled."); + } + } + private async Task InitializeCachesAsync(string databaseName, DocumentCollection collection, CancellationToken cancellationToken) { if (databaseName == null) @@ -1272,6 +1327,11 @@ public void Dispose() this.initTaskCache = null; } + if (this.clientTelemetry != null) + { + this.clientTelemetry.Dispose(); + } + DefaultTrace.TraceInformation("DocumentClient with id {0} disposed.", this.traceId); DefaultTrace.Flush(); diff --git a/Microsoft.Azure.Cosmos/src/Handler/ClientPipelineBuilder.cs b/Microsoft.Azure.Cosmos/src/Handler/ClientPipelineBuilder.cs index 3c9b36a552..d4c260f904 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/ClientPipelineBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/ClientPipelineBuilder.cs @@ -45,16 +45,14 @@ public ClientPipelineBuilder( #if !INTERNAL this.diagnosticsHandler = new DiagnosticsHandler(); Debug.Assert(this.diagnosticsHandler.InnerHandler == null, nameof(this.diagnosticsHandler)); - +#else + this.diagnosticsHandler = null; +#endif if (telemetry != null) { this.telemetryHandler = new TelemetryHandler(telemetry); Debug.Assert(this.telemetryHandler.InnerHandler == null, nameof(this.telemetryHandler)); } -#else - this.diagnosticsHandler = null; - this.telemetryHandler = null; -#endif this.UseRetryPolicy(); this.AddCustomHandlers(customHandlers); diff --git a/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs b/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs index c1e85e5b24..449a981d0d 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs @@ -30,7 +30,7 @@ public override async Task SendAsync( try { this.telemetry - .Collect( + .CollectOperationInfo( cosmosDiagnostics: response.Diagnostics, statusCode: response.StatusCode, responseSizeInBytes: this.GetPayloadSize(response), @@ -40,7 +40,7 @@ public override async Task SendAsync( resourceType: request.ResourceType, consistencyLevel: request.Headers?[Documents.HttpConstants.HttpHeaders.ConsistencyLevel], requestCharge: response.Headers.RequestCharge, - subStatusCode: response.Headers.SubStatusCodeLiteral); + subStatusCode: response.Headers.SubStatusCode); } catch (Exception ex) { diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index 2db9aca11c..309e2f84f3 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -29,7 +29,6 @@ internal class ClientContextCore : CosmosClientContext private readonly CosmosResponseFactoryInternal responseFactory; private readonly RequestInvokerHandler requestHandler; private readonly CosmosClientOptions clientOptions; - private readonly ClientTelemetry telemetry; private readonly string userAgent; private bool isDisposed = false; @@ -53,7 +52,6 @@ private ClientContextCore( this.documentClient = documentClient; this.userAgent = userAgent; this.batchExecutorCache = batchExecutorCache; - this.telemetry = telemetry; } internal static CosmosClientContext Create( @@ -81,7 +79,8 @@ internal static CosmosClientContext Create( storeClientFactory: clientOptions.StoreClientFactory, desiredConsistencyLevel: clientOptions.GetDocumentsConsistencyLevel(), handler: httpMessageHandler, - sessionContainer: clientOptions.SessionContainer); + sessionContainer: clientOptions.SessionContainer, + cosmosClientId: cosmosClient.Id); return ClientContextCore.Create( cosmosClient, @@ -107,34 +106,6 @@ internal static CosmosClientContext Create( clientOptions = ClientContextCore.CreateOrCloneClientOptions(clientOptions); - ConnectionPolicy connectionPolicy = clientOptions.GetConnectionPolicy(cosmosClient.ClientId); - ClientTelemetry telemetry = null; - if (connectionPolicy.EnableClientTelemetry) - { - try - { - telemetry = ClientTelemetry.CreateAndStartBackgroundTelemetry( - clientId: cosmosClient.Id, - documentClient: documentClient, - userAgent: connectionPolicy.UserAgentContainer.UserAgent, - connectionMode: connectionPolicy.ConnectionMode, - authorizationTokenProvider: cosmosClient.AuthorizationTokenProvider, - diagnosticsHelper: DiagnosticsHandlerHelper.Instance, - preferredRegions: clientOptions.ApplicationPreferredRegions); - - } - catch (Exception ex) - { - DefaultTrace.TraceInformation($"Error While starting Telemetry Job : {ex.Message}. Hence disabling Client Telemetry"); - connectionPolicy.EnableClientTelemetry = false; - } - - } - else - { - DefaultTrace.TraceInformation("Client Telemetry Disabled."); - } - if (requestInvokerHandler == null) { //Request pipeline @@ -142,7 +113,7 @@ internal static CosmosClientContext Create( cosmosClient, clientOptions.ConsistencyLevel, clientOptions.CustomHandlers, - telemetry: telemetry); + telemetry: documentClient.clientTelemetry); requestInvokerHandler = clientPipelineBuilder.Build(); } @@ -165,7 +136,7 @@ internal static CosmosClientContext Create( documentClient: documentClient, userAgent: documentClient.ConnectionPolicy.UserAgentContainer.UserAgent, batchExecutorCache: new BatchAsyncContainerExecutorCache(), - telemetry: telemetry); + telemetry: documentClient.clientTelemetry); } /// @@ -458,7 +429,6 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - this.telemetry?.Dispose(); this.batchExecutorCache.Dispose(); this.DocumentClient.Dispose(); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs index 2aa7f03187..d789c97b03 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs @@ -291,7 +291,8 @@ internal static void RecordOtelAttributes(CosmosException exception, DiagnosticS { scope.AddAttribute(OpenTelemetryAttributeKeys.StatusCode, exception.StatusCode); scope.AddAttribute(OpenTelemetryAttributeKeys.RequestCharge, exception.RequestCharge); - scope.AddAttribute(OpenTelemetryAttributeKeys.Region, ClientTelemetryHelper.GetContactedRegions(exception.Diagnostics)); + scope.AddAttribute(OpenTelemetryAttributeKeys.Region, + ClientTelemetryHelper.GetContactedRegions(exception.Diagnostics?.GetContactedRegions())); scope.AddAttribute(OpenTelemetryAttributeKeys.ExceptionMessage, exception.Message); CosmosDbEventSource.RecordDiagnosticsForExceptions(exception.Diagnostics); diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNullReferenceException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNullReferenceException.cs index 73ab8134fb..03e4cb21e8 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNullReferenceException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNullReferenceException.cs @@ -79,7 +79,8 @@ public override string ToString() internal static void RecordOtelAttributes(CosmosNullReferenceException exception, DiagnosticScope scope) { - scope.AddAttribute(OpenTelemetryAttributeKeys.Region, ClientTelemetryHelper.GetContactedRegions(exception.Diagnostics)); + scope.AddAttribute(OpenTelemetryAttributeKeys.Region, + ClientTelemetryHelper.GetContactedRegions(exception.Diagnostics?.GetContactedRegions())); scope.AddAttribute(OpenTelemetryAttributeKeys.ExceptionMessage, exception.GetBaseException().Message); CosmosDbEventSource.RecordDiagnosticsForExceptions(exception.Diagnostics); diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosObjectDisposedException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosObjectDisposedException.cs index f2aadae767..64d5018f60 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosObjectDisposedException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosObjectDisposedException.cs @@ -96,7 +96,8 @@ public override string ToString() /// internal static void RecordOtelAttributes(CosmosObjectDisposedException exception, DiagnosticScope scope) { - scope.AddAttribute(OpenTelemetryAttributeKeys.Region, ClientTelemetryHelper.GetContactedRegions(exception.Diagnostics)); + scope.AddAttribute(OpenTelemetryAttributeKeys.Region, + ClientTelemetryHelper.GetContactedRegions(exception.Diagnostics?.GetContactedRegions())); scope.AddAttribute(OpenTelemetryAttributeKeys.ExceptionMessage, exception.Message); CosmosDbEventSource.RecordDiagnosticsForExceptions(exception.Diagnostics); diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosOperationCanceledException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosOperationCanceledException.cs index 7cba912c2f..e21843656a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosOperationCanceledException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosOperationCanceledException.cs @@ -135,8 +135,10 @@ private Lazy CreateToStringMessage() /// internal static void RecordOtelAttributes(CosmosOperationCanceledException exception, DiagnosticScope scope) { - scope.AddAttribute(OpenTelemetryAttributeKeys.Region, ClientTelemetryHelper.GetContactedRegions(exception.Diagnostics)); - scope.AddAttribute(OpenTelemetryAttributeKeys.ExceptionMessage, exception.GetBaseException().Message); + scope.AddAttribute(OpenTelemetryAttributeKeys.Region, + ClientTelemetryHelper.GetContactedRegions(exception.Diagnostics?.GetContactedRegions())); + scope.AddAttribute(OpenTelemetryAttributeKeys.ExceptionMessage, + exception.GetBaseException().Message); CosmosDbEventSource.RecordDiagnosticsForExceptions(exception.Diagnostics); } diff --git a/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs b/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs index d38cf2180c..30ece5015b 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Cosmos.Routing using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Common; + using Microsoft.Azure.Cosmos.Telemetry; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Cosmos.Tracing.TraceData; using Microsoft.Azure.Documents; @@ -18,21 +19,26 @@ namespace Microsoft.Azure.Cosmos.Routing /// internal class ClientCollectionCache : CollectionCache { + private const string TelemetrySourceName = "ClientCollectionCache"; + private readonly IStoreModel storeModel; private readonly ICosmosAuthorizationTokenProvider tokenProvider; private readonly IRetryPolicyFactory retryPolicy; private readonly ISessionContainer sessionContainer; + private readonly ClientTelemetry clientTelemetry; public ClientCollectionCache( ISessionContainer sessionContainer, IStoreModel storeModel, ICosmosAuthorizationTokenProvider tokenProvider, - IRetryPolicyFactory retryPolicy) + IRetryPolicyFactory retryPolicy, + ClientTelemetry clientTelemetry) { this.storeModel = storeModel ?? throw new ArgumentNullException("storeModel"); this.tokenProvider = tokenProvider; this.retryPolicy = retryPolicy; this.sessionContainer = sessionContainer; + this.clientTelemetry = clientTelemetry; } protected override Task GetByRidAsync(string apiVersion, @@ -207,7 +213,24 @@ private async Task ReadCollectionAsync( using (DocumentServiceResponse response = await this.storeModel.ProcessMessageAsync(request)) { - return CosmosResource.FromStream(response); + ContainerProperties containerProperties = CosmosResource.FromStream(response); + + if (this.clientTelemetry != null) + { + ClientCollectionCache.GetDatabaseAndCollectionName(collectionLink, out string databaseName, out string collectionName); + this.clientTelemetry.CollectCacheInfo( + cacheRefreshSource: ClientCollectionCache.TelemetrySourceName, + regionsContactedList: response.RequestStats.RegionsContacted, + requestLatency: response.RequestStats.RequestLatency, + statusCode: response.StatusCode, + containerId: collectionName, + operationType: request.OperationType, + resourceType: request.ResourceType, + subStatusCode: response.SubStatusCode, + databaseId: databaseName); + } + + return containerProperties; } } catch (DocumentClientException ex) @@ -219,5 +242,12 @@ await this.storeModel.ProcessMessageAsync(request)) } } } + + private static void GetDatabaseAndCollectionName(string path, out string databaseName, out string collectionName) + { + string[] segments = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + PathsHelper.ParseDatabaseNameAndCollectionNameFromUrlSegments(segments, out databaseName, out collectionName); + } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetry.cs b/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetry.cs index 272c11490a..3f124e2ee0 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetry.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetry.cs @@ -15,6 +15,8 @@ namespace Microsoft.Azure.Cosmos.Telemetry using Handler; using HdrHistogram; using Microsoft.Azure.Cosmos.Core.Trace; + using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Cosmos.Telemetry.Models; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Collections; using Microsoft.Azure.Documents.Rntbd; @@ -35,50 +37,65 @@ internal class ClientTelemetry : IDisposable private static readonly TimeSpan observingWindow = ClientTelemetryOptions.GetScheduledTimeSpan(); private readonly ClientTelemetryProperties clientTelemetryInfo; - private readonly DocumentClient documentClient; private readonly CosmosHttpClient httpClient; private readonly AuthorizationTokenProvider tokenProvider; private readonly DiagnosticsHandlerHelper diagnosticsHelper; private readonly CancellationTokenSource cancellationTokenSource; + private readonly GlobalEndpointManager globalEndpointManager; + private Task telemetryTask; private ConcurrentDictionary operationInfoMap = new ConcurrentDictionary(); + private ConcurrentDictionary cacheRefreshInfoMap + = new ConcurrentDictionary(); + private int numberOfFailures = 0; + /// + /// Only for Mocking in tests + /// + internal ClientTelemetry() + { + this.cancellationTokenSource = new CancellationTokenSource(); + } + /// /// Factory method to intiakize telemetry object and start observer task /// /// - /// + /// /// /// /// /// /// + /// /// ClientTelemetry public static ClientTelemetry CreateAndStartBackgroundTelemetry( string clientId, - DocumentClient documentClient, + CosmosHttpClient httpClient, string userAgent, ConnectionMode connectionMode, AuthorizationTokenProvider authorizationTokenProvider, DiagnosticsHandlerHelper diagnosticsHelper, - IReadOnlyList preferredRegions) + IReadOnlyList preferredRegions, + GlobalEndpointManager globalEndpointManager) { DefaultTrace.TraceInformation("Initiating telemetry with background task."); ClientTelemetry clientTelemetry = new ClientTelemetry( clientId, - documentClient, + httpClient, userAgent, connectionMode, authorizationTokenProvider, diagnosticsHelper, - preferredRegions); + preferredRegions, + globalEndpointManager); clientTelemetry.StartObserverTask(); @@ -87,14 +104,15 @@ public static ClientTelemetry CreateAndStartBackgroundTelemetry( private ClientTelemetry( string clientId, - DocumentClient documentClient, + CosmosHttpClient httpClient, string userAgent, ConnectionMode connectionMode, AuthorizationTokenProvider authorizationTokenProvider, DiagnosticsHandlerHelper diagnosticsHelper, - IReadOnlyList preferredRegions) + IReadOnlyList preferredRegions, + GlobalEndpointManager globalEndpointManager) { - this.documentClient = documentClient ?? throw new ArgumentNullException(nameof(documentClient)); + this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); this.diagnosticsHelper = diagnosticsHelper ?? throw new ArgumentNullException(nameof(diagnosticsHelper)); this.tokenProvider = authorizationTokenProvider ?? throw new ArgumentNullException(nameof(authorizationTokenProvider)); @@ -106,8 +124,8 @@ private ClientTelemetry( preferredRegions: preferredRegions, aggregationIntervalInSec: (int)observingWindow.TotalSeconds); - this.httpClient = documentClient.httpClient; this.cancellationTokenSource = new CancellationTokenSource(); + this.globalEndpointManager = globalEndpointManager; } /// @@ -138,26 +156,20 @@ private async Task EnrichAndSendAsync() break; } - // Load account information if not available, cache is already implemented - if (String.IsNullOrEmpty(this.clientTelemetryInfo.GlobalDatabaseAccountName)) + if (string.IsNullOrEmpty(this.clientTelemetryInfo.GlobalDatabaseAccountName)) { - AccountProperties accountProperties = await ClientTelemetryHelper.SetAccountNameAsync(this.documentClient); - this.clientTelemetryInfo.GlobalDatabaseAccountName = accountProperties?.Id; + AccountProperties accountProperties = await ClientTelemetryHelper.SetAccountNameAsync(this.globalEndpointManager); + this.clientTelemetryInfo.GlobalDatabaseAccountName = accountProperties.Id; } + + await Task.Delay(observingWindow, this.cancellationTokenSource.Token); + + this.clientTelemetryInfo.MachineId = VmMetadataApiHandler.GetMachineId(); // Load host information from cache Compute vmInformation = VmMetadataApiHandler.GetMachineInfo(); - if (vmInformation != null) - { - this.clientTelemetryInfo.ApplicationRegion = vmInformation.Location; - this.clientTelemetryInfo.HostEnvInfo = ClientTelemetryOptions.GetHostInformation(vmInformation); - - //TODO: Set AcceleratingNetwork flag from instance metadata once it is available. - } - - this.clientTelemetryInfo.MachineId = VmMetadataApiHandler.GetMachineId(); - - await Task.Delay(observingWindow, this.cancellationTokenSource.Token); + this.clientTelemetryInfo.ApplicationRegion = vmInformation?.Location; + this.clientTelemetryInfo.HostEnvInfo = ClientTelemetryOptions.GetHostInformation(vmInformation); // If cancellation is requested after the delay then return from here. if (this.cancellationTokenSource.IsCancellationRequested) @@ -174,7 +186,11 @@ private async Task EnrichAndSendAsync() ConcurrentDictionary operationInfoSnapshot = Interlocked.Exchange(ref this.operationInfoMap, new ConcurrentDictionary()); + ConcurrentDictionary cacheRefreshInfoSnapshot + = Interlocked.Exchange(ref this.cacheRefreshInfoMap, new ConcurrentDictionary()); + this.clientTelemetryInfo.OperationInfo = ClientTelemetryHelper.ToListWithMetricsInfo(operationInfoSnapshot); + this.clientTelemetryInfo.CacheRefreshInfo = ClientTelemetryHelper.ToListWithMetricsInfo(cacheRefreshInfoSnapshot); await this.SendAsync(); } @@ -187,6 +203,56 @@ private async Task EnrichAndSendAsync() DefaultTrace.TraceInformation("Telemetry Job Stopped."); } + /// + /// Collects Cache Telemetry Information. + /// + internal void CollectCacheInfo(string cacheRefreshSource, + HashSet<(string regionName, Uri uri)> regionsContactedList, + TimeSpan? requestLatency, + HttpStatusCode statusCode, + string containerId, + OperationType operationType, + ResourceType resourceType, + SubStatusCodes subStatusCode, + string databaseId, + long responseSizeInBytes = 0, + string consistencyLevel = null ) + { + if (string.IsNullOrEmpty(cacheRefreshSource)) + { + throw new ArgumentNullException(nameof(cacheRefreshSource)); + } + + DefaultTrace.TraceVerbose($"Collecting cacheRefreshSource {cacheRefreshSource} data for Telemetry."); + + string regionsContacted = ClientTelemetryHelper.GetContactedRegions(regionsContactedList); + + // Recording Request Latency + CacheRefreshInfo payloadKey = new CacheRefreshInfo(cacheRefreshSource: cacheRefreshSource, + regionsContacted: regionsContacted?.ToString(), + responseSizeInBytes: responseSizeInBytes, + consistency: consistencyLevel, + databaseName: databaseId, + containerName: containerId, + operation: operationType, + resource: resourceType, + statusCode: (int)statusCode, + subStatusCode: (int)subStatusCode); + + LongConcurrentHistogram latency = this.cacheRefreshInfoMap + .GetOrAdd(payloadKey, new LongConcurrentHistogram(ClientTelemetryOptions.RequestLatencyMin, + ClientTelemetryOptions.RequestLatencyMax, + ClientTelemetryOptions.RequestLatencyPrecision)); + try + { + latency.RecordValue(requestLatency.Value.Ticks); + } + catch (Exception ex) + { + DefaultTrace.TraceError("Latency Recording Failed by Telemetry. Exception : {0}", ex.Message); + } + } + /// /// Collects Telemetry Information. /// @@ -200,7 +266,7 @@ private async Task EnrichAndSendAsync() /// /// /// - internal void Collect(CosmosDiagnostics cosmosDiagnostics, + internal void CollectOperationInfo(CosmosDiagnostics cosmosDiagnostics, HttpStatusCode statusCode, long responseSizeInBytes, string containerId, @@ -209,7 +275,7 @@ internal void Collect(CosmosDiagnostics cosmosDiagnostics, ResourceType resourceType, string consistencyLevel, double requestCharge, - string subStatusCode) + SubStatusCodes subStatusCode) { DefaultTrace.TraceVerbose("Collecting Operation data for Telemetry."); @@ -218,7 +284,7 @@ internal void Collect(CosmosDiagnostics cosmosDiagnostics, throw new ArgumentNullException(nameof(cosmosDiagnostics)); } - string regionsContacted = ClientTelemetryHelper.GetContactedRegions(cosmosDiagnostics); + string regionsContacted = ClientTelemetryHelper.GetContactedRegions(cosmosDiagnostics.GetContactedRegions()); // Recording Request Latency and Request Charge OperationInfo payloadKey = new OperationInfo(regionsContacted: regionsContacted?.ToString(), @@ -229,7 +295,7 @@ internal void Collect(CosmosDiagnostics cosmosDiagnostics, operation: operationType, resource: resourceType, statusCode: (int)statusCode, - subStatusCode: subStatusCode); + subStatusCode: (int)subStatusCode); (LongConcurrentHistogram latency, LongConcurrentHistogram requestcharge) = this.operationInfoMap .GetOrAdd(payloadKey, x => (latency: new LongConcurrentHistogram(ClientTelemetryOptions.RequestLatencyMin, diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetryHelper.cs b/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetryHelper.cs index 963dfd0c4e..dca2ef6b94 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetryHelper.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetryHelper.cs @@ -5,12 +5,15 @@ namespace Microsoft.Azure.Cosmos.Telemetry { using System; using System.Collections.Generic; + using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using HdrHistogram; using Microsoft.Azure.Cosmos.Core.Trace; + using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Cosmos.Telemetry.Models; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Rntbd; @@ -20,14 +23,14 @@ internal static class ClientTelemetryHelper /// Task to get Account Properties from cache if available otherwise make a network call. /// /// Async Task - internal static async Task SetAccountNameAsync(DocumentClient documentclient) + internal static async Task SetAccountNameAsync(GlobalEndpointManager globalEndpointManager) { DefaultTrace.TraceVerbose("Getting Account Information for Telemetry."); try { - if (documentclient.GlobalEndpointManager != null) + if (globalEndpointManager != null) { - return await documentclient.GlobalEndpointManager.GetDatabaseAccountAsync(); + return await globalEndpointManager.GetDatabaseAccountAsync(); } } catch (Exception ex) @@ -58,7 +61,7 @@ internal static void RecordSystemUsage( return; } - DefaultTrace.TraceInformation("System Usage recorded by telemetry is : {0}", systemUsageHistory); + DefaultTrace.TraceVerbose("System Usage recorded by telemetry is : {0}", systemUsageHistory); systemInfoCollection.Add(TelemetrySystemUsage.GetCpuInfo(systemUsageHistory.Values)); systemInfoCollection.Add(TelemetrySystemUsage.GetMemoryRemainingInfo(systemUsageHistory.Values)); @@ -82,7 +85,7 @@ internal static List ToListWithMetricsInfo( IDictionary metrics) { - DefaultTrace.TraceInformation("Aggregating operation information to list started"); + DefaultTrace.TraceVerbose("Aggregating operation information to list started"); List payloadWithMetricInformation = new List(); foreach (KeyValuePair entry in metrics) @@ -105,15 +108,36 @@ internal static List ToListWithMetricsInfo( return payloadWithMetricInformation; } + /// + /// Convert map with CacheRefreshInfo information to list of operations along with request latency and request charge metrics + /// + /// + /// Collection of ReportPayload + internal static List ToListWithMetricsInfo(IDictionary metrics) + { + DefaultTrace.TraceVerbose("Aggregating CacheRefreshInfo information to list started"); + + List payloadWithMetricInformation = new List(); + foreach (KeyValuePair entry in metrics) + { + CacheRefreshInfo payloadForLatency = entry.Key; + payloadForLatency.MetricInfo = new MetricInfo(ClientTelemetryOptions.RequestLatencyName, ClientTelemetryOptions.RequestLatencyUnit); + payloadForLatency.SetAggregators(entry.Value, ClientTelemetryOptions.TicksToMsFactor); + + payloadWithMetricInformation.Add(payloadForLatency); + } + + DefaultTrace.TraceVerbose("Aggregating CacheRefreshInfo information to list done"); + + return payloadWithMetricInformation; + } + /// /// Get comma separated list of regions contacted from the diagnostic /// - /// /// Comma separated region list - internal static string GetContactedRegions(CosmosDiagnostics cosmosDiagnostics) + internal static string GetContactedRegions(IReadOnlyCollection<(string regionName, Uri uri)> regionList) { - IReadOnlyList<(string regionName, Uri uri)> regionList = cosmosDiagnostics.GetContactedRegions(); - if (regionList == null || regionList.Count == 0) { return null; @@ -121,9 +145,9 @@ internal static string GetContactedRegions(CosmosDiagnostics cosmosDiagnostics) if (regionList.Count == 1) { - return regionList[0].regionName; + return regionList.ElementAt(0).regionName; } - + StringBuilder regionsContacted = new StringBuilder(); foreach ((string name, _) in regionList) { diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetryOptions.cs b/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetryOptions.cs index cb090a0c0c..f114483b07 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetryOptions.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetryOptions.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry { using System; using Microsoft.Azure.Cosmos.Core.Trace; + using Microsoft.Azure.Cosmos.Telemetry.Models; using Microsoft.Azure.Documents; using Newtonsoft.Json; using Util; diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/AzureVMMetadata.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Models/AzureVMMetadata.cs similarity index 91% rename from Microsoft.Azure.Cosmos/src/Telemetry/AzureVMMetadata.cs rename to Microsoft.Azure.Cosmos/src/Telemetry/Models/AzureVMMetadata.cs index b1547b546d..4b08d58286 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/AzureVMMetadata.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Models/AzureVMMetadata.cs @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Telemetry +namespace Microsoft.Azure.Cosmos.Telemetry.Models { using System; using Newtonsoft.Json; diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/Models/CacheRefreshInfo.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Models/CacheRefreshInfo.cs new file mode 100644 index 0000000000..85d6676844 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Models/CacheRefreshInfo.cs @@ -0,0 +1,61 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Telemetry.Models +{ + using System; + using Microsoft.Azure.Documents; + using Newtonsoft.Json; + + [Serializable] + internal sealed class CacheRefreshInfo : OperationInfo + { + [JsonProperty(PropertyName = "cacheRefreshSource")] + internal string CacheRefreshSource { get; } + + internal CacheRefreshInfo(string metricsName, string unitName) + : base(metricsName, unitName) + { + } + + [JsonConstructor] + internal CacheRefreshInfo(string regionsContacted, + long? responseSizeInBytes, + string consistency, + string databaseName, + string containerName, + OperationType? operation, + ResourceType? resource, + int? statusCode, + int subStatusCode, + string cacheRefreshSource) + : base( + regionsContacted: regionsContacted, + responseSizeInBytes: responseSizeInBytes, + consistency: consistency, + databaseName: databaseName, + containerName: containerName, + operation: operation, + resource: resource, + statusCode: statusCode, + subStatusCode: subStatusCode) + { + this.CacheRefreshSource = cacheRefreshSource; + } + + public override int GetHashCode() + { + int hash = base.GetHashCode(); + hash = (hash * 7) ^ (this.CacheRefreshSource == null ? 0 : this.CacheRefreshSource.GetHashCode()); + return hash; + } + + public override bool Equals(object obj) + { + return base.Equals(obj) && + obj is CacheRefreshInfo payload && + String.CompareOrdinal(this.CacheRefreshSource, payload.CacheRefreshSource) == 0; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetryProperties.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Models/ClientTelemetryProperties.cs similarity index 92% rename from Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetryProperties.cs rename to Microsoft.Azure.Cosmos/src/Telemetry/Models/ClientTelemetryProperties.cs index 1cab0ff889..87418ac8d7 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/ClientTelemetryProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Models/ClientTelemetryProperties.cs @@ -2,12 +2,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Telemetry +namespace Microsoft.Azure.Cosmos.Telemetry.Models { using System; using System.Collections.Generic; using Newtonsoft.Json; - using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; [Serializable] @@ -56,7 +55,7 @@ internal sealed class ClientTelemetryProperties internal List SystemInfo { get; set; } [JsonProperty(PropertyName = "cacheRefreshInfo")] - private List CacheRefreshInfo { get; set; } + internal List CacheRefreshInfo { get; set; } [JsonProperty(PropertyName = "operationInfo")] internal List OperationInfo { get; set; } @@ -75,10 +74,7 @@ internal ClientTelemetryProperties(string clientId, this.ProcessId = processId; this.UserAgent = userAgent; this.ConnectionMode = connectionMode.ToString().ToUpperInvariant(); - if (connectionMode == Microsoft.Azure.Cosmos.ConnectionMode.Direct) - { - this.IsDirectConnectionMode = true; - } + this.IsDirectConnectionMode = connectionMode == Cosmos.ConnectionMode.Direct; this.SystemInfo = new List(); this.PreferredRegions = preferredRegions; this.AggregationIntervalInSec = aggregationIntervalInSec; @@ -99,7 +95,7 @@ public ClientTelemetryProperties(string dateTimeUtc, bool? acceleratedNetworking, IReadOnlyList preferredRegions, List systemInfo, - List cacheRefreshInfo, + List cacheRefreshInfo, List operationInfo, string machineId) { diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/Compute.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Models/Compute.cs similarity index 86% rename from Microsoft.Azure.Cosmos/src/Telemetry/Compute.cs rename to Microsoft.Azure.Cosmos/src/Telemetry/Models/Compute.cs index b09af55cce..6fade8407a 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/Compute.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Models/Compute.cs @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Telemetry +namespace Microsoft.Azure.Cosmos.Telemetry.Models { using System; using Newtonsoft.Json; @@ -13,11 +13,11 @@ internal sealed class Compute { [JsonConstructor] public Compute( - string vMId, - string location, - string sKU, - string azEnvironment, - string oSType, + string vMId, + string location, + string sKU, + string azEnvironment, + string oSType, string vMSize) { this.Location = location; diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/MetricInfo.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Models/MetricInfo.cs similarity index 89% rename from Microsoft.Azure.Cosmos/src/Telemetry/MetricInfo.cs rename to Microsoft.Azure.Cosmos/src/Telemetry/Models/MetricInfo.cs index 0e4dc28cd8..2ab1902a6f 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/MetricInfo.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Models/MetricInfo.cs @@ -2,11 +2,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Telemetry +namespace Microsoft.Azure.Cosmos.Telemetry.Models { using System; using System.Collections.Generic; using HdrHistogram; + using Microsoft.Azure.Cosmos.Telemetry; using Microsoft.Azure.Cosmos.Util; using Newtonsoft.Json; @@ -19,12 +20,12 @@ internal MetricInfo(string metricsName, string unitName) this.UnitName = unitName; } - public MetricInfo(string metricsName, - string unitName, - double mean = 0, - long count = 0, - long min = 0, - long max = 0, + public MetricInfo(string metricsName, + string unitName, + double mean = 0, + long count = 0, + long min = 0, + long max = 0, IReadOnlyDictionary percentiles = null) : this(metricsName, unitName) { @@ -36,10 +37,10 @@ public MetricInfo(string metricsName, } [JsonProperty(PropertyName = "metricsName")] - internal String MetricsName { get; } + internal string MetricsName { get; } [JsonProperty(PropertyName = "unitName")] - internal String UnitName { get; } + internal string UnitName { get; } [JsonProperty(PropertyName = "mean")] internal double Mean { get; set; } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OperationInfo.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Models/OperationInfo.cs similarity index 81% rename from Microsoft.Azure.Cosmos/src/Telemetry/OperationInfo.cs rename to Microsoft.Azure.Cosmos/src/Telemetry/Models/OperationInfo.cs index a76d7d6d5e..7c4a2575d4 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OperationInfo.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Models/OperationInfo.cs @@ -2,15 +2,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Telemetry +namespace Microsoft.Azure.Cosmos.Telemetry.Models { using System; using HdrHistogram; + using Microsoft.Azure.Cosmos.Telemetry; using Microsoft.Azure.Documents; using Newtonsoft.Json; [Serializable] - internal sealed class OperationInfo + internal class OperationInfo { [JsonProperty(PropertyName = "regionsContacted")] internal string RegionsContacted { get; } @@ -19,10 +20,10 @@ internal sealed class OperationInfo internal bool? GreaterThan1Kb { get; set; } [JsonProperty(PropertyName = "databaseName")] - private string DatabaseName { get; } + internal string DatabaseName { get; } [JsonProperty(PropertyName = "containerName")] - private string ContainerName { get; } + internal string ContainerName { get; } [JsonProperty(PropertyName = "operation")] internal string Operation { get; } @@ -37,7 +38,7 @@ internal sealed class OperationInfo public int? StatusCode { get; } [JsonProperty(PropertyName = "subStatusCode")] - public string SubStatusCode { get; } + public int SubStatusCode { get; } [JsonProperty(PropertyName = "metricInfo")] internal MetricInfo MetricInfo { get; set; } @@ -47,15 +48,15 @@ internal OperationInfo(string metricsName, string unitName) this.MetricInfo = new MetricInfo(metricsName, unitName); } - internal OperationInfo(string regionsContacted, - long? responseSizeInBytes, - string consistency, - string databaseName, - string containerName, - OperationType? operation, - ResourceType? resource, + internal OperationInfo(string regionsContacted, + long? responseSizeInBytes, + string consistency, + string databaseName, + string containerName, + OperationType? operation, + ResourceType? resource, int? statusCode, - string subStatusCode) + int subStatusCode) { this.RegionsContacted = regionsContacted; if (responseSizeInBytes != null) @@ -71,15 +72,15 @@ internal OperationInfo(string regionsContacted, this.SubStatusCode = subStatusCode; } - public OperationInfo(string regionsContacted, - bool? greaterThan1Kb, - string databaseName, - string containerName, - string operation, - string resource, - string consistency, + public OperationInfo(string regionsContacted, + bool? greaterThan1Kb, + string databaseName, + string containerName, + string operation, + string resource, + string consistency, int? statusCode, - string subStatusCode, + int subStatusCode, MetricInfo metricInfo) { this.RegionsContacted = regionsContacted; @@ -119,7 +120,7 @@ public override int GetHashCode() hash = (hash * 7) ^ (this.Operation == null ? 0 : this.Operation.GetHashCode()); hash = (hash * 7) ^ (this.Resource == null ? 0 : this.Resource.GetHashCode()); hash = (hash * 7) ^ (this.StatusCode == null ? 0 : this.StatusCode.GetHashCode()); - hash = (hash * 7) ^ (this.SubStatusCode == null ? 0 : this.SubStatusCode.GetHashCode()); + hash = (hash * 7) ^ (this.SubStatusCode.GetHashCode()); return hash; } @@ -127,14 +128,14 @@ public override bool Equals(object obj) { bool isequal = obj is OperationInfo payload && ((this.RegionsContacted == null && payload.RegionsContacted == null) || (this.RegionsContacted != null && payload.RegionsContacted != null && this.RegionsContacted.Equals(payload.RegionsContacted))) && - ((this.GreaterThan1Kb == null && payload.GreaterThan1Kb == null ) || (this.GreaterThan1Kb != null && payload.GreaterThan1Kb != null && this.GreaterThan1Kb.Equals(payload.GreaterThan1Kb))) && + ((this.GreaterThan1Kb == null && payload.GreaterThan1Kb == null) || (this.GreaterThan1Kb != null && payload.GreaterThan1Kb != null && this.GreaterThan1Kb.Equals(payload.GreaterThan1Kb))) && ((this.Consistency == null && payload.Consistency == null) || (this.Consistency != null && payload.Consistency != null && this.Consistency.Equals(payload.Consistency))) && ((this.DatabaseName == null && payload.DatabaseName == null) || (this.DatabaseName != null && payload.DatabaseName != null && this.DatabaseName.Equals(payload.DatabaseName))) && ((this.ContainerName == null && payload.ContainerName == null) || (this.ContainerName != null && payload.ContainerName != null && this.ContainerName.Equals(payload.ContainerName))) && ((this.Operation == null && payload.Operation == null) || (this.Operation != null && payload.Operation != null && this.Operation.Equals(payload.Operation))) && ((this.Resource == null && payload.Resource == null) || (this.Resource != null && payload.Resource != null && this.Resource.Equals(payload.Resource))) && ((this.StatusCode == null && payload.StatusCode == null) || (this.StatusCode != null && payload.StatusCode != null && this.StatusCode.Equals(payload.StatusCode))) && - ((this.SubStatusCode == null && payload.SubStatusCode == null) || (this.SubStatusCode != null && payload.SubStatusCode != null && this.SubStatusCode.Equals(payload.SubStatusCode))); + this.SubStatusCode.Equals(payload.SubStatusCode); return isequal; } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/SystemInfo.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Models/SystemInfo.cs similarity index 95% rename from Microsoft.Azure.Cosmos/src/Telemetry/SystemInfo.cs rename to Microsoft.Azure.Cosmos/src/Telemetry/Models/SystemInfo.cs index 996aad8c59..cb3bd79687 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/SystemInfo.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Models/SystemInfo.cs @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Telemetry +namespace Microsoft.Azure.Cosmos.Telemetry.Models { using System; using HdrHistogram; diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs index c498fbeafb..f825fbf3b7 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs @@ -88,7 +88,7 @@ public void Record(OpenTelemetryAttributes response) if (response.Diagnostics != null) { - this.scope.AddAttribute(OpenTelemetryAttributeKeys.Region, ClientTelemetryHelper.GetContactedRegions(response.Diagnostics) ?? OpenTelemetryAttributes.NotAvailable); + this.scope.AddAttribute(OpenTelemetryAttributeKeys.Region, ClientTelemetryHelper.GetContactedRegions(response.Diagnostics.GetContactedRegions()) ?? OpenTelemetryAttributes.NotAvailable); CosmosDbEventSource.RecordDiagnosticsForRequests(this.config, response); } else diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/TelemetrySystemUsage.cs b/Microsoft.Azure.Cosmos/src/Telemetry/TelemetrySystemUsage.cs index 8366d588e3..9106bafd7d 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/TelemetrySystemUsage.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/TelemetrySystemUsage.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System; using System.Collections.Generic; using HdrHistogram; + using Microsoft.Azure.Cosmos.Telemetry.Models; using Microsoft.Azure.Documents.Rntbd; /// diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/VmMetadataApiHandler.cs b/Microsoft.Azure.Cosmos/src/Telemetry/VmMetadataApiHandler.cs index e8c4a802ef..41e82799c3 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/VmMetadataApiHandler.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/VmMetadataApiHandler.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System.Text; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Core.Trace; + using Microsoft.Azure.Cosmos.Telemetry.Models; using Microsoft.Azure.Documents; using Newtonsoft.Json.Linq; using Util; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTelemetryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTelemetryTests.cs index 8ce2aa9327..5a4f3b086d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTelemetryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientTelemetryTests.cs @@ -10,7 +10,6 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Threading.Tasks; using System.Net; using System.Net.Http; - using System.Diagnostics; using System.Reflection; using Microsoft.Azure.Cosmos.Fluent; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -24,6 +23,9 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Globalization; using System.Linq; using Cosmos.Util; + using Microsoft.Azure.Cosmos.Telemetry.Models; + using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Cosmos.Common; [TestClass] public class ClientTelemetryTests : BaseCosmosClientHelper @@ -248,6 +250,7 @@ public async Task PointReadFailureOperationsTest(ConnectionMode mode) try { Container container = await this.CreateClientAndContainer(mode, Microsoft.Azure.Cosmos.ConsistencyLevel.ConsistentPrefix); + await container.ReadItemAsync( new Guid().ToString(), new Cosmos.PartitionKey(new Guid().ToString()), @@ -269,7 +272,8 @@ await container.ReadItemAsync( await this.WaitAndAssert(expectedOperationCount: 2, expectedConsistencyLevel: Microsoft.Azure.Cosmos.ConsistencyLevel.Eventual, - expectedOperationRecordCountMap: expectedRecordCountInOperation); + expectedOperationRecordCountMap: expectedRecordCountInOperation, + expectedCacheSource: null); } [TestMethod] @@ -303,7 +307,8 @@ await container.ReadItemStreamAsync( await this.WaitAndAssert(expectedOperationCount: 2, expectedConsistencyLevel: Microsoft.Azure.Cosmos.ConsistencyLevel.ConsistentPrefix, - expectedOperationRecordCountMap: expectedRecordCountInOperation); + expectedOperationRecordCountMap: expectedRecordCountInOperation, + expectedCacheSource: null); } [TestMethod] @@ -312,6 +317,7 @@ await this.WaitAndAssert(expectedOperationCount: 2, public async Task StreamOperationsTest(ConnectionMode mode) { Container container = await this.CreateClientAndContainer(mode); + // Create an item var testItem = new { id = "MyTestItemId", partitionKeyPath = "MyTestPkValue", details = "it's working", status = "done" }; await container @@ -351,7 +357,8 @@ await container }; await this.WaitAndAssert(expectedOperationCount: 12, - expectedOperationRecordCountMap: expectedRecordCountInOperation); + expectedOperationRecordCountMap: expectedRecordCountInOperation, + expectedCacheSource: null); } [TestMethod] @@ -653,6 +660,10 @@ public async Task QueryOperationInvalidContinuationTokenTest(ConnectionMode mode { Container container = await this.CreateClientAndContainer(mode); + // Create an item : First successful request to load Cache + ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity("MyTestPkValue"); + await container.CreateItemAsync(testItem); + List results = new List(); using (FeedIterator resultSetIterator = container.GetItemQueryIterator( "SELECT * FROM c", @@ -673,8 +684,13 @@ public async Task QueryOperationInvalidContinuationTokenTest(ConnectionMode mode } } - await this.WaitAndAssert(expectedOperationCount: 0, - expectedSubstatuscode: "0"); // Does not record telemetry + IDictionary expectedRecordCountInOperation = new Dictionary + { + { Documents.OperationType.Create.ToString(), 1} + }; + + await this.WaitAndAssert(expectedOperationCount: 2, + expectedOperationRecordCountMap: expectedRecordCountInOperation); } [TestMethod] @@ -750,7 +766,7 @@ public async Task CreateItemWithSubStatusCodeTest(ConnectionMode mode) await this.WaitAndAssert(expectedOperationCount: 2, expectedOperationRecordCountMap: expectedRecordCountInOperation, - expectedSubstatuscode: "999999"); + expectedSubstatuscode: 999999); } @@ -765,14 +781,16 @@ private async Task WaitAndAssert( int expectedOperationCount = 0, Microsoft.Azure.Cosmos.ConsistencyLevel? expectedConsistencyLevel = null, IDictionary expectedOperationRecordCountMap = null, - string expectedSubstatuscode = null, - bool? isAzureInstance = null) + int expectedSubstatuscode = 0, + bool? isAzureInstance = null, + string expectedCacheSource = "ClientCollectionCache") { Assert.IsNotNull(this.actualInfo, "Telemetry Information not available"); // As this feature is thread based execution so wait for the results to avoid test flakiness List localCopyOfActualInfo = null; ValueStopwatch stopwatch = ValueStopwatch.StartNew(); + HashSet cacheRefreshInfoSet = new HashSet(); do { await Task.Delay(TimeSpan.FromMilliseconds(1500)); // wait at least for 1 round of telemetry @@ -782,12 +800,26 @@ private async Task WaitAndAssert( { // Setting the number of unique OperationInfo irrespective of response size as response size is varying in case of queries. this.actualInfo - .ForEach(x => x.OperationInfo - .ForEach(y => - { - y.GreaterThan1Kb = false; - actualOperationSet.Add(y); - })); + .ForEach(x => + { + if (x.CacheRefreshInfo != null && x.CacheRefreshInfo.Count > 0) + { + x.CacheRefreshInfo + .ForEach(y => + { + y.GreaterThan1Kb = false; + cacheRefreshInfoSet.Add(y); + }); + + } + + x.OperationInfo + .ForEach(y => + { + y.GreaterThan1Kb = false; + actualOperationSet.Add(y); + }); + }); if (actualOperationSet.Count == expectedOperationCount / 2) { @@ -821,6 +853,15 @@ private async Task WaitAndAssert( actualOperationList: actualOperationList, expectedSubstatuscode: expectedSubstatuscode); + if(!string.IsNullOrEmpty(expectedCacheSource)) + { + Assert.IsTrue(cacheRefreshInfoSet.Count > 0, "Cache Refresh Information is not there"); + + ClientTelemetryTests.AssertCacheRefreshInfoInformation( + cacheRefreshInfoSet: cacheRefreshInfoSet, + expectedCacheSource: expectedCacheSource); + } + ClientTelemetryTests.AssertSystemLevelInformation(actualSystemInformation, this.expectedMetricNameUnitMap); } @@ -839,7 +880,8 @@ private static void AssertSystemLevelInformation(List actualSystemIn Assert.AreEqual(systemInfo.MetricInfo.UnitName, actualMetricNameUnitMap[systemInfo.MetricInfo.MetricsName]); } - if(!systemInfo.MetricInfo.MetricsName.Equals(ClientTelemetryOptions.IsThreadStarvingName)) + if(!systemInfo.MetricInfo.MetricsName.Equals(ClientTelemetryOptions.IsThreadStarvingName) && + !systemInfo.MetricInfo.MetricsName.Equals(ClientTelemetryOptions.ThreadWaitIntervalInMsName)) { Assert.IsTrue(systemInfo.MetricInfo.Count > 0, $"MetricInfo ({systemInfo.MetricInfo.MetricsName}) Count is not greater than 0"); Assert.IsNotNull(systemInfo.MetricInfo.Percentiles, $"Percentiles is null for metrics ({systemInfo.MetricInfo.MetricsName})"); @@ -863,7 +905,7 @@ private static void AssertOperationLevelInformation( Microsoft.Azure.Cosmos.ConsistencyLevel? expectedConsistencyLevel, IDictionary expectedOperationRecordCountMap, List actualOperationList, - string expectedSubstatuscode = null) + int expectedSubstatuscode = 0) { IDictionary actualOperationRecordCountMap = new Dictionary(); @@ -872,7 +914,6 @@ private static void AssertOperationLevelInformation( { Assert.IsNotNull(operation.Operation, "Operation Type is null"); Assert.IsNotNull(operation.Resource, "Resource Type is null"); - Assert.IsNotNull(operation.StatusCode, "StatusCode is null"); Assert.AreEqual(expectedSubstatuscode, operation.SubStatusCode); Assert.AreEqual(expectedConsistencyLevel?.ToString(), operation.Consistency, $"Consistency is not {expectedConsistencyLevel}"); @@ -900,7 +941,7 @@ private static void AssertOperationLevelInformation( if (expectedOperationRecordCountMap != null) { - Assert.IsTrue(expectedOperationRecordCountMap.EqualsTo(actualOperationRecordCountMap), $"actual record i.e. ({actualOperationRecordCountMap}) for operation does not match with expected record i.e. ({expectedOperationRecordCountMap})"); + Assert.IsTrue(expectedOperationRecordCountMap.EqualsTo(actualOperationRecordCountMap), $"actual record i.e. ({actualOperationRecordCountMap}) for operation does not match with expected record i.e. ({expectedOperationRecordCountMap})"); } } @@ -960,6 +1001,32 @@ private static void AssertAccountLevelInformation( } } + + private static void AssertCacheRefreshInfoInformation( + HashSet cacheRefreshInfoSet, + string expectedCacheSource) + { + foreach(CacheRefreshInfo cacheRefreshInfo in cacheRefreshInfoSet) + { + Assert.IsNotNull(cacheRefreshInfo.CacheRefreshSource); + Assert.IsTrue(expectedCacheSource.Contains(cacheRefreshInfo.CacheRefreshSource)); + Assert.IsNotNull(cacheRefreshInfo.Operation, "Operation Type is null"); + Assert.IsNotNull(cacheRefreshInfo.Resource, "Resource Type is null"); + Assert.IsNotNull(cacheRefreshInfo.StatusCode, "StatusCode is null"); + Assert.IsNotNull(cacheRefreshInfo.SubStatusCode); + Assert.IsNull(cacheRefreshInfo.Consistency); + Assert.IsNotNull(cacheRefreshInfo.ContainerName, "ContainerName is null"); + Assert.IsNotNull(cacheRefreshInfo.MetricInfo, "MetricInfo is null"); + Assert.IsNotNull(cacheRefreshInfo.MetricInfo.MetricsName, "MetricsName is null"); + Assert.IsNotNull(cacheRefreshInfo.MetricInfo.UnitName, "UnitName is null"); + Assert.IsNotNull(cacheRefreshInfo.MetricInfo.Percentiles, "Percentiles is null"); + Assert.IsTrue(cacheRefreshInfo.MetricInfo.Count >= 0, "MetricInfo Count is not greater than 0"); + Assert.IsTrue(cacheRefreshInfo.MetricInfo.Mean >= 0, "MetricInfo Mean is not greater than or equal to 0"); + Assert.IsTrue(cacheRefreshInfo.MetricInfo.Max >= 0, "MetricInfo Max is not greater than or equal to 0"); + Assert.IsTrue(cacheRefreshInfo.MetricInfo.Min >= 0, "MetricInfo Min is not greater than or equal to 0"); + } + } + [TestMethod] public async Task CheckMisconfiguredTelemetryEndpoint_should_stop_the_job() { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/RegionContactedInDiagnosticsBenchmark.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/RegionContactedInDiagnosticsBenchmark.cs index 6d45e61a10..bcf95ba776 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/RegionContactedInDiagnosticsBenchmark.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/RegionContactedInDiagnosticsBenchmark.cs @@ -73,7 +73,7 @@ internal void CollectRegionContacted(CosmosDiagnostics cosmosDiagnostics) throw new ArgumentNullException(nameof(cosmosDiagnostics)); } - ClientTelemetryHelper.GetContactedRegions(cosmosDiagnostics); + ClientTelemetryHelper.GetContactedRegions(cosmosDiagnostics.GetContactedRegions()); } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockDocumentClient.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockDocumentClient.cs index 7c0346c866..503dd0ab88 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockDocumentClient.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockDocumentClient.cs @@ -153,7 +153,7 @@ ValueTask ICosmosAuthorizationTokenProvider.GetUserAuthorizationTokenAsy private void Init() { - this.collectionCache = new Mock(null, new ServerStoreModel(null), null, null); + this.collectionCache = new Mock(null, new ServerStoreModel(null), null, null, null); ContainerProperties containerProperties = ContainerProperties.CreateWithResourceId("test"); containerProperties.PartitionKey = partitionKeyDefinition; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ClientTelemetryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ClientTelemetryTests.cs index c2434953ed..3bbbd1d3ac 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ClientTelemetryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ClientTelemetryTests.cs @@ -5,16 +5,12 @@ namespace Microsoft.Azure.Cosmos.Tests { using System; - using System.Net; - using System.Text; - using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using HdrHistogram; - using System.Net.Http; using Newtonsoft.Json; using Microsoft.Azure.Cosmos.Telemetry; using System.Collections.Generic; - using System.Xml.Serialization; + using Microsoft.Azure.Cosmos.Telemetry.Models; /// /// Tests for . @@ -89,7 +85,12 @@ public void CheckMetricsAggregationLogicWithAdjustment() [TestMethod] public void CheckJsonSerializerContract() { - string json = JsonConvert.SerializeObject(new ClientTelemetryProperties("clientId", "", null, ConnectionMode.Direct, null, 10), ClientTelemetryOptions.JsonSerializerSettings); + string json = JsonConvert.SerializeObject(new ClientTelemetryProperties(clientId: "clientId", + processId: "", + userAgent: null, + connectionMode: ConnectionMode.Direct, + preferredRegions: null, + aggregationIntervalInSec: 10), ClientTelemetryOptions.JsonSerializerSettings); Assert.AreEqual("{\"clientId\":\"clientId\",\"processId\":\"\",\"connectionMode\":\"DIRECT\",\"aggregationIntervalInSec\":10,\"systemInfo\":[]}", json); } @@ -100,7 +101,12 @@ public void CheckJsonSerializerContractWithPreferredRegions() { "region1" }; - string json = JsonConvert.SerializeObject(new ClientTelemetryProperties("clientId", "", null, ConnectionMode.Direct, preferredRegion, 1), ClientTelemetryOptions.JsonSerializerSettings); + string json = JsonConvert.SerializeObject(new ClientTelemetryProperties(clientId: "clientId", + processId: "", + userAgent: null, + connectionMode: ConnectionMode.Direct, + preferredRegions: preferredRegion, + aggregationIntervalInSec: 1), ClientTelemetryOptions.JsonSerializerSettings); Assert.AreEqual("{\"clientId\":\"clientId\",\"processId\":\"\",\"connectionMode\":\"DIRECT\",\"preferredRegions\":[\"region1\"],\"aggregationIntervalInSec\":1,\"systemInfo\":[]}", json); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs index ef6c70561c..e39bb8d0f3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs @@ -505,7 +505,7 @@ public void WithUnrecognizedApplicationRegionThrows() authKeyOrResourceToken: MockCosmosUtil.RandomInvalidCorrectlyFormatedAuthKey) .WithApplicationRegion(notAValidAzureRegion); - ArgumentException argumentException = Assert.ThrowsException(() => cosmosClientBuilder.Build(new MockDocumentClient())); + ArgumentException argumentException = Assert.ThrowsException(() => cosmosClientBuilder.Build()); Assert.IsTrue(argumentException.Message.Contains(notAValidAzureRegion), $"Expected error message to contain {notAValidAzureRegion} but got: {argumentException.Message}"); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs index c0a7c13a3c..fe4287484c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs @@ -212,7 +212,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( ConsistencyLevel.Session, new Mock().Object, partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null).Object, + clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null, null).Object, globalEndpointManager: Mock.Of()); Assert.IsNull(dsr.Headers[HttpConstants.HttpHeaders.SessionToken]); @@ -239,7 +239,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( ConsistencyLevel.Session, new Mock().Object, partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null).Object, + clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null, null).Object, globalEndpointManager: Mock.Of()); Assert.IsNull(dsrQueryPlan.Headers[HttpConstants.HttpHeaders.SessionToken]); @@ -293,7 +293,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( ConsistencyLevel.Session, new Mock().Object, partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null).Object, + clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null, null).Object, globalEndpointManager: Mock.Of()); Assert.AreEqual(dsrSessionToken, dsr.Headers[HttpConstants.HttpHeaders.SessionToken]); @@ -323,7 +323,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( ConsistencyLevel.Session, sessionContainer, partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null).Object, + clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null, null).Object, globalEndpointManager: globalEndpointManager.Object); if (dsrNoSessionToken.IsReadOnlyRequest || dsrNoSessionToken.OperationType == OperationType.Batch || multiMaster) @@ -411,7 +411,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( ConsistencyLevel.Session, new Mock().Object, partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null).Object, + clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null, null).Object, globalEndpointManager: Mock.Of()); Assert.AreEqual(sessionToken, dsrSprocExecute.Headers[HttpConstants.HttpHeaders.SessionToken]); @@ -450,7 +450,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( ConsistencyLevel.Session, sessionContainer, partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null).Object, + clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null, null).Object, globalEndpointManager: globalEndpointManager.Object); if (isWriteRequest && multiMaster) @@ -903,7 +903,7 @@ public async Task GatewayStoreModel_AvoidGlobalSessionToken() eventSource, null, MockCosmosUtil.CreateCosmosHttpClient(() => new HttpClient())); - Mock clientCollectionCache = new Mock(new SessionContainer("testhost"), storeModel, null, null); + Mock clientCollectionCache = new Mock(new SessionContainer("testhost"), storeModel, null, null, null); Mock partitionKeyRangeCache = new Mock(null, storeModel, clientCollectionCache.Object); sessionContainer.SetSessionToken( @@ -998,7 +998,7 @@ Task sendFunc(HttpRequestMessage request) null, MockCosmosUtil.CreateCosmosHttpClient(() => new HttpClient(messageHandler))); - Mock clientCollectionCache = new Mock(new SessionContainer("testhost"), storeModel, null, null); + Mock clientCollectionCache = new Mock(new SessionContainer("testhost"), storeModel, null, null, null); Mock partitionKeyRangeCache = new Mock(null, storeModel, clientCollectionCache.Object); storeModel.SetCaches(partitionKeyRangeCache.Object, clientCollectionCache.Object); @@ -1069,7 +1069,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( ConsistencyLevel.Session, sessionContainer, partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(sessionContainer, gatewayStoreModel, null, null).Object, + clientCollectionCache: new Mock(sessionContainer, gatewayStoreModel, null, null, null).Object, globalEndpointManager: globalEndpointManager.Object); Assert.AreEqual($"{childPKRangeId}:{parentSession}", documentServiceRequestToChild.Headers[HttpConstants.HttpHeaders.SessionToken]); @@ -1135,7 +1135,7 @@ await GatewayStoreModel.ApplySessionTokenAsync( ConsistencyLevel.Session, sessionContainer, partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(sessionContainer, gatewayStoreModel, null, null).Object, + clientCollectionCache: new Mock(sessionContainer, gatewayStoreModel, null, null, null).Object, globalEndpointManager: globalEndpointManager.Object); Assert.AreEqual($"{childPKRangeId}:{tokenWithAllMax}", documentServiceRequestToChild.Headers[HttpConstants.HttpHeaders.SessionToken]); @@ -1204,7 +1204,7 @@ static async Task messageHandler(HttpRequestMessage request null, MockCosmosUtil.CreateCosmosHttpClient(() => new HttpClient(httpMessageHandler))); - ClientCollectionCache clientCollectionCache = new Mock(new SessionContainer("testhost"), storeModel, null, null).Object; + ClientCollectionCache clientCollectionCache = new Mock(new SessionContainer("testhost"), storeModel, null, null, null).Object; PartitionKeyRangeCache partitionKeyRangeCache = new Mock(null, storeModel, clientCollectionCache).Object; storeModel.SetCaches(partitionKeyRangeCache, clientCollectionCache); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs index 6eaa63c744..8884e22815 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs @@ -52,8 +52,7 @@ public void HandlerOrder() [TestMethod] public void HandlerOrderIfTelemetryIsEnabled() { - Environment.SetEnvironmentVariable(ClientTelemetryOptions.EnvPropsClientTelemetryEnabled, "true"); - using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(); + using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(enableTelemetry: true); Type[] types = new Type[] { @@ -67,12 +66,10 @@ public void HandlerOrderIfTelemetryIsEnabled() RequestHandler handler = client.RequestHandler; foreach (Type type in types) { - Assert.IsTrue(type.Equals(handler.GetType())); + Assert.IsTrue(type.Equals(handler.GetType()), $"{type} is not equal to {handler.GetType()}"); handler = handler.InnerHandler; } Assert.IsNull(handler); - - Environment.SetEnvironmentVariable(ClientTelemetryOptions.EnvPropsClientTelemetryEnabled, null); } [TestMethod] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/ContactedRegionsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/ContactedRegionsTests.cs index c3d6af063b..6f6a84cbe2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/ContactedRegionsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/ContactedRegionsTests.cs @@ -71,7 +71,7 @@ public void ContactedRegionsWithNameForClientTelemetryTest() { CosmosDiagnostics diagnostics = new CosmosTraceDiagnostics(this.CreateTestTraceTree()); - string regionsContacted = ClientTelemetryHelper.GetContactedRegions(diagnostics); + string regionsContacted = ClientTelemetryHelper.GetContactedRegions(diagnostics.GetContactedRegions()); Assert.IsNotNull(regionsContacted); Assert.AreEqual("Central US,Central India,East US 2,France Central", regionsContacted); @@ -91,7 +91,7 @@ public void ContactedRegionWithNameForClientTelemetryTest() CosmosDiagnostics diagnostics = new CosmosTraceDiagnostics(trace); - string regionsContacted = ClientTelemetryHelper.GetContactedRegions(diagnostics); + string regionsContacted = ClientTelemetryHelper.GetContactedRegions(diagnostics.GetContactedRegions()); Assert.IsNotNull(regionsContacted); Assert.AreEqual("France Central", regionsContacted); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockCosmosUtil.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockCosmosUtil.cs index 4a4447d0e3..8ea04f99a5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockCosmosUtil.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockCosmosUtil.cs @@ -16,6 +16,7 @@ namespace Microsoft.Azure.Cosmos.Tests using Microsoft.Azure.Cosmos; using Microsoft.Azure.Cosmos.Fluent; using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Cosmos.Telemetry; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Collections; @@ -38,6 +39,8 @@ public static CosmosClient CreateMockCosmosClient( customizeClientBuilder?.Invoke(cosmosClientBuilder); if(enableTelemetry) { + documentClient.clientTelemetry = new Mock().Object; + cosmosClientBuilder.WithTelemetryEnabled(); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs index e678e6d53b..12d776fb38 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs @@ -161,7 +161,7 @@ internal virtual PartitionKeyRange ResolvePartitionKeyRangeById(string collectio private void Init() { - this.collectionCache = new Mock(new SessionContainer("testhost"), new ServerStoreModel(null), null, null); + this.collectionCache = new Mock(new SessionContainer("testhost"), new ServerStoreModel(null), null, null, null); const string pkPath = "/pk"; this.collectionCache.Setup (m => diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/VmMetadataApiHandlerTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/VmMetadataApiHandlerTest.cs index ed25aa72eb..388659d140 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/VmMetadataApiHandlerTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/VmMetadataApiHandlerTest.cs @@ -14,6 +14,7 @@ namespace Microsoft.Azure.Cosmos using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Core.Trace; using Microsoft.Azure.Cosmos.Telemetry; + using Microsoft.Azure.Cosmos.Telemetry.Models; using Microsoft.Azure.Cosmos.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq;