From 3d1d6bafb6d61f711b5aec7b0795dae8d6e2ecef Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 13 Sep 2024 08:19:52 +0530 Subject: [PATCH 01/49] Added request level metrics --- .../src/Handler/TelemetryHandler.cs | 57 +++++++-------- .../src/Routing/ClientCollectionCache.cs | 4 +- .../OpenTelemetryMetricsCollector.cs | 73 +++++++++++++++++++ .../Collector/TelemetryInformation.cs | 4 + .../OpenTelemetry/OpenTelemetryMetrics.cs | 37 ++++++++++ .../src/Telemetry/TelemetryToServiceHelper.cs | 42 ++++++++--- .../ClientCreateAndInitializeTest.cs | 30 ++++++-- ...icrosoft.Azure.Cosmos.EmulatorTests.csproj | 3 +- 8 files changed, 201 insertions(+), 49 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/Collector/OpenTelemetryMetricsCollector.cs create mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetrics.cs diff --git a/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs b/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs index 039373718f..7f07ccae59 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Handlers { using System; + using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -26,38 +27,36 @@ public override async Task SendAsync( CancellationToken cancellationToken) { ResponseMessage response = await base.SendAsync(request, cancellationToken); - if (this.IsAllowed(request)) + try { - try - { - this.telemetryToServiceHelper.GetCollector().CollectOperationAndNetworkInfo( - () => new TelemetryInformation - { - RegionsContactedList = response.Diagnostics.GetContactedRegions(), - RequestLatency = response.Diagnostics.GetClientElapsedTime(), - StatusCode = response.StatusCode, - ResponseSizeInBytes = TelemetryHandler.GetPayloadSize(response), - ContainerId = request.ContainerId, - DatabaseId = request.DatabaseId, - OperationType = request.OperationType, - ResourceType = request.ResourceType, - ConsistencyLevel = request.Headers?[Documents.HttpConstants.HttpHeaders.ConsistencyLevel], - RequestCharge = response.Headers.RequestCharge, - SubStatusCode = response.Headers.SubStatusCode, - Trace = response.Trace - }); - } - catch (Exception ex) - { - DefaultTrace.TraceError("Error while collecting telemetry information : {0}", ex); - } + this.telemetryToServiceHelper + .GetCollectors(request) + .ForEach((collector) => collector.CollectOperationAndNetworkInfo( + () => new TelemetryInformation + { + RegionsContactedList = response.Diagnostics.GetContactedRegions(), + RequestLatency = response.Diagnostics.GetClientElapsedTime(), + StatusCode = response.StatusCode, + ResponseSizeInBytes = TelemetryHandler.GetPayloadSize(response), + ContainerId = request.ContainerId, + DatabaseId = request.DatabaseId, + OperationType = request.OperationType, + ResourceType = request.ResourceType, + ConsistencyLevel = request.Headers?[Documents.HttpConstants.HttpHeaders.ConsistencyLevel], + RequestCharge = response.Headers.RequestCharge, + SubStatusCode = response.Headers.SubStatusCode, + Trace = response.Trace, + MaxItemCount = request.Headers.PageSize, + ActualItemCount = response.Headers.ItemCount, + PartitionKeyRangeId = request.Headers.PartitionKeyRangeId + })); + } + catch (Exception ex) + { + DefaultTrace.TraceError("Error while collecting telemetry information : {0}", ex); } - return response; - } - private bool IsAllowed(RequestMessage request) - { - return ClientTelemetryOptions.AllowedResourceTypes.Equals(request.ResourceType); + return response; } /// diff --git a/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs b/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs index 534a0b8a22..d643eeba7e 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs @@ -216,7 +216,7 @@ await this.storeModel.ProcessMessageAsync(request)) { ContainerProperties containerProperties = CosmosResource.FromStream(response); - this.telemetryToServiceHelper.GetCollector().CollectCacheInfo( + this.telemetryToServiceHelper.GetCollectors().ForEach((collector) => collector.CollectCacheInfo( ClientCollectionCache.TelemetrySourceName, () => new TelemetryInformation { @@ -227,7 +227,7 @@ await this.storeModel.ProcessMessageAsync(request)) ResourceType = request.ResourceType, SubStatusCode = response.SubStatusCode, CollectionLink = collectionLink - }); + })); return containerProperties; } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/Collector/OpenTelemetryMetricsCollector.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Collector/OpenTelemetryMetricsCollector.cs new file mode 100644 index 0000000000..6542ae94e7 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Collector/OpenTelemetryMetricsCollector.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Telemetry +{ + using System; + using System.Collections.Generic; + using Microsoft.Azure.Cosmos.Telemetry.Collector; + + /// + /// The OpenTelemetryMetricsCollector class is responsible for collecting and recording Cosmos DB operational metrics, such as item counts, request latency, request units, and regions contacted. + /// This data is captured using the OpenTelemetry metrics API, which allows tracking and analysis of Cosmos DB operations at a granular level. + /// + internal class OpenTelemetryMetricsCollector : ITelemetryCollector + { + private readonly string clientId; + private readonly string accountName; + + /// + /// Initializes a new instance of the OpenTelemetryMetricsCollector class. + /// + /// A unique identifier for the Cosmos DB client instance + /// The Cosmos DB account name. + public OpenTelemetryMetricsCollector(string clientId, string accountName) + { + this.clientId = clientId; + this.accountName = accountName; + } + + public void CollectCacheInfo(string cacheName, Func getTelemetryInformation) + { + // No OP + } + + /// + /// Collects telemetry data related to operations and network information, including request performance, item counts, and regions contacted. + /// + /// A function that provides telemetry details such as operation type, status code, consistency level, and request charge. + /// This method gathers telemetry information, including details such as the database, container, operation type, status code, consistency level, and partition key range ID. It uses these dimensions to push metrics to OpenTelemetry, enabling tracking of performance metrics such as request latency, request charge, and item counts. + public void CollectOperationAndNetworkInfo(Func getTelemetryInformation) + { + TelemetryInformation telemetryInformation = getTelemetryInformation(); + + KeyValuePair[] dimensions = new[] + { + new KeyValuePair("Container", $"{this.accountName}/{telemetryInformation.DatabaseId}/{telemetryInformation.ContainerId}"), + new KeyValuePair("Operation", telemetryInformation.OperationType), + new KeyValuePair("OperationStatusCode", telemetryInformation.StatusCode), + new KeyValuePair("ClientCorrelationId", this.clientId), + new KeyValuePair("ConsistencyLevel", telemetryInformation.ConsistencyLevel), + new KeyValuePair("PartitionKeyRangeId", telemetryInformation.PartitionKeyRangeId), + }; + + PushOperationLevelMetrics(telemetryInformation, dimensions); + } + + /// + /// Pushes various operation-level metrics to OpenTelemetry. + /// + /// Contains telemetry data related to the operation, such as item counts, request charge, and latency. + /// Key-value pairs representing various metadata about the operation (e.g., container, operation type, consistency level). + private static void PushOperationLevelMetrics(TelemetryInformation telemetryInformation, KeyValuePair[] dimensions) + { + OpenTelemetryMetrics.MaxItemCounter.Add(Convert.ToInt32(telemetryInformation.MaxItemCount), dimensions); + OpenTelemetryMetrics.ActualItemCounter.Add(Convert.ToInt32(telemetryInformation.ActualItemCount), dimensions); + OpenTelemetryMetrics.RegionsContactedCounter.Add(telemetryInformation.RegionsContactedList.Count, dimensions); + OpenTelemetryMetrics.RequestUnitsHistogram.Record(telemetryInformation.RequestCharge, dimensions); + OpenTelemetryMetrics.RequestLatencyHistogram.Record(telemetryInformation.RequestLatency.Value.Milliseconds, dimensions); + OpenTelemetryMetrics.NumberOfOperationCallCounter.Add(1, dimensions); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/Collector/TelemetryInformation.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Collector/TelemetryInformation.cs index a8210fb0a4..580561d6b4 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/Collector/TelemetryInformation.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Collector/TelemetryInformation.cs @@ -26,5 +26,9 @@ internal class TelemetryInformation internal double RequestCharge { get; set; } // Required only for operation level telemetry internal string CollectionLink { get; set; } = null; // Required only for collection cache telemetry internal ITrace Trace { get; set; } // Required to fetch network level telemetry out of the trace object + + internal string MaxItemCount { get; set; } + internal string ActualItemCount { get; set; } + internal string PartitionKeyRangeId { get; set; } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetrics.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetrics.cs new file mode 100644 index 0000000000..ed0eebbc95 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetrics.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Metrics; + using System.Text; + + /// + /// The OpenTelemetryMetrics class contains internal static members to create and record Cosmos DB SDK metrics using OpenTelemetry. These metrics allow you to monitor the performance and resource consumption of Cosmos DB operations. + /// + internal static class OpenTelemetryMetrics + { + private static readonly Meter Meter = new Meter("Azure.Cosmos.SDK.Metrics"); + + internal static readonly Counter NumberOfOperationCallCounter = + Meter.CreateCounter("cosmos.client.op.calls", "#", "Number of operation calls"); + + internal static readonly Histogram RequestLatencyHistogram = + Meter.CreateHistogram("cosmos.client.op.latency", "#", "Total end-to-end duration of the operation"); + + internal static readonly Histogram RequestUnitsHistogram = + Meter.CreateHistogram("cosmos.client.op.RUs", "#", "Total request units per operation (sum of RUs for all requested needed when processing an operation)"); + + internal static readonly Counter MaxItemCounter = + Meter.CreateCounter("cosmos.client.op.maxItemCount", "#", "For feed operations (query, readAll, readMany, change feed) and batch operations this meter capture the requested maxItemCount per page/request"); + + internal static readonly Counter ActualItemCounter = + Meter.CreateCounter("cosmos.client.op.actualItemCount", "#", "For feed operations (query, readAll, readMany, change feed) batch operations this meter capture the actual item count in responses from the service"); + + internal static readonly Counter RegionsContactedCounter = + Meter.CreateCounter("cosmos.client.op.regionsContacted", "#", "Number of regions contacted when executing an operation"); + } +} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/TelemetryToServiceHelper.cs b/Microsoft.Azure.Cosmos/src/Telemetry/TelemetryToServiceHelper.cs index 4b94811b67..5ee15cd1f8 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/TelemetryToServiceHelper.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/TelemetryToServiceHelper.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry { using System; + using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -19,14 +20,15 @@ namespace Microsoft.Azure.Cosmos.Telemetry internal class TelemetryToServiceHelper : IDisposable { + private readonly OpenTelemetryMetricsCollector openTelemetryCollector = null; private ITelemetryCollector collector = new TelemetryCollectorNoOp(); - + internal static TimeSpan DefaultBackgroundRefreshClientConfigTimeInterval = TimeSpan.FromMinutes(10); private readonly AuthorizationTokenProvider cosmosAuthorization; private readonly CosmosHttpClient httpClient; - private readonly Uri serviceEnpoint; + private readonly Uri serviceEndpoint; private readonly ConnectionPolicy connectionPolicy; private readonly string clientId; private readonly GlobalEndpointManager globalEndpointManager; @@ -34,9 +36,9 @@ internal static TimeSpan DefaultBackgroundRefreshClientConfigTimeInterval private ClientTelemetry clientTelemetry = null; - private TelemetryToServiceHelper() + private TelemetryToServiceHelper(string clientId, string accountName) { - //NoOpConstructor + this.openTelemetryCollector = new OpenTelemetryMetricsCollector(clientId: clientId, accountName: accountName); } private TelemetryToServiceHelper( @@ -52,9 +54,11 @@ private TelemetryToServiceHelper( this.cosmosAuthorization = cosmosAuthorization; this.httpClient = httpClient; this.connectionPolicy = connectionPolicy; - this.serviceEnpoint = serviceEndpoint; + this.serviceEndpoint = serviceEndpoint; this.globalEndpointManager = globalEndpointManager; this.cancellationTokenSource = cancellationTokenSource; + + this.openTelemetryCollector = new OpenTelemetryMetricsCollector(clientId: clientId, accountName: serviceEndpoint.Host); } public static TelemetryToServiceHelper CreateAndInitializeClientConfigAndTelemetryJob(string clientId, @@ -66,11 +70,11 @@ public static TelemetryToServiceHelper CreateAndInitializeClientConfigAndTelemet CancellationTokenSource cancellationTokenSource) { #if INTERNAL - return new TelemetryToServiceHelper(); + return new TelemetryToServiceHelper(clientId: clientId, accountName: serviceEndpoint.Host); #else if (connectionPolicy.CosmosClientTelemetryOptions.DisableSendingMetricsToService) { - return new TelemetryToServiceHelper(); + return new TelemetryToServiceHelper(clientId: clientId, accountName: serviceEndpoint.Host); } TelemetryToServiceHelper helper = new TelemetryToServiceHelper( @@ -82,7 +86,7 @@ public static TelemetryToServiceHelper CreateAndInitializeClientConfigAndTelemet globalEndpointManager: globalEndpointManager, cancellationTokenSource: cancellationTokenSource); - _ = helper.RetrieveConfigAndInitiateTelemetryAsync(); // Let it run in backgroud + _ = helper.RetrieveConfigAndInitiateTelemetryAsync(); // Let it run in background return helper; #endif @@ -92,7 +96,7 @@ private async Task RetrieveConfigAndInitiateTelemetryAsync() { try { - Uri serviceEndpointWithPath = new Uri(this.serviceEnpoint + Paths.ClientConfigPathSegment); + Uri serviceEndpointWithPath = new Uri(this.serviceEndpoint + Paths.ClientConfigPathSegment); while (!this.cancellationTokenSource.IsCancellationRequested) { TryCatch databaseAccountClientConfigs = await this.GetDatabaseAccountClientConfigAsync( @@ -172,9 +176,25 @@ await cosmosAuthorization.AddAuthorizationHeaderAsync( } } - public ITelemetryCollector GetCollector() + public List GetCollectors(RequestMessage request = null) + { + List collectors = new List(2); + if (request is null || IsAllowed(request)) + { + collectors.Add(this.collector); + } + + if (this.openTelemetryCollector != null) + { + collectors.Add(this.openTelemetryCollector); + } + + return collectors; + } + + private static bool IsAllowed(RequestMessage request) { - return this.collector; + return ClientTelemetryOptions.AllowedResourceTypes.Equals(request.ResourceType); } public bool IsClientTelemetryJobRunning() diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs index 1813700e05..f3838ce796 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs @@ -13,6 +13,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; + using OpenTelemetry.Metrics; + using OpenTelemetry; + using System.Diagnostics; [TestClass] public class ClientCreateAndInitializeTest : BaseCosmosClientHelper @@ -74,15 +77,30 @@ public async Task CreateAndInitializeTest() }; CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync(endpoint, authKey, containers, cosmosClientOptions); - Assert.IsNotNull(cosmosClient); + // Assert.IsNotNull(cosmosClient); int httpCallsMadeAfterCreation = httpCallsMade; ContainerInternal container = (ContainerInternal)cosmosClient.GetContainer(this.database.Id, "ClientCreateAndInitializeContainer"); - ItemResponse readResponse = await container.ReadItemAsync("1", new Cosmos.PartitionKey("Status1")); - string diagnostics = readResponse.Diagnostics.ToString(); - Assert.IsTrue(diagnostics.Contains("\"ConnectionMode\":\"Direct\"")); - Assert.AreEqual(httpCallsMade, httpCallsMadeAfterCreation); - cosmosClient.Dispose(); + + await Task.Delay(1000); + + Stopwatch sw = Stopwatch.StartNew(); + sw.Start(); + while(true) + { + ItemResponse readResponse = await container.ReadItemAsync("1", new Cosmos.PartitionKey("Status1")); + string diagnostics = readResponse.Diagnostics.ToString(); + if(sw.ElapsedMilliseconds > 2000) + { + break; + } + } + sw.Stop(); + + await Task.Delay(1000); + + Assert.IsTrue(diagnostics.Contains("\"ConnectionMode\":\"Direct\"")); + Assert.AreEqual(httpCallsMade, httpCallsMadeAfterCreation); } [TestMethod] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 4f70def22d..998b08f577 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -52,9 +52,10 @@ - + + From 60c411baa03646a26d9915923e021e22ec126299 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 13 Sep 2024 12:27:58 +0530 Subject: [PATCH 02/49] add IsClientMetricsEnabled option --- .../src/CosmosClientTelemetryOptions.cs | 6 ++++++ .../src/Telemetry/TelemetryToServiceHelper.cs | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientTelemetryOptions.cs b/Microsoft.Azure.Cosmos/src/CosmosClientTelemetryOptions.cs index d05a840f0e..f7ef2e765e 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClientTelemetryOptions.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClientTelemetryOptions.cs @@ -53,5 +53,11 @@ public class CosmosClientTelemetryOptions /// but has to beware that customer data may be shown when the later option is chosen. It's the user's responsibility to sanitize the queries if necessary. /// public QueryTextMode QueryTextMode { get; set; } = QueryTextMode.None; + /// Indicates whether client-side metrics collection is enabled or disabled. + /// When set to true, the application will capture and report client metrics such as request counts, latencies, errors, and other key performance indicators. + /// If false, no metrics related to the client will be gathered or reported. + /// Metrics data can be published to a monitoring system like Prometheus or Azure Monitor, depending on the configured metrics provider. + /// + public bool IsClientMetricsEnabled { get; set; } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/TelemetryToServiceHelper.cs b/Microsoft.Azure.Cosmos/src/Telemetry/TelemetryToServiceHelper.cs index 5ee15cd1f8..244f7ddfb8 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/TelemetryToServiceHelper.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/TelemetryToServiceHelper.cs @@ -36,9 +36,12 @@ internal static TimeSpan DefaultBackgroundRefreshClientConfigTimeInterval private ClientTelemetry clientTelemetry = null; - private TelemetryToServiceHelper(string clientId, string accountName) + private TelemetryToServiceHelper(bool isMetricEnabled, string clientId, string accountName) { - this.openTelemetryCollector = new OpenTelemetryMetricsCollector(clientId: clientId, accountName: accountName); + if (isMetricEnabled) + { + this.openTelemetryCollector = new OpenTelemetryMetricsCollector(clientId: clientId, accountName: accountName); + } } private TelemetryToServiceHelper( @@ -49,6 +52,7 @@ private TelemetryToServiceHelper( Uri serviceEndpoint, GlobalEndpointManager globalEndpointManager, CancellationTokenSource cancellationTokenSource) + : this(connectionPolicy.CosmosClientTelemetryOptions.IsClientMetricsEnabled, clientId, serviceEndpoint.Host) { this.clientId = clientId; this.cosmosAuthorization = cosmosAuthorization; @@ -57,8 +61,6 @@ private TelemetryToServiceHelper( this.serviceEndpoint = serviceEndpoint; this.globalEndpointManager = globalEndpointManager; this.cancellationTokenSource = cancellationTokenSource; - - this.openTelemetryCollector = new OpenTelemetryMetricsCollector(clientId: clientId, accountName: serviceEndpoint.Host); } public static TelemetryToServiceHelper CreateAndInitializeClientConfigAndTelemetryJob(string clientId, @@ -70,11 +72,15 @@ public static TelemetryToServiceHelper CreateAndInitializeClientConfigAndTelemet CancellationTokenSource cancellationTokenSource) { #if INTERNAL - return new TelemetryToServiceHelper(clientId: clientId, accountName: serviceEndpoint.Host); + return new TelemetryToServiceHelper(isMetricEnabled: connectionPolicy.CosmosClientTelemetryOptions.IsClientMetricsEnabled, + clientId: clientId, + accountName: serviceEndpoint.Host); #else if (connectionPolicy.CosmosClientTelemetryOptions.DisableSendingMetricsToService) { - return new TelemetryToServiceHelper(clientId: clientId, accountName: serviceEndpoint.Host); + return new TelemetryToServiceHelper(isMetricEnabled: connectionPolicy.CosmosClientTelemetryOptions.IsClientMetricsEnabled, + clientId: clientId, + accountName: serviceEndpoint.Host); } TelemetryToServiceHelper helper = new TelemetryToServiceHelper( From 55b6505e57a7874d3cfd719d2a63e56d40de2bc5 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 13 Sep 2024 13:10:03 +0530 Subject: [PATCH 03/49] added contract file --- .../Contracts/DotNetSDKAPI.json | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 463f7bbe16..70c64f4c71 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -3225,6 +3225,18 @@ ], "MethodInfo": "Boolean get_DisableSendingMetricsToService();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Boolean get_IsClientMetricsEnabled()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Boolean get_IsClientMetricsEnabled();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Boolean IsClientMetricsEnabled": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "Boolean IsClientMetricsEnabled;CanRead:True;CanWrite:True;Boolean get_IsClientMetricsEnabled();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_IsClientMetricsEnabled(Boolean);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Microsoft.Azure.Cosmos.CosmosThresholdOptions CosmosThresholdOptions": { "Type": "Property", "Attributes": [], @@ -3276,10 +3288,7 @@ "MethodInfo": "Void set_DisableSendingMetricsToService(Boolean);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "Void set_QueryTextMode(Microsoft.Azure.Cosmos.QueryTextMode)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], + "MethodInfo": "Void set_QueryTextMode(Microsoft.Azure.Cosmos.QueryTextMode);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" } }, From 6fb216c455bd11cdd6cfe09a39032c03a9732bfa Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Mon, 16 Sep 2024 21:46:57 +0530 Subject: [PATCH 04/49] wip --- .../src/Handler/TelemetryHandler.cs | 2 +- .../OpenTelemetryMetricsCollector.cs | 18 +- .../OpenTelemetry/OpenTelemetryMetrics.cs | 39 +- .../ClientCreateAndInitializeTest.cs | 386 ----------------- .../Metrics/OpenTelemetryMetricsTest.cs | 125 ++++++ ...- Backup.Azure.Cosmos.EmulatorTests.csproj | 388 ++++++++++++++++++ 6 files changed, 556 insertions(+), 402 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft - Backup.Azure.Cosmos.EmulatorTests.csproj diff --git a/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs b/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs index 7f07ccae59..04e9e92740 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs @@ -46,7 +46,7 @@ public override async Task SendAsync( RequestCharge = response.Headers.RequestCharge, SubStatusCode = response.Headers.SubStatusCode, Trace = response.Trace, - MaxItemCount = request.Headers.PageSize, + MaxItemCount = Convert.ToString(new Random().Next(100)), ActualItemCount = response.Headers.ItemCount, PartitionKeyRangeId = request.Headers.PartitionKeyRangeId })); diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/Collector/OpenTelemetryMetricsCollector.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Collector/OpenTelemetryMetricsCollector.cs index 6542ae94e7..da8809e5dd 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/Collector/OpenTelemetryMetricsCollector.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Collector/OpenTelemetryMetricsCollector.cs @@ -5,7 +5,9 @@ namespace Microsoft.Azure.Cosmos.Telemetry { using System; + using System.Collections.Concurrent; using System.Collections.Generic; + using System.Diagnostics.Metrics; using Microsoft.Azure.Cosmos.Telemetry.Collector; /// @@ -17,6 +19,8 @@ internal class OpenTelemetryMetricsCollector : ITelemetryCollector private readonly string clientId; private readonly string accountName; + private static readonly ConcurrentBag[]>> maxItemCounts = new (); + /// /// Initializes a new instance of the OpenTelemetryMetricsCollector class. /// @@ -62,12 +66,20 @@ public void CollectOperationAndNetworkInfo(Func getTelemet /// Key-value pairs representing various metadata about the operation (e.g., container, operation type, consistency level). private static void PushOperationLevelMetrics(TelemetryInformation telemetryInformation, KeyValuePair[] dimensions) { - OpenTelemetryMetrics.MaxItemCounter.Add(Convert.ToInt32(telemetryInformation.MaxItemCount), dimensions); - OpenTelemetryMetrics.ActualItemCounter.Add(Convert.ToInt32(telemetryInformation.ActualItemCount), dimensions); - OpenTelemetryMetrics.RegionsContactedCounter.Add(telemetryInformation.RegionsContactedList.Count, dimensions); + maxItemCounts.Add(new Tuple[]>(Convert.ToInt32(telemetryInformation.MaxItemCount), dimensions)); + OpenTelemetryMetrics.RequestUnitsHistogram.Record(telemetryInformation.RequestCharge, dimensions); OpenTelemetryMetrics.RequestLatencyHistogram.Record(telemetryInformation.RequestLatency.Value.Milliseconds, dimensions); OpenTelemetryMetrics.NumberOfOperationCallCounter.Add(1, dimensions); } + + public static IEnumerable> GetMaxItemCount() + { + foreach (Tuple[]> maxItemCount in maxItemCounts) + { + yield return new Measurement(maxItemCount.Item1, maxItemCount.Item2); + } + } + } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetrics.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetrics.cs index ed0eebbc95..b9e5fe9a79 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetrics.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetrics.cs @@ -8,30 +8,45 @@ namespace Microsoft.Azure.Cosmos using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Text; + using Microsoft.Azure.Cosmos.Telemetry; /// /// The OpenTelemetryMetrics class contains internal static members to create and record Cosmos DB SDK metrics using OpenTelemetry. These metrics allow you to monitor the performance and resource consumption of Cosmos DB operations. /// internal static class OpenTelemetryMetrics { - private static readonly Meter Meter = new Meter("Azure.Cosmos.SDK.Metrics"); + internal static readonly Meter CosmosMeter = new Meter("Azure.Cosmos.Client"); internal static readonly Counter NumberOfOperationCallCounter = - Meter.CreateCounter("cosmos.client.op.calls", "#", "Number of operation calls"); + CosmosMeter.CreateCounter(name: "cosmos.client.op.calls", + unit: "#", + description: "Number of operation calls"); internal static readonly Histogram RequestLatencyHistogram = - Meter.CreateHistogram("cosmos.client.op.latency", "#", "Total end-to-end duration of the operation"); + CosmosMeter.CreateHistogram(name: "cosmos.client.op.latency", + unit: "#", + description: "Total end-to-end duration of the operation"); internal static readonly Histogram RequestUnitsHistogram = - Meter.CreateHistogram("cosmos.client.op.RUs", "#", "Total request units per operation (sum of RUs for all requested needed when processing an operation)"); + CosmosMeter.CreateHistogram(name: "cosmos.client.op.RUs", + unit: "#", + description: "Total request units per operation (sum of RUs for all requested needed when processing an operation)"); + + internal static readonly ObservableGauge maxItemGauge = + CosmosMeter.CreateObservableGauge(name: "cosmos.client.op.maxItemCount", + observeValue: () => OpenTelemetryMetricsCollector.GetMaxItemCount(), + unit: "#", + description: "For feed operations (query, readAll, readMany, change feed) and batch operations this meter capture the requested maxItemCount per page/request"); + + /*internal static readonly ObservableGauge ActualItemCounter = + CosmosMeter.CreateObservableGauge(name: "cosmos.client.op.actualItemCount", + unit: "#", + description: "For feed operations (query, readAll, readMany, change feed) batch operations this meter capture the actual item count in responses from the service"); + + internal static readonly ObservableGauge RegionsContactedCounter = + CosmosMeter.CreateObservableGauge(name: "cosmos.client.op.regionsContacted", + unit: "#", + description: "Number of regions contacted when executing an operation");*/ - internal static readonly Counter MaxItemCounter = - Meter.CreateCounter("cosmos.client.op.maxItemCount", "#", "For feed operations (query, readAll, readMany, change feed) and batch operations this meter capture the requested maxItemCount per page/request"); - - internal static readonly Counter ActualItemCounter = - Meter.CreateCounter("cosmos.client.op.actualItemCount", "#", "For feed operations (query, readAll, readMany, change feed) batch operations this meter capture the actual item count in responses from the service"); - - internal static readonly Counter RegionsContactedCounter = - Meter.CreateCounter("cosmos.client.op.regionsContacted", "#", "Number of regions contacted when executing an operation"); } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs index f3838ce796..e69de29bb2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs @@ -1,386 +0,0 @@ -namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests -{ - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Net.Http; - using System.Reflection; - using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.Fluent; - using Microsoft.Azure.Documents; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using Moq; - using Moq.Protected; - using OpenTelemetry.Metrics; - using OpenTelemetry; - using System.Diagnostics; - - [TestClass] - public class ClientCreateAndInitializeTest : BaseCosmosClientHelper - { - private ContainerInternal Container = null; - private const string PartitionKey = "/pk"; - - [TestInitialize] - public async Task TestInitialize() - { - await this.TestInit(); - - ContainerResponse response = await this.database.CreateContainerAsync( - new ContainerProperties(id: "ClientCreateAndInitializeContainer", partitionKeyPath: PartitionKey), - throughput: 20000, - cancellationToken: this.cancellationToken); - Assert.IsNotNull(response); - Assert.IsNotNull(response.Container); - Assert.IsNotNull(response.Resource); - this.Container = (ContainerInlineCore)response; - - // Create items with different - for (int i = 0; i < 500; i++) - { - ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(); - item.pk = "Status" + i.ToString(); - item.id = i.ToString(); - ItemResponse itemResponse = await this.Container.CreateItemAsync(item); - Assert.AreEqual(HttpStatusCode.Created, itemResponse.StatusCode); - } - } - - [TestCleanup] - public async Task Cleanup() - { - await base.TestCleanup(); - } - - [TestMethod] - public async Task CreateAndInitializeTest() - { - int httpCallsMade = 0; - HttpClientHandlerHelper httpClientHandlerHelper = new HttpClientHandlerHelper - { - RequestCallBack = (request, cancellationToken) => - { - httpCallsMade++; - return null; - } - }; - - (string endpoint, string authKey) = TestCommon.GetAccountInfo(); - List<(string, string)> containers = new List<(string, string)> - { (this.database.Id, "ClientCreateAndInitializeContainer")}; - - CosmosClientOptions cosmosClientOptions = new CosmosClientOptions - { - HttpClientFactory = () => new HttpClient(httpClientHandlerHelper), - }; - - CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync(endpoint, authKey, containers, cosmosClientOptions); - // Assert.IsNotNull(cosmosClient); - int httpCallsMadeAfterCreation = httpCallsMade; - - ContainerInternal container = (ContainerInternal)cosmosClient.GetContainer(this.database.Id, "ClientCreateAndInitializeContainer"); - - await Task.Delay(1000); - - Stopwatch sw = Stopwatch.StartNew(); - sw.Start(); - while(true) - { - ItemResponse readResponse = await container.ReadItemAsync("1", new Cosmos.PartitionKey("Status1")); - string diagnostics = readResponse.Diagnostics.ToString(); - if(sw.ElapsedMilliseconds > 2000) - { - break; - } - } - sw.Stop(); - - await Task.Delay(1000); - - Assert.IsTrue(diagnostics.Contains("\"ConnectionMode\":\"Direct\"")); - Assert.AreEqual(httpCallsMade, httpCallsMadeAfterCreation); - } - - [TestMethod] - public async Task CreateAndInitializeWithCosmosClientBuilderTest() - { - int httpCallsMade = 0; - HttpClientHandlerHelper httpClientHandlerHelper = new HttpClientHandlerHelper - { - RequestCallBack = (request, cancellationToken) => - { - httpCallsMade++; - return null; - } - }; - - (string endpoint, string authKey) = TestCommon.GetAccountInfo(); - List<(string, string)> containers = new List<(string, string)> - { (this.database.Id, "ClientCreateAndInitializeContainer")}; - - CosmosClientBuilder builder = new CosmosClientBuilder(endpoint, authKey).WithHttpClientFactory(() => new HttpClient(httpClientHandlerHelper)); - CosmosClient cosmosClient = await builder.BuildAndInitializeAsync(containers); - Assert.IsNotNull(cosmosClient); - int httpCallsMadeAfterCreation = httpCallsMade; - - ContainerInternal container = (ContainerInternal)cosmosClient.GetContainer(this.database.Id, "ClientCreateAndInitializeContainer"); - ItemResponse readResponse = await container.ReadItemAsync("1", new Cosmos.PartitionKey("Status1")); - Assert.AreEqual(httpCallsMade, httpCallsMadeAfterCreation); - cosmosClient.Dispose(); - } - - [TestMethod] - [ExpectedException(typeof(HttpRequestException))] - public async Task AuthIncorrectTest() - { - List<(string databaseId, string containerId)> containers = new List<(string databaseId, string containerId)> - { (this.database.Id, "ClientCreateAndInitializeContainer")}; - string authKey = TestCommon.GetAccountInfo().authKey; - CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync("https://127.0.0.1:0000/", authKey, containers); - cosmosClient.Dispose(); - } - - [TestMethod] - [ExpectedException(typeof(CosmosException))] - public async Task DatabaseIncorrectTest() - { - List<(string databaseId, string containerId)> containers = new List<(string databaseId, string containerId)> - { ("IncorrectDatabase", "ClientCreateAndInitializeContainer")}; - (string endpoint, string authKey) = TestCommon.GetAccountInfo(); - try - { - CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync(endpoint, authKey, containers); - } - catch (CosmosException ex) - { - Assert.IsTrue(ex.StatusCode == HttpStatusCode.NotFound); - throw; - } - } - - [TestMethod] - [ExpectedException(typeof(CosmosException))] - public async Task ContainerIncorrectTest() - { - List<(string databaseId, string containerId)> containers = new List<(string databaseId, string containerId)> - { (this.database.Id, "IncorrectContainer")}; - (string endpoint, string authKey) = TestCommon.GetAccountInfo(); - try - { - CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync(endpoint, authKey, containers); - } - catch (CosmosException ex) - { - Assert.IsTrue(ex.StatusCode == HttpStatusCode.NotFound); - throw; - } - } - - [TestMethod] - public async Task InitializeContainersAsync_WhenThrowsException_ShouldDisposeCosmosClient() - { - // Arrange. - List<(string databaseId, string containerId)> containers = new() - { ("IncorrectDatabase", "ClientCreateAndInitializeContainer")}; - - CosmosException cosmosException = new ( - statusCode: HttpStatusCode.NotFound, - message: "Test", - stackTrace: null, - headers: null, - trace: default, - error: null, - innerException: null); - - Mock cosmosClient = new (); - cosmosClient - .Setup(x => x.GetContainer(It.IsAny(), It.IsAny())) - .Throws(cosmosException); - - cosmosClient - .Protected() - .Setup("Dispose", ItExpr.Is(x => x)) - .Verifiable(); - - // Act. - CosmosException ex = await Assert.ThrowsExceptionAsync(() => cosmosClient.Object.InitializeContainersAsync(containers, this.cancellationToken)); - - // Assert. - Assert.IsNotNull(ex); - Assert.IsTrue(ex.StatusCode == HttpStatusCode.NotFound); - cosmosClient.Verify(); - } - - /// - /// Test to validate that when is called with a - /// valid database id and a container id that exists in the database, an attempt is made to open - /// the rntbd connections to the backend replicas and the connections are opened successfully. - /// - [TestMethod] - [Owner("dkunda")] - public async Task CreateAndInitializeAsync_WithValidDatabaseAndContainer_ShouldOpenRntbdConnectionsToBackendReplicas() - { - // Arrange. - int httpCallsMade = 0, maxRequestsPerConnection = 6; - HttpClientHandlerHelper httpClientHandlerHelper = new () - { - RequestCallBack = (request, cancellationToken) => - { - httpCallsMade++; - return null; - } - }; - List<(string, string)> containers = new () - { - ( - this.database.Id, - "ClientCreateAndInitializeContainer" - ) - }; - - (string endpoint, string authKey) = TestCommon.GetAccountInfo(); - CosmosClientOptions cosmosClientOptions = new () - { - HttpClientFactory = () => new HttpClient(httpClientHandlerHelper), - ConnectionMode = ConnectionMode.Direct, - MaxRequestsPerTcpConnection = maxRequestsPerConnection, - }; - - // Act. - CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync( - accountEndpoint: endpoint, - authKeyOrResourceToken: authKey, - containers: containers, - cosmosClientOptions: cosmosClientOptions); - - // Assert. - Assert.AreEqual(5, httpCallsMade); - - IStoreClientFactory factory = (IStoreClientFactory)cosmosClient.DocumentClient.GetType() - .GetField("storeClientFactory", BindingFlags.NonPublic | BindingFlags.Instance) - .GetValue(cosmosClient.DocumentClient); - StoreClientFactory storeClientFactory = (StoreClientFactory) factory; - - TransportClient client = (TransportClient)storeClientFactory.GetType() - .GetField("transportClient", BindingFlags.NonPublic | BindingFlags.Instance) - .GetValue(storeClientFactory); - Documents.Rntbd.TransportClient transportClient = (Documents.Rntbd.TransportClient) client; - - Documents.Rntbd.ChannelDictionary channelDict = (Documents.Rntbd.ChannelDictionary)transportClient.GetType() - .GetField("channelDictionary", BindingFlags.NonPublic | BindingFlags.Instance) - .GetValue(transportClient); - - ConcurrentDictionary allChannels = (ConcurrentDictionary)channelDict.GetType() - .GetField("channels", BindingFlags.NonPublic | BindingFlags.Instance) - .GetValue(channelDict); - - Assert.AreEqual(1, allChannels.Count); - - Documents.Rntbd.LoadBalancingChannel loadBalancingChannel = (Documents.Rntbd.LoadBalancingChannel)allChannels[allChannels.Keys.First()]; - Documents.Rntbd.LoadBalancingPartition loadBalancingPartition = (Documents.Rntbd.LoadBalancingPartition)loadBalancingChannel.GetType() - .GetField("singlePartition", BindingFlags.NonPublic | BindingFlags.Instance) - .GetValue(loadBalancingChannel); - - Assert.IsNotNull(loadBalancingPartition); - - int channelCapacity = (int)loadBalancingPartition.GetType() - .GetField("capacity", BindingFlags.NonPublic | BindingFlags.Instance) - .GetValue(loadBalancingPartition); - - List openChannels = (List)loadBalancingPartition.GetType() - .GetField("openChannels", BindingFlags.NonPublic | BindingFlags.Instance) - .GetValue(loadBalancingPartition); - - Assert.IsNotNull(openChannels); - Assert.AreEqual(1, openChannels.Count, "Here the expected value 1 explains how many TCP connections were opened by the LoadBalancingPartition.OpenChannelAsync()." + - "The emulator by default returns 12 partitions, and each partition has 4 replicas, and by behavior the emulator uses the same URI for each of these replica," + - "hence 12 * 4 = 48 times we call the OpenChannelAsync(). However, the number of TCP connections established would be just one per each unique endpoint."); - Assert.AreEqual(openChannels.Count * maxRequestsPerConnection, channelCapacity); - - Documents.Rntbd.LbChannelState channelState = openChannels.First(); - - Assert.IsNotNull(channelState); - Assert.IsTrue(channelState.DeepHealthy); - } - - /// - /// Test to validate that when is called with - /// the Gateway Mode enabled, the operation should not open any Rntbd connections to the backend replicas. - /// - [TestMethod] - [Owner("dkunda")] - public async Task CreateAndInitializeAsync_WithGatewayModeEnabled_ShouldNotOpenConnectionToBackendReplicas() - { - // Arrange. - int httpCallsMade = 0; - HttpClientHandlerHelper httpClientHandlerHelper = new() - { - RequestCallBack = (request, cancellationToken) => - { - httpCallsMade++; - return null; - } - }; - List<(string, string)> containers = new() - { - ( - this.database.Id, - "ClientCreateAndInitializeContainer" - ) - }; - - (string endpoint, string authKey) = TestCommon.GetAccountInfo(); - CosmosClientOptions cosmosClientOptions = new() - { - HttpClientFactory = () => new HttpClient(httpClientHandlerHelper), - ConnectionMode = ConnectionMode.Gateway, - }; - - // Act. - CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync( - accountEndpoint: endpoint, - authKeyOrResourceToken: authKey, - containers: containers, - cosmosClientOptions: cosmosClientOptions); - - // Assert. - Assert.IsNotNull(cosmosClient); - Assert.AreEqual(1, httpCallsMade); - } - - /// - /// Test to validate that when is called with a - /// valid database id and an invalid container that doesn't exists in the database, the cosmos - /// client initialization fails and a is thrown with a 404 status code. - /// - [TestMethod] - [Owner("dkunda")] - public async Task CreateAndInitializeAsync_WithValidDatabaseAndInvalidContainer_ShouldThrowException() - { - // Arrange. - List<(string, string)> containers = new() - { - ( - this.database.Id, - "ClientCreateAndInitializeInvalidContainer" - ) - }; - - (string endpoint, string authKey) = TestCommon.GetAccountInfo(); - CosmosClientOptions cosmosClientOptions = new(); - - // Act. - CosmosException ce = await Assert.ThrowsExceptionAsync(() => CosmosClient.CreateAndInitializeAsync( - accountEndpoint: endpoint, - authKeyOrResourceToken: authKey, - containers: containers, - cosmosClientOptions: cosmosClientOptions)); - - // Assert. - Assert.IsNotNull(ce); - Assert.AreEqual(HttpStatusCode.NotFound, ce.StatusCode); - } - } -} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs new file mode 100644 index 0000000000..06086b5a2d --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -0,0 +1,125 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Reflection; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Fluent; + using Microsoft.Azure.Documents; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using Moq.Protected; + using OpenTelemetry.Metrics; + using OpenTelemetry; + using System.Diagnostics; + + [TestClass] + public class OpenTelemetryMetricsTest : BaseCosmosClientHelper + { + private ContainerInternal Container = null; + private const string PartitionKey = "/pk"; + + [TestInitialize] + public async Task TestInitialize() + { + await this.TestInit(); + + ContainerResponse response = await this.database.CreateContainerAsync( + new ContainerProperties(id: "ClientCreateAndInitializeContainer", partitionKeyPath: PartitionKey), + throughput: 20000, + cancellationToken: this.cancellationToken); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Container); + Assert.IsNotNull(response.Resource); + this.Container = (ContainerInlineCore)response; + + // Create items with different + for (int i = 0; i < 500; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(); + item.pk = "Status" + i.ToString(); + item.id = i.ToString(); + ItemResponse itemResponse = await this.Container.CreateItemAsync(item); + Assert.AreEqual(HttpStatusCode.Created, itemResponse.StatusCode); + } + } + + [TestCleanup] + public async Task Cleanup() + { + await base.TestCleanup(); + } + + [TestMethod] + public async Task OperationLevelMetrics() + { + //var histogramBuckets = new double[] { 0, 5, 10, 25, 50, 75, 100, 250, 500 }; + MeterProvider meterProvider = Sdk + .CreateMeterProviderBuilder() + .AddMeter("*")/* + .AddView("cosmos.client.op.RUs", new ExplicitBucketHistogramConfiguration { Boundaries = histogramBuckets })*/ + .AddConsoleExporter() + .Build(); + + int httpCallsMade = 0; + HttpClientHandlerHelper httpClientHandlerHelper = new HttpClientHandlerHelper + { + RequestCallBack = (request, cancellationToken) => + { + httpCallsMade++; + return null; + } + }; + + (string endpoint, string authKey) = TestCommon.GetAccountInfo(); + List<(string, string)> containers = new List<(string, string)> + { (this.database.Id, "ClientCreateAndInitializeContainer")}; + + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions + { + HttpClientFactory = () => new HttpClient(httpClientHandlerHelper), + CosmosClientTelemetryOptions = new CosmosClientTelemetryOptions() + { + IsClientMetricsEnabled = true + } + }; + + CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync(endpoint, authKey, containers, cosmosClientOptions); + // Assert.IsNotNull(cosmosClient); + int httpCallsMadeAfterCreation = httpCallsMade; + + ContainerInternal container = (ContainerInternal)cosmosClient.GetContainer(this.database.Id, "ClientCreateAndInitializeContainer"); + + await Task.Delay(1000); + + Stopwatch sw = Stopwatch.StartNew(); + sw.Start(); + while(true) + { + ItemResponse readResponse = await container.ReadItemAsync("1", new Cosmos.PartitionKey("Status1")); + string diagnostics = readResponse.Diagnostics.ToString(); + if(sw.ElapsedMilliseconds > 2000) + { + break; + } + } + sw.Stop(); + + await Task.Delay(1000); + + cosmosClient.Dispose(); + + meterProvider.Dispose(); + + await Task.Delay(1000); + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft - Backup.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft - Backup.Azure.Cosmos.EmulatorTests.csproj new file mode 100644 index 0000000000..9978bcce5c --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft - Backup.Azure.Cosmos.EmulatorTests.csproj @@ -0,0 +1,388 @@ + + + true + true + AnyCPU + net6.0 + false + false + Microsoft.Azure.Cosmos + Microsoft.Azure.Cosmos.EmulatorTests + true + master + True + $(LangVersion) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + PreserveNewest + + + Documents\15KB.json + PreserveNewest + + + Documents\100KBDocument.json + PreserveNewest + + + Documents\1MBDocument.json + PreserveNewest + + + Documents\2.5MBDocument.json + PreserveNewest + + + Documents\2MBDocument.json + PreserveNewest + + + Documents\MillionSong1KDocuments.json + PreserveNewest + + + + + + + + PreserveNewest + + + + + PreserveNewest + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + true + true + ..\..\..\testkey.snk + + + + x64 + true + + From 841c7bcc4ca936778eb04006e391764b598995c9 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 18 Sep 2024 22:13:04 +0530 Subject: [PATCH 05/49] adding test more changes wip rewrite meters refactor code dimension names test fix contract update import fixes wip added other metrics code refactor add docuemnattion Delete Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft - Backup.Azure.Cosmos.EmulatorTests.csproj upodated contract --- .../src/CosmosClientTelemetryOptions.cs | 2 + Microsoft.Azure.Cosmos/src/DocumentClient.cs | 5 + .../src/Handler/TelemetryHandler.cs | 57 +-- .../src/Microsoft.Azure.Cosmos.csproj | 4 +- .../src/Resource/ClientContextCore.cs | 6 + .../src/Routing/ClientCollectionCache.cs | 4 +- .../OpenTelemetryMetricsCollector.cs | 85 ---- .../Collector/TelemetryInformation.cs | 4 - .../OpenTelemetry/CosmosOperationMeter.cs | 103 +++++ .../OpenTelemetry/OpenTelemetryMetrics.cs | 52 --- .../OpenTelemetryMetricsConstant.cs | 153 +++++++ .../src/Telemetry/TelemetryToServiceHelper.cs | 48 +-- .../ClientCreateAndInitializeTest.cs | 368 +++++++++++++++++ .../Metrics/CustomMetricExporter.cs | 62 +++ .../Metrics/OpenTelemetryMetricsTest.cs | 127 +++--- ...- Backup.Azure.Cosmos.EmulatorTests.csproj | 388 ------------------ ...icrosoft.Azure.Cosmos.EmulatorTests.csproj | 10 +- .../Tracing/CustomOtelExporter.cs | 1 - .../Contracts/DotNetSDKAPI.json | 369 ++++++++++++++++- 19 files changed, 1176 insertions(+), 672 deletions(-) delete mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/Collector/OpenTelemetryMetricsCollector.cs create mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs delete mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetrics.cs create mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs delete mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft - Backup.Azure.Cosmos.EmulatorTests.csproj diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientTelemetryOptions.cs b/Microsoft.Azure.Cosmos/src/CosmosClientTelemetryOptions.cs index f7ef2e765e..5fd942dff8 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClientTelemetryOptions.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClientTelemetryOptions.cs @@ -53,6 +53,8 @@ public class CosmosClientTelemetryOptions /// but has to beware that customer data may be shown when the later option is chosen. It's the user's responsibility to sanitize the queries if necessary. /// public QueryTextMode QueryTextMode { get; set; } = QueryTextMode.None; + + /// /// Indicates whether client-side metrics collection is enabled or disabled. /// When set to true, the application will capture and report client metrics such as request counts, latencies, errors, and other key performance indicators. /// If false, no metrics related to the client will be gathered or reported. diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index 946fa4cb08..f24ed27dd5 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -954,6 +954,11 @@ internal virtual void Initialize(Uri serviceEndpoint, // Loading VM Information (non blocking call and initialization won't fail if this call fails) VmMetadataApiHandler.TryInitialize(this.httpClient); + if (this.cosmosClientTelemetryOptions.IsClientMetricsEnabled) + { + CosmosOperationMeter.Initialize(); + } + // Starting ClientTelemetry Job this.telemetryToServiceHelper = TelemetryToServiceHelper.CreateAndInitializeClientConfigAndTelemetryJob(this.clientId, this.ConnectionPolicy, diff --git a/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs b/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs index 04e9e92740..039373718f 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/TelemetryHandler.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Cosmos.Handlers { using System; - using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -27,38 +26,40 @@ public override async Task SendAsync( CancellationToken cancellationToken) { ResponseMessage response = await base.SendAsync(request, cancellationToken); - try + if (this.IsAllowed(request)) { - this.telemetryToServiceHelper - .GetCollectors(request) - .ForEach((collector) => collector.CollectOperationAndNetworkInfo( - () => new TelemetryInformation - { - RegionsContactedList = response.Diagnostics.GetContactedRegions(), - RequestLatency = response.Diagnostics.GetClientElapsedTime(), - StatusCode = response.StatusCode, - ResponseSizeInBytes = TelemetryHandler.GetPayloadSize(response), - ContainerId = request.ContainerId, - DatabaseId = request.DatabaseId, - OperationType = request.OperationType, - ResourceType = request.ResourceType, - ConsistencyLevel = request.Headers?[Documents.HttpConstants.HttpHeaders.ConsistencyLevel], - RequestCharge = response.Headers.RequestCharge, - SubStatusCode = response.Headers.SubStatusCode, - Trace = response.Trace, - MaxItemCount = Convert.ToString(new Random().Next(100)), - ActualItemCount = response.Headers.ItemCount, - PartitionKeyRangeId = request.Headers.PartitionKeyRangeId - })); - } - catch (Exception ex) - { - DefaultTrace.TraceError("Error while collecting telemetry information : {0}", ex); + try + { + this.telemetryToServiceHelper.GetCollector().CollectOperationAndNetworkInfo( + () => new TelemetryInformation + { + RegionsContactedList = response.Diagnostics.GetContactedRegions(), + RequestLatency = response.Diagnostics.GetClientElapsedTime(), + StatusCode = response.StatusCode, + ResponseSizeInBytes = TelemetryHandler.GetPayloadSize(response), + ContainerId = request.ContainerId, + DatabaseId = request.DatabaseId, + OperationType = request.OperationType, + ResourceType = request.ResourceType, + ConsistencyLevel = request.Headers?[Documents.HttpConstants.HttpHeaders.ConsistencyLevel], + RequestCharge = response.Headers.RequestCharge, + SubStatusCode = response.Headers.SubStatusCode, + Trace = response.Trace + }); + } + catch (Exception ex) + { + DefaultTrace.TraceError("Error while collecting telemetry information : {0}", ex); + } } - return response; } + private bool IsAllowed(RequestMessage request) + { + return ClientTelemetryOptions.AllowedResourceTypes.Equals(request.ResourceType); + } + /// /// It returns the payload size after reading it from the Response content stream. /// To avoid blocking IO calls to get the stream length, it will return response content length if stream is of Memory Type diff --git a/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj b/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj index 55b2fba26a..bfe90ace04 100644 --- a/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj +++ b/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj @@ -114,7 +114,7 @@ - + @@ -127,7 +127,7 @@ - + diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index 37fe60f89e..dbc4947c4b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -530,6 +530,12 @@ private async Task RunWithDiagnosticsHelperAsync( // Record request response information OpenTelemetryAttributes response = openTelemetry?.Item2(result); recorder.Record(response); + + CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, + accountName: this.client.Endpoint.Host, + containerName: containerName, + databaseName: databaseName, + attributes: response); } return result; diff --git a/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs b/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs index d643eeba7e..534a0b8a22 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs @@ -216,7 +216,7 @@ await this.storeModel.ProcessMessageAsync(request)) { ContainerProperties containerProperties = CosmosResource.FromStream(response); - this.telemetryToServiceHelper.GetCollectors().ForEach((collector) => collector.CollectCacheInfo( + this.telemetryToServiceHelper.GetCollector().CollectCacheInfo( ClientCollectionCache.TelemetrySourceName, () => new TelemetryInformation { @@ -227,7 +227,7 @@ await this.storeModel.ProcessMessageAsync(request)) ResourceType = request.ResourceType, SubStatusCode = response.SubStatusCode, CollectionLink = collectionLink - })); + }); return containerProperties; } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/Collector/OpenTelemetryMetricsCollector.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Collector/OpenTelemetryMetricsCollector.cs deleted file mode 100644 index da8809e5dd..0000000000 --- a/Microsoft.Azure.Cosmos/src/Telemetry/Collector/OpenTelemetryMetricsCollector.cs +++ /dev/null @@ -1,85 +0,0 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Telemetry -{ - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Diagnostics.Metrics; - using Microsoft.Azure.Cosmos.Telemetry.Collector; - - /// - /// The OpenTelemetryMetricsCollector class is responsible for collecting and recording Cosmos DB operational metrics, such as item counts, request latency, request units, and regions contacted. - /// This data is captured using the OpenTelemetry metrics API, which allows tracking and analysis of Cosmos DB operations at a granular level. - /// - internal class OpenTelemetryMetricsCollector : ITelemetryCollector - { - private readonly string clientId; - private readonly string accountName; - - private static readonly ConcurrentBag[]>> maxItemCounts = new (); - - /// - /// Initializes a new instance of the OpenTelemetryMetricsCollector class. - /// - /// A unique identifier for the Cosmos DB client instance - /// The Cosmos DB account name. - public OpenTelemetryMetricsCollector(string clientId, string accountName) - { - this.clientId = clientId; - this.accountName = accountName; - } - - public void CollectCacheInfo(string cacheName, Func getTelemetryInformation) - { - // No OP - } - - /// - /// Collects telemetry data related to operations and network information, including request performance, item counts, and regions contacted. - /// - /// A function that provides telemetry details such as operation type, status code, consistency level, and request charge. - /// This method gathers telemetry information, including details such as the database, container, operation type, status code, consistency level, and partition key range ID. It uses these dimensions to push metrics to OpenTelemetry, enabling tracking of performance metrics such as request latency, request charge, and item counts. - public void CollectOperationAndNetworkInfo(Func getTelemetryInformation) - { - TelemetryInformation telemetryInformation = getTelemetryInformation(); - - KeyValuePair[] dimensions = new[] - { - new KeyValuePair("Container", $"{this.accountName}/{telemetryInformation.DatabaseId}/{telemetryInformation.ContainerId}"), - new KeyValuePair("Operation", telemetryInformation.OperationType), - new KeyValuePair("OperationStatusCode", telemetryInformation.StatusCode), - new KeyValuePair("ClientCorrelationId", this.clientId), - new KeyValuePair("ConsistencyLevel", telemetryInformation.ConsistencyLevel), - new KeyValuePair("PartitionKeyRangeId", telemetryInformation.PartitionKeyRangeId), - }; - - PushOperationLevelMetrics(telemetryInformation, dimensions); - } - - /// - /// Pushes various operation-level metrics to OpenTelemetry. - /// - /// Contains telemetry data related to the operation, such as item counts, request charge, and latency. - /// Key-value pairs representing various metadata about the operation (e.g., container, operation type, consistency level). - private static void PushOperationLevelMetrics(TelemetryInformation telemetryInformation, KeyValuePair[] dimensions) - { - maxItemCounts.Add(new Tuple[]>(Convert.ToInt32(telemetryInformation.MaxItemCount), dimensions)); - - OpenTelemetryMetrics.RequestUnitsHistogram.Record(telemetryInformation.RequestCharge, dimensions); - OpenTelemetryMetrics.RequestLatencyHistogram.Record(telemetryInformation.RequestLatency.Value.Milliseconds, dimensions); - OpenTelemetryMetrics.NumberOfOperationCallCounter.Add(1, dimensions); - } - - public static IEnumerable> GetMaxItemCount() - { - foreach (Tuple[]> maxItemCount in maxItemCounts) - { - yield return new Measurement(maxItemCount.Item1, maxItemCount.Item2); - } - } - - } -} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/Collector/TelemetryInformation.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Collector/TelemetryInformation.cs index 580561d6b4..a8210fb0a4 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/Collector/TelemetryInformation.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Collector/TelemetryInformation.cs @@ -26,9 +26,5 @@ internal class TelemetryInformation internal double RequestCharge { get; set; } // Required only for operation level telemetry internal string CollectionLink { get; set; } = null; // Required only for collection cache telemetry internal ITrace Trace { get; set; } // Required to fetch network level telemetry out of the trace object - - internal string MaxItemCount { get; set; } - internal string ActualItemCount { get; set; } - internal string PartitionKeyRangeId { get; set; } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs new file mode 100644 index 0000000000..39d17fb660 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -0,0 +1,103 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Telemetry +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Metrics; + + internal static class CosmosOperationMeter + { + internal static Histogram RequestLatencyHistogram = null; + internal static Histogram RequestUnitsHistogram = null; + internal static Histogram ActualItemHistogram = null; + internal static Histogram InstanceCountHistogram = null; + + private static Meter cosmosMeter; + + public static void Initialize() + { + cosmosMeter ??= new Meter(OpenTelemetryMetricsConstant.OperationMetrics.MeterName, OpenTelemetryMetricsConstant.OperationMetrics.Version); + + CosmosOperationMeter.RequestLatencyHistogram = cosmosMeter.CreateHistogram(name: OpenTelemetryMetricsConstant.OperationMetrics.Name.Latency, + unit: OpenTelemetryMetricsConstant.OperationMetrics.Unit.Sec, + description: OpenTelemetryMetricsConstant.OperationMetrics.Description.Latency); + + CosmosOperationMeter.RequestUnitsHistogram = cosmosMeter.CreateHistogram(name: OpenTelemetryMetricsConstant.OperationMetrics.Name.RequestCharge, + unit: OpenTelemetryMetricsConstant.OperationMetrics.Unit.RequestUnit, + description: OpenTelemetryMetricsConstant.OperationMetrics.Description.RequestCharge); + + CosmosOperationMeter.ActualItemHistogram = cosmosMeter.CreateHistogram(name: OpenTelemetryMetricsConstant.OperationMetrics.Name.RowCount, + unit: OpenTelemetryMetricsConstant.OperationMetrics.Unit.Count, + description: OpenTelemetryMetricsConstant.OperationMetrics.Description.RowCount); + + CosmosOperationMeter.InstanceCountHistogram = cosmosMeter.CreateHistogram(name: OpenTelemetryMetricsConstant.OperationMetrics.Name.ActiveInstances, + unit: OpenTelemetryMetricsConstant.OperationMetrics.Unit.Count, + description: OpenTelemetryMetricsConstant.OperationMetrics.Description.ActiveInstances); + } + + public static void RecordTelemetry(string operationName, string accountName, string containerName, string databaseName, OpenTelemetryAttributes attributes) + { + Func[]> dimensionsFunc = () => new[] + { + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, "cosmosdb"), + new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, operationName), + new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName), + new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName), + + //new KeyValuePair(OpenTelemetryAttributeKeys.Consistency, "consistency"), + + new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)attributes.StatusCode), + new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, (int)attributes.SubStatusCode) + }; + + CosmosOperationMeter.RecordActualItemCount(Convert.ToInt32(attributes.ItemCount), dimensionsFunc); + CosmosOperationMeter.RecordRequestUnit(attributes.RequestCharge.Value, dimensionsFunc); + CosmosOperationMeter.RecordRequestLatency(attributes.Diagnostics.GetClientElapsedTime(), dimensionsFunc); + CosmosOperationMeter.RecordActiveInstances(dimensionsFunc); + } + + public static void RecordActualItemCount(int actualItemCount, Func[]> dimensionsFunc) + { + if (CosmosOperationMeter.ActualItemHistogram == null || !CosmosOperationMeter.ActualItemHistogram.Enabled) + { + return; + } + + CosmosOperationMeter.ActualItemHistogram.Record(actualItemCount, dimensionsFunc()); + } + + internal static void RecordRequestUnit(double requestCharge, Func[]> dimensionsFunc) + { + if (CosmosOperationMeter.RequestUnitsHistogram == null || !CosmosOperationMeter.RequestUnitsHistogram.Enabled) + { + return; + } + + CosmosOperationMeter.RequestUnitsHistogram?.Record(requestCharge, dimensionsFunc()); + } + + internal static void RecordRequestLatency(TimeSpan? requestLatency, Func[]> dimensionsFunc) + { + if (CosmosOperationMeter.RequestLatencyHistogram == null || !CosmosOperationMeter.RequestLatencyHistogram.Enabled || !requestLatency.HasValue) + { + return; + } + + CosmosOperationMeter.RequestLatencyHistogram.Record(requestLatency.Value.Milliseconds, dimensionsFunc()); + } + + public static void RecordActiveInstances(Func[]> dimensionsFunc) + { + if (CosmosOperationMeter.InstanceCountHistogram == null || !CosmosOperationMeter.InstanceCountHistogram.Enabled) + { + return; + } + + CosmosOperationMeter.InstanceCountHistogram.Record(CosmosClient.NumberOfActiveClients, dimensionsFunc()); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetrics.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetrics.cs deleted file mode 100644 index b9e5fe9a79..0000000000 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetrics.cs +++ /dev/null @@ -1,52 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.Metrics; - using System.Text; - using Microsoft.Azure.Cosmos.Telemetry; - - /// - /// The OpenTelemetryMetrics class contains internal static members to create and record Cosmos DB SDK metrics using OpenTelemetry. These metrics allow you to monitor the performance and resource consumption of Cosmos DB operations. - /// - internal static class OpenTelemetryMetrics - { - internal static readonly Meter CosmosMeter = new Meter("Azure.Cosmos.Client"); - - internal static readonly Counter NumberOfOperationCallCounter = - CosmosMeter.CreateCounter(name: "cosmos.client.op.calls", - unit: "#", - description: "Number of operation calls"); - - internal static readonly Histogram RequestLatencyHistogram = - CosmosMeter.CreateHistogram(name: "cosmos.client.op.latency", - unit: "#", - description: "Total end-to-end duration of the operation"); - - internal static readonly Histogram RequestUnitsHistogram = - CosmosMeter.CreateHistogram(name: "cosmos.client.op.RUs", - unit: "#", - description: "Total request units per operation (sum of RUs for all requested needed when processing an operation)"); - - internal static readonly ObservableGauge maxItemGauge = - CosmosMeter.CreateObservableGauge(name: "cosmos.client.op.maxItemCount", - observeValue: () => OpenTelemetryMetricsCollector.GetMaxItemCount(), - unit: "#", - description: "For feed operations (query, readAll, readMany, change feed) and batch operations this meter capture the requested maxItemCount per page/request"); - - /*internal static readonly ObservableGauge ActualItemCounter = - CosmosMeter.CreateObservableGauge(name: "cosmos.client.op.actualItemCount", - unit: "#", - description: "For feed operations (query, readAll, readMany, change feed) batch operations this meter capture the actual item count in responses from the service"); - - internal static readonly ObservableGauge RegionsContactedCounter = - CosmosMeter.CreateObservableGauge(name: "cosmos.client.op.regionsContacted", - unit: "#", - description: "Number of regions contacted when executing an operation");*/ - - } -} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs new file mode 100644 index 0000000000..6cadb7efa0 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs @@ -0,0 +1,153 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Telemetry +{ + /// + /// OpenTelemetryMetricsConstant + /// + public sealed class OpenTelemetryMetricsConstant + { + /// + /// OperationMetrics + /// + public static class OperationMetrics + { + /// + /// OperationMetricName + /// + public const string MeterName = "Azure.Cosmos.Client.Operation"; + + /// + /// MetricVersion100 + /// + public const string Version = "1.0.0"; + + /// + /// Metric Names + /// + public static class Name + { + /// + /// Total request units per operation (sum of RUs for all requested needed when processing an operation) + /// + public const string RequestCharge = "db.cosmosdb.operation.request_charge"; + + /// + /// Total end-to-end duration of the operation + /// + public const string Latency = "db.client.operation.duration"; + + /// + /// For feed operations (query, readAll, readMany, change feed) batch operations this meter capture the actual item count in responses from the service. + /// + public const string RowCount = "db.client.response.row_count"; + + /// + /// Number of active SDK client instances. + /// + public const string ActiveInstances = "db.cosmosdb.client.active_instances"; + + } + + /// + /// Unit for metrics + /// + public static class Unit + { + /// + /// Count + /// + public const string Count = "#"; + + /// + /// Milliseconds + /// + public const string Sec = "s"; + + /// + /// RUUnit + /// + public const string RequestUnit = "# RU"; + + } + + /// + /// Metric Descriptions + /// + public static class Description + { + /// + /// LatencyDesc + /// + public const string Latency = "Total end-to-end duration of the operation"; + + /// + /// RUDesc + /// + public const string RequestCharge = "Total request units per operation (sum of RUs for all requested needed when processing an operation)"; + + /// + /// ActualItemDesc + /// + public const string RowCount = "For feed operations (query, readAll, readMany, change feed) batch operations this meter capture the actual item count in responses from the service"; + + /// + /// InstanceMetricDesc + /// + public const string ActiveInstances = "Number of active SDK client instances."; + } + } + + /// + /// Buckets + /// + public static class HistogramBuckets + { + /// + /// ExplicitBucketBoundaries for "db.cosmosdb.operation.request_charge" Metrics + /// + /// + /// 1, 5, 10: Low Usage Levels, These smaller buckets allow for precise tracking of operations that consume a minimal number of Request Units. This is important for lightweight operations, such as basic read requests or small queries, where resource utilization should be optimized. Monitoring these low usage levels can help ensure that the application is not inadvertently using more resources than necessary.

+ /// 25, 50: Moderate Usage Levels, These ranges capture more moderate operations, which are typical in many applications. For example, queries that return a reasonable amount of data or perform standard CRUD operations may fall within these limits. Identifying usage patterns in these buckets can help detect efficiency issues in routine operations.

+ /// 100, 250: Higher Usage Levels, These boundaries represent operations that may require significant resources, such as complex queries or larger transactions. Monitoring RUs in these ranges can help identify performance bottlenecks or costly queries that might lead to throttling.

+ /// 500, 1000: Very High Usage Levels, These buckets capture operations that consume a substantial number of Request Units, which may indicate potentially expensive queries or batch processes. Understanding the frequency and patterns of such high RU usage can be critical in optimizing performance and ensuring the application remains within provisioned throughput limits. + ///
+ public static readonly double[] RequestUnitBuckets = new double[] { 1, 5, 10, 25, 50, 100, 250, 500, 1000}; + + /// + /// ExplicitBucketBoundaries for "db.client.operation.duration" Metrics. + /// + /// + /// 0.001, 0.005, 0.010 seconds: Higher Precision at Sub-Millisecond Levels, For high-performance workloads, especially when dealing with microservices or low-latency queries.

+ /// 0.050, 0.100, 0.200 seconds: Granularity for Standard Web Applications, These values allow detailed tracking for latencies between 50ms and 200ms, which are common in web applications. Fine-grained buckets in this range help in identifying performance issues before they grow critical, while covering the typical response times expected in Cosmos DB.

+ /// 0.500, 1.000 seconds: Wider Range for Intermediate Latencies, Operations that take longer, in the range of 500ms to 1 second, are still important for performance monitoring. By capturing these values, you maintain awareness of potential bottlenecks or slower requests that may need optimization.

+ /// 2.000, 5.000 seconds: Capturing Outliers and Slow Queries, It’s important to track outliers that might go beyond 1 second. Having buckets for 2 and 5 seconds enables identification of rare, long-running operations that may require further investigation. + ///
+ public static readonly double[] RequestLatencyBuckets = new double[] { 0.001, 0.005, 0.010, 0.050, 0.100, 0.200, 0.500, 1.000, 2.000, 5.000 }; + + /// + /// ExplicitBucketBoundaries for "db.client.response.row_count" Metrics + /// + /// + /// 10, 50, 100: Small Response Sizes, These buckets are useful for capturing scenarios where only a small number of items are returned. Such small queries are common in real-time or interactive applications where performance and quick responses are critical. They also help in tracking the efficiency of operations that should return minimal data, minimizing resource usage.

+ /// 250, 500, 1000: Moderate Response Sizes, These values represent typical workloads where moderate amounts of data are returned in each query. This is useful for applications that need to return more information, such as data analytics or reporting systems. Tracking these ranges helps identify whether the system is optimized for these relatively larger data sets and if they lead to any performance degradation.

+ /// 2000, 5000: Larger Response Sizes, These boundaries capture scenarios where the query returns large datasets, often used in batch processing or in-depth analytical queries. These larger page sizes can potentially increase memory or CPU usage and may lead to longer query execution times, making it important to track performance in these ranges.

+ /// 10000: Very Large Response Sizes (Outliers), This boundary is included to capture rare, very large response sizes. Such queries can put significant strain on system resources, including memory, CPU, and network bandwidth, and can often lead to performance issues such as high latency or even network drops. + ///
+ public static readonly double[] RowCountBuckets = new double[] { 10, 50, 100, 250, 500, 1000, 2000, 5000, 10000 }; + + /// + /// ExplicitBucketBoundaries for "db.cosmosdb.client.active_instances" Metrics + /// + /// + /// 1: Single Instance, This bucket represents the ideal scenario where there is only one active instance of the SDK client per process. Monitoring this level helps confirm that best practices are being followed.

+ /// 2, 3, 5: Low Multi-Instance Usage, These buckets capture scenarios where there are a small number of instances (2 to 5). Monitoring these levels is important as having a couple of instances may be necessary for certain applications, but it can also start to introduce resource contention. Identifying any performance degradation at this level can help detect early signs of potential issues.

+ /// 10, 20: Moderate Multi-Instance Usage, These boundaries capture situations where there are more than a few instances of the SDK client. As the number of active instances increases, the risk of CPU and memory-related issues also rises. Tracking these ranges can provide insight into whether the application is scaling appropriately or if optimizations are required.

+ /// 50, 100: These buckets account for scenarios where the number of active instances is significant. Monitoring these higher values is critical, as having many instances may lead to severe performance impacts, including increased latency and resource exhaustion. This can help identify applications that may be misconfigured or operating beyond their intended limits. + ///
+ public static readonly double[] ActiveInstancesBuckets = new double[] { 1, 2, 3, 5, 10, 50, 100 }; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/TelemetryToServiceHelper.cs b/Microsoft.Azure.Cosmos/src/Telemetry/TelemetryToServiceHelper.cs index 244f7ddfb8..4b94811b67 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/TelemetryToServiceHelper.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/TelemetryToServiceHelper.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Cosmos.Telemetry { using System; - using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -20,15 +19,14 @@ namespace Microsoft.Azure.Cosmos.Telemetry internal class TelemetryToServiceHelper : IDisposable { - private readonly OpenTelemetryMetricsCollector openTelemetryCollector = null; private ITelemetryCollector collector = new TelemetryCollectorNoOp(); - + internal static TimeSpan DefaultBackgroundRefreshClientConfigTimeInterval = TimeSpan.FromMinutes(10); private readonly AuthorizationTokenProvider cosmosAuthorization; private readonly CosmosHttpClient httpClient; - private readonly Uri serviceEndpoint; + private readonly Uri serviceEnpoint; private readonly ConnectionPolicy connectionPolicy; private readonly string clientId; private readonly GlobalEndpointManager globalEndpointManager; @@ -36,12 +34,9 @@ internal static TimeSpan DefaultBackgroundRefreshClientConfigTimeInterval private ClientTelemetry clientTelemetry = null; - private TelemetryToServiceHelper(bool isMetricEnabled, string clientId, string accountName) + private TelemetryToServiceHelper() { - if (isMetricEnabled) - { - this.openTelemetryCollector = new OpenTelemetryMetricsCollector(clientId: clientId, accountName: accountName); - } + //NoOpConstructor } private TelemetryToServiceHelper( @@ -52,13 +47,12 @@ private TelemetryToServiceHelper( Uri serviceEndpoint, GlobalEndpointManager globalEndpointManager, CancellationTokenSource cancellationTokenSource) - : this(connectionPolicy.CosmosClientTelemetryOptions.IsClientMetricsEnabled, clientId, serviceEndpoint.Host) { this.clientId = clientId; this.cosmosAuthorization = cosmosAuthorization; this.httpClient = httpClient; this.connectionPolicy = connectionPolicy; - this.serviceEndpoint = serviceEndpoint; + this.serviceEnpoint = serviceEndpoint; this.globalEndpointManager = globalEndpointManager; this.cancellationTokenSource = cancellationTokenSource; } @@ -72,15 +66,11 @@ public static TelemetryToServiceHelper CreateAndInitializeClientConfigAndTelemet CancellationTokenSource cancellationTokenSource) { #if INTERNAL - return new TelemetryToServiceHelper(isMetricEnabled: connectionPolicy.CosmosClientTelemetryOptions.IsClientMetricsEnabled, - clientId: clientId, - accountName: serviceEndpoint.Host); + return new TelemetryToServiceHelper(); #else if (connectionPolicy.CosmosClientTelemetryOptions.DisableSendingMetricsToService) { - return new TelemetryToServiceHelper(isMetricEnabled: connectionPolicy.CosmosClientTelemetryOptions.IsClientMetricsEnabled, - clientId: clientId, - accountName: serviceEndpoint.Host); + return new TelemetryToServiceHelper(); } TelemetryToServiceHelper helper = new TelemetryToServiceHelper( @@ -92,7 +82,7 @@ public static TelemetryToServiceHelper CreateAndInitializeClientConfigAndTelemet globalEndpointManager: globalEndpointManager, cancellationTokenSource: cancellationTokenSource); - _ = helper.RetrieveConfigAndInitiateTelemetryAsync(); // Let it run in background + _ = helper.RetrieveConfigAndInitiateTelemetryAsync(); // Let it run in backgroud return helper; #endif @@ -102,7 +92,7 @@ private async Task RetrieveConfigAndInitiateTelemetryAsync() { try { - Uri serviceEndpointWithPath = new Uri(this.serviceEndpoint + Paths.ClientConfigPathSegment); + Uri serviceEndpointWithPath = new Uri(this.serviceEnpoint + Paths.ClientConfigPathSegment); while (!this.cancellationTokenSource.IsCancellationRequested) { TryCatch databaseAccountClientConfigs = await this.GetDatabaseAccountClientConfigAsync( @@ -182,25 +172,9 @@ await cosmosAuthorization.AddAuthorizationHeaderAsync( } } - public List GetCollectors(RequestMessage request = null) - { - List collectors = new List(2); - if (request is null || IsAllowed(request)) - { - collectors.Add(this.collector); - } - - if (this.openTelemetryCollector != null) - { - collectors.Add(this.openTelemetryCollector); - } - - return collectors; - } - - private static bool IsAllowed(RequestMessage request) + public ITelemetryCollector GetCollector() { - return ClientTelemetryOptions.AllowedResourceTypes.Equals(request.ResourceType); + return this.collector; } public bool IsClientTelemetryJobRunning() diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs index e69de29bb2..e9111e6327 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs @@ -0,0 +1,368 @@ +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Reflection; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Fluent; + using Microsoft.Azure.Documents; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + using Moq.Protected; + + [TestClass] + public class OpenTelemetryMetricsTest : BaseCosmosClientHelper + { + private ContainerInternal Container = null; + private const string PartitionKey = "/pk"; + + [TestInitialize] + public async Task TestInitialize() + { + await this.TestInit(); + + ContainerResponse response = await this.database.CreateContainerAsync( + new ContainerProperties(id: "ClientCreateAndInitializeContainer", partitionKeyPath: PartitionKey), + throughput: 20000, + cancellationToken: this.cancellationToken); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Container); + Assert.IsNotNull(response.Resource); + this.Container = (ContainerInlineCore)response; + + // Create items with different + for (int i = 0; i < 500; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(); + item.pk = "Status" + i.ToString(); + item.id = i.ToString(); + ItemResponse itemResponse = await this.Container.CreateItemAsync(item); + Assert.AreEqual(HttpStatusCode.Created, itemResponse.StatusCode); + } + } + + [TestCleanup] + public async Task Cleanup() + { + await base.TestCleanup(); + } + + [TestMethod] + public async Task CreateAndInitializeTest() + { + int httpCallsMade = 0; + HttpClientHandlerHelper httpClientHandlerHelper = new HttpClientHandlerHelper + { + RequestCallBack = (request, cancellationToken) => + { + httpCallsMade++; + return null; + } + }; + + (string endpoint, string authKey) = TestCommon.GetAccountInfo(); + List<(string, string)> containers = new List<(string, string)> + { (this.database.Id, "ClientCreateAndInitializeContainer")}; + + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions + { + HttpClientFactory = () => new HttpClient(httpClientHandlerHelper), + }; + + CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync(endpoint, authKey, containers, cosmosClientOptions); + Assert.IsNotNull(cosmosClient); + int httpCallsMadeAfterCreation = httpCallsMade; + + ContainerInternal container = (ContainerInternal)cosmosClient.GetContainer(this.database.Id, "ClientCreateAndInitializeContainer"); + ItemResponse readResponse = await container.ReadItemAsync("1", new Cosmos.PartitionKey("Status1")); + string diagnostics = readResponse.Diagnostics.ToString(); + Assert.IsTrue(diagnostics.Contains("\"ConnectionMode\":\"Direct\"")); + Assert.AreEqual(httpCallsMade, httpCallsMadeAfterCreation); + cosmosClient.Dispose(); + } + + [TestMethod] + public async Task CreateAndInitializeWithCosmosClientBuilderTest() + { + int httpCallsMade = 0; + HttpClientHandlerHelper httpClientHandlerHelper = new HttpClientHandlerHelper + { + RequestCallBack = (request, cancellationToken) => + { + httpCallsMade++; + return null; + } + }; + + (string endpoint, string authKey) = TestCommon.GetAccountInfo(); + List<(string, string)> containers = new List<(string, string)> + { (this.database.Id, "ClientCreateAndInitializeContainer")}; + + CosmosClientBuilder builder = new CosmosClientBuilder(endpoint, authKey).WithHttpClientFactory(() => new HttpClient(httpClientHandlerHelper)); + CosmosClient cosmosClient = await builder.BuildAndInitializeAsync(containers); + Assert.IsNotNull(cosmosClient); + int httpCallsMadeAfterCreation = httpCallsMade; + + ContainerInternal container = (ContainerInternal)cosmosClient.GetContainer(this.database.Id, "ClientCreateAndInitializeContainer"); + ItemResponse readResponse = await container.ReadItemAsync("1", new Cosmos.PartitionKey("Status1")); + Assert.AreEqual(httpCallsMade, httpCallsMadeAfterCreation); + cosmosClient.Dispose(); + } + + [TestMethod] + [ExpectedException(typeof(HttpRequestException))] + public async Task AuthIncorrectTest() + { + List<(string databaseId, string containerId)> containers = new List<(string databaseId, string containerId)> + { (this.database.Id, "ClientCreateAndInitializeContainer")}; + string authKey = TestCommon.GetAccountInfo().authKey; + CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync("https://127.0.0.1:0000/", authKey, containers); + cosmosClient.Dispose(); + } + + [TestMethod] + [ExpectedException(typeof(CosmosException))] + public async Task DatabaseIncorrectTest() + { + List<(string databaseId, string containerId)> containers = new List<(string databaseId, string containerId)> + { ("IncorrectDatabase", "ClientCreateAndInitializeContainer")}; + (string endpoint, string authKey) = TestCommon.GetAccountInfo(); + try + { + CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync(endpoint, authKey, containers); + } + catch (CosmosException ex) + { + Assert.IsTrue(ex.StatusCode == HttpStatusCode.NotFound); + throw; + } + } + + [TestMethod] + [ExpectedException(typeof(CosmosException))] + public async Task ContainerIncorrectTest() + { + List<(string databaseId, string containerId)> containers = new List<(string databaseId, string containerId)> + { (this.database.Id, "IncorrectContainer")}; + (string endpoint, string authKey) = TestCommon.GetAccountInfo(); + try + { + CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync(endpoint, authKey, containers); + } + catch (CosmosException ex) + { + Assert.IsTrue(ex.StatusCode == HttpStatusCode.NotFound); + throw; + } + } + + [TestMethod] + public async Task InitializeContainersAsync_WhenThrowsException_ShouldDisposeCosmosClient() + { + // Arrange. + List<(string databaseId, string containerId)> containers = new() + { ("IncorrectDatabase", "ClientCreateAndInitializeContainer")}; + + CosmosException cosmosException = new ( + statusCode: HttpStatusCode.NotFound, + message: "Test", + stackTrace: null, + headers: null, + trace: default, + error: null, + innerException: null); + + Mock cosmosClient = new (); + cosmosClient + .Setup(x => x.GetContainer(It.IsAny(), It.IsAny())) + .Throws(cosmosException); + + cosmosClient + .Protected() + .Setup("Dispose", ItExpr.Is(x => x)) + .Verifiable(); + + // Act. + CosmosException ex = await Assert.ThrowsExceptionAsync(() => cosmosClient.Object.InitializeContainersAsync(containers, this.cancellationToken)); + + // Assert. + Assert.IsNotNull(ex); + Assert.IsTrue(ex.StatusCode == HttpStatusCode.NotFound); + cosmosClient.Verify(); + } + + /// + /// Test to validate that when is called with a + /// valid database id and a container id that exists in the database, an attempt is made to open + /// the rntbd connections to the backend replicas and the connections are opened successfully. + /// + [TestMethod] + [Owner("dkunda")] + public async Task CreateAndInitializeAsync_WithValidDatabaseAndContainer_ShouldOpenRntbdConnectionsToBackendReplicas() + { + // Arrange. + int httpCallsMade = 0, maxRequestsPerConnection = 6; + HttpClientHandlerHelper httpClientHandlerHelper = new () + { + RequestCallBack = (request, cancellationToken) => + { + httpCallsMade++; + return null; + } + }; + List<(string, string)> containers = new () + { + ( + this.database.Id, + "ClientCreateAndInitializeContainer" + ) + }; + + (string endpoint, string authKey) = TestCommon.GetAccountInfo(); + CosmosClientOptions cosmosClientOptions = new () + { + HttpClientFactory = () => new HttpClient(httpClientHandlerHelper), + ConnectionMode = ConnectionMode.Direct, + MaxRequestsPerTcpConnection = maxRequestsPerConnection, + }; + + // Act. + CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync( + accountEndpoint: endpoint, + authKeyOrResourceToken: authKey, + containers: containers, + cosmosClientOptions: cosmosClientOptions); + + // Assert. + Assert.AreEqual(5, httpCallsMade); + + IStoreClientFactory factory = (IStoreClientFactory)cosmosClient.DocumentClient.GetType() + .GetField("storeClientFactory", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(cosmosClient.DocumentClient); + StoreClientFactory storeClientFactory = (StoreClientFactory) factory; + + TransportClient client = (TransportClient)storeClientFactory.GetType() + .GetField("transportClient", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(storeClientFactory); + Documents.Rntbd.TransportClient transportClient = (Documents.Rntbd.TransportClient) client; + + Documents.Rntbd.ChannelDictionary channelDict = (Documents.Rntbd.ChannelDictionary)transportClient.GetType() + .GetField("channelDictionary", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(transportClient); + + ConcurrentDictionary allChannels = (ConcurrentDictionary)channelDict.GetType() + .GetField("channels", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(channelDict); + + Assert.AreEqual(1, allChannels.Count); + + Documents.Rntbd.LoadBalancingChannel loadBalancingChannel = (Documents.Rntbd.LoadBalancingChannel)allChannels[allChannels.Keys.First()]; + Documents.Rntbd.LoadBalancingPartition loadBalancingPartition = (Documents.Rntbd.LoadBalancingPartition)loadBalancingChannel.GetType() + .GetField("singlePartition", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(loadBalancingChannel); + + Assert.IsNotNull(loadBalancingPartition); + + int channelCapacity = (int)loadBalancingPartition.GetType() + .GetField("capacity", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(loadBalancingPartition); + + List openChannels = (List)loadBalancingPartition.GetType() + .GetField("openChannels", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(loadBalancingPartition); + + Assert.IsNotNull(openChannels); + Assert.AreEqual(1, openChannels.Count, "Here the expected value 1 explains how many TCP connections were opened by the LoadBalancingPartition.OpenChannelAsync()." + + "The emulator by default returns 12 partitions, and each partition has 4 replicas, and by behavior the emulator uses the same URI for each of these replica," + + "hence 12 * 4 = 48 times we call the OpenChannelAsync(). However, the number of TCP connections established would be just one per each unique endpoint."); + Assert.AreEqual(openChannels.Count * maxRequestsPerConnection, channelCapacity); + + Documents.Rntbd.LbChannelState channelState = openChannels.First(); + + Assert.IsNotNull(channelState); + Assert.IsTrue(channelState.DeepHealthy); + } + + /// + /// Test to validate that when is called with + /// the Gateway Mode enabled, the operation should not open any Rntbd connections to the backend replicas. + /// + [TestMethod] + [Owner("dkunda")] + public async Task CreateAndInitializeAsync_WithGatewayModeEnabled_ShouldNotOpenConnectionToBackendReplicas() + { + // Arrange. + int httpCallsMade = 0; + HttpClientHandlerHelper httpClientHandlerHelper = new() + { + RequestCallBack = (request, cancellationToken) => + { + httpCallsMade++; + return null; + } + }; + List<(string, string)> containers = new() + { + ( + this.database.Id, + "ClientCreateAndInitializeContainer" + ) + }; + + (string endpoint, string authKey) = TestCommon.GetAccountInfo(); + CosmosClientOptions cosmosClientOptions = new() + { + HttpClientFactory = () => new HttpClient(httpClientHandlerHelper), + ConnectionMode = ConnectionMode.Gateway, + }; + + // Act. + CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync( + accountEndpoint: endpoint, + authKeyOrResourceToken: authKey, + containers: containers, + cosmosClientOptions: cosmosClientOptions); + + // Assert. + Assert.IsNotNull(cosmosClient); + Assert.AreEqual(1, httpCallsMade); + } + + /// + /// Test to validate that when is called with a + /// valid database id and an invalid container that doesn't exists in the database, the cosmos + /// client initialization fails and a is thrown with a 404 status code. + /// + [TestMethod] + [Owner("dkunda")] + public async Task CreateAndInitializeAsync_WithValidDatabaseAndInvalidContainer_ShouldThrowException() + { + // Arrange. + List<(string, string)> containers = new() + { + ( + this.database.Id, + "ClientCreateAndInitializeInvalidContainer" + ) + }; + + (string endpoint, string authKey) = TestCommon.GetAccountInfo(); + CosmosClientOptions cosmosClientOptions = new(); + + // Act. + CosmosException ce = await Assert.ThrowsExceptionAsync(() => CosmosClient.CreateAndInitializeAsync( + accountEndpoint: endpoint, + authKeyOrResourceToken: authKey, + containers: containers, + cosmosClientOptions: cosmosClientOptions)); + + // Assert. + Assert.IsNotNull(ce); + Assert.AreEqual(HttpStatusCode.NotFound, ce.StatusCode); + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs new file mode 100644 index 0000000000..6e2e022591 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics +{ + using OpenTelemetry.Metrics; + using OpenTelemetry; + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System.Linq; + + public class CustomMetricExporter : BaseExporter + { + private static readonly Dictionary expectedMetrics = new Dictionary() + { + { "db.client.operation.duration", MetricType.Histogram }, + { "db.client.response.row_count", MetricType.Histogram}, + { "db.cosmosdb.operation.request_charge", MetricType.Histogram }, + { "db.cosmosdb.client.active_instances", MetricType.Histogram }, + }; + + // This method will be called periodically by OpenTelemetry SDK + public override ExportResult Export(in Batch batch) + { + Console.WriteLine("\n[Custom Exporter] Exporting metrics:"); + + Dictionary actualMetrics = new Dictionary(); + foreach (Metric metric in batch) + { + actualMetrics.Add(metric.Name, metric.MetricType); + + Console.WriteLine($"Metric: {metric.Name}, Type: {metric.MetricType}"); + foreach (MetricPoint metricPoint in metric.GetMetricPoints()) + { + switch (metric.MetricType) + { + case MetricType.LongSum: + Console.WriteLine($"Value (Counter): {metricPoint.GetSumLong()}"); + break; + case MetricType.LongGauge: + Console.WriteLine($"Value (Gauge): {metricPoint.GetGaugeLastValueLong()}"); + break; + case MetricType.Histogram: + Console.WriteLine($"Value (Histogram): {metricPoint.GetHistogramCount()}"); + + foreach (HistogramBucket bucket in metricPoint.GetHistogramBuckets()) + { + Console.WriteLine($"{bucket.ExplicitBound} : {bucket.BucketCount}"); + } + break; + } + } + } + + Assert.IsTrue(expectedMetrics.Count == actualMetrics.Count && !expectedMetrics.Except(actualMetrics).Any()); + + return ExportResult.Success; + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs index 06086b5a2d..7154f6c4ee 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -5,53 +5,17 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics { using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Net.Http; - using System.Reflection; using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.Fluent; - using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; - using Moq; - using Moq.Protected; using OpenTelemetry.Metrics; using OpenTelemetry; using System.Diagnostics; + using OpenTelemetry.Resources; + using Microsoft.Azure.Cosmos.Telemetry; [TestClass] public class OpenTelemetryMetricsTest : BaseCosmosClientHelper { - private ContainerInternal Container = null; - private const string PartitionKey = "/pk"; - - [TestInitialize] - public async Task TestInitialize() - { - await this.TestInit(); - - ContainerResponse response = await this.database.CreateContainerAsync( - new ContainerProperties(id: "ClientCreateAndInitializeContainer", partitionKeyPath: PartitionKey), - throughput: 20000, - cancellationToken: this.cancellationToken); - Assert.IsNotNull(response); - Assert.IsNotNull(response.Container); - Assert.IsNotNull(response.Resource); - this.Container = (ContainerInlineCore)response; - - // Create items with different - for (int i = 0; i < 500; i++) - { - ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(); - item.pk = "Status" + i.ToString(); - item.id = i.ToString(); - ItemResponse itemResponse = await this.Container.CreateItemAsync(item); - Assert.AreEqual(HttpStatusCode.Created, itemResponse.StatusCode); - } - } - [TestCleanup] public async Task Cleanup() { @@ -61,65 +25,86 @@ public async Task Cleanup() [TestMethod] public async Task OperationLevelMetrics() { - //var histogramBuckets = new double[] { 0, 5, 10, 25, 50, 75, 100, 250, 500 }; + Console.WriteLine("Start => " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); MeterProvider meterProvider = Sdk - .CreateMeterProviderBuilder() - .AddMeter("*")/* - .AddView("cosmos.client.op.RUs", new ExplicitBucketHistogramConfiguration { Boundaries = histogramBuckets })*/ - .AddConsoleExporter() - .Build(); - - int httpCallsMade = 0; - HttpClientHandlerHelper httpClientHandlerHelper = new HttpClientHandlerHelper - { - RequestCallBack = (request, cancellationToken) => + .CreateMeterProviderBuilder() + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("OperationLevelMetrics")) + .AddMeter("Azure.Cosmos.Client.Operation") + .AddView(instrumentName: OpenTelemetryMetricsConstant.OperationMetrics.Name.RequestCharge, new ExplicitBucketHistogramConfiguration // Define histogram buckets { - httpCallsMade++; - return null; - } - }; + Boundaries = OpenTelemetryMetricsConstant.HistogramBuckets.RequestUnitBuckets + }) + .AddView(instrumentName: OpenTelemetryMetricsConstant.OperationMetrics.Name.Latency, new ExplicitBucketHistogramConfiguration // Define histogram buckets + { + Boundaries = OpenTelemetryMetricsConstant.HistogramBuckets.RequestLatencyBuckets + }) + .AddView(instrumentName: OpenTelemetryMetricsConstant.OperationMetrics.Name.RowCount, new ExplicitBucketHistogramConfiguration // Define histogram buckets + { + Boundaries = OpenTelemetryMetricsConstant.HistogramBuckets.RowCountBuckets + }) + .AddView(instrumentName: OpenTelemetryMetricsConstant.OperationMetrics.Name.ActiveInstances, new ExplicitBucketHistogramConfiguration // Define histogram buckets + { + Boundaries = OpenTelemetryMetricsConstant.HistogramBuckets.ActiveInstancesBuckets + }) + /*.AddOtlpExporter((exporterOptions, metricReaderOptions) => + { + exporterOptions.Endpoint = new Uri("http://localhost:9090/api/v1/otlp/v1/metrics"); + exporterOptions.Protocol = OtlpExportProtocol.HttpProtobuf; + metricReaderOptions.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = 1000; + })*/ + .AddConsoleExporter() + //.AddAzureMonitorMetricExporter( o => o.ConnectionString = "") + //.AddReader(new PeriodicExportingMetricReader(new CustomMetricExporter(), exportIntervalMilliseconds: 1000)) + .Build(); (string endpoint, string authKey) = TestCommon.GetAccountInfo(); - List<(string, string)> containers = new List<(string, string)> - { (this.database.Id, "ClientCreateAndInitializeContainer")}; - CosmosClientOptions cosmosClientOptions = new CosmosClientOptions { - HttpClientFactory = () => new HttpClient(httpClientHandlerHelper), CosmosClientTelemetryOptions = new CosmosClientTelemetryOptions() { IsClientMetricsEnabled = true } }; - CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync(endpoint, authKey, containers, cosmosClientOptions); - // Assert.IsNotNull(cosmosClient); - int httpCallsMadeAfterCreation = httpCallsMade; + this.SetClient(new CosmosClient(endpoint, authKey, cosmosClientOptions)); - ContainerInternal container = (ContainerInternal)cosmosClient.GetContainer(this.database.Id, "ClientCreateAndInitializeContainer"); - - await Task.Delay(1000); + Database database = await this.GetClient().CreateDatabaseIfNotExistsAsync(Guid.NewGuid().ToString()); + Container container = await database.CreateContainerIfNotExistsAsync(Guid.NewGuid().ToString(), "/pk", throughput: 10000); Stopwatch sw = Stopwatch.StartNew(); sw.Start(); + int counter = 1; while(true) { - ItemResponse readResponse = await container.ReadItemAsync("1", new Cosmos.PartitionKey("Status1")); - string diagnostics = readResponse.Diagnostics.ToString(); - if(sw.ElapsedMilliseconds > 2000) + string randomId = Guid.NewGuid().ToString(); + string pk = "Status1"; + + await container.CreateItemAsync(ToDoActivity.CreateRandomToDoActivity(id: randomId, pk: pk)); + await container.ReadItemAsync(randomId, new PartitionKey(pk)); + + QueryRequestOptions queryRequestOptions = new QueryRequestOptions() + { + MaxItemCount = Random.Shared.Next(1, 100) + }; + FeedIterator feedIterator = container.GetItemQueryIterator("SELECT * FROM c", requestOptions: queryRequestOptions); + while (feedIterator.HasMoreResults) + { + FeedResponse toDoActivities = await feedIterator.ReadNextAsync(); + Console.WriteLine($"{counter++} : Read {toDoActivities.Count} items"); + } + + if (sw.ElapsedMilliseconds > TimeSpan.FromSeconds(1).TotalMilliseconds) { break; } } sw.Stop(); - - await Task.Delay(1000); - - cosmosClient.Dispose(); meterProvider.Dispose(); - await Task.Delay(1000); + await Task.Delay(TimeSpan.FromSeconds(1)); + + Console.WriteLine("End => " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft - Backup.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft - Backup.Azure.Cosmos.EmulatorTests.csproj deleted file mode 100644 index 9978bcce5c..0000000000 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft - Backup.Azure.Cosmos.EmulatorTests.csproj +++ /dev/null @@ -1,388 +0,0 @@ - - - true - true - AnyCPU - net6.0 - false - false - Microsoft.Azure.Cosmos - Microsoft.Azure.Cosmos.EmulatorTests - true - master - True - $(LangVersion) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Always - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - PreserveNewest - - - Documents\15KB.json - PreserveNewest - - - Documents\100KBDocument.json - PreserveNewest - - - Documents\1MBDocument.json - PreserveNewest - - - Documents\2.5MBDocument.json - PreserveNewest - - - Documents\2MBDocument.json - PreserveNewest - - - Documents\MillionSong1KDocuments.json - PreserveNewest - - - - - - - - PreserveNewest - - - - - PreserveNewest - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - - true - true - ..\..\..\testkey.snk - - - - x64 - true - - diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 998b08f577..d670bdcca8 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -51,11 +51,19 @@
+ + + + + + + + @@ -66,7 +74,7 @@ - + diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/CustomOtelExporter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/CustomOtelExporter.cs index 7038ef3194..7522423fc2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/CustomOtelExporter.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/CustomOtelExporter.cs @@ -7,7 +7,6 @@ namespace Microsoft.Azure.Cosmos.Tracing using System; using System.Collections.Generic; using System.Diagnostics; - using Microsoft.Azure.Cosmos.Telemetry; using OpenTelemetry; using OpenTelemetry.Trace; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 70c64f4c71..58aef55f21 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -3287,8 +3287,18 @@ ], "MethodInfo": "Void set_DisableSendingMetricsToService(Boolean);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Void set_IsClientMetricsEnabled(Boolean)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_IsClientMetricsEnabled(Boolean);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Void set_QueryTextMode(Microsoft.Azure.Cosmos.QueryTextMode)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], "MethodInfo": "Void set_QueryTextMode(Microsoft.Azure.Cosmos.QueryTextMode);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" } }, @@ -11103,6 +11113,363 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+HistogramBuckets": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Void .ctor()": { + "Type": "Constructor", + "Attributes": [], + "MethodInfo": "[Void .ctor(), Void .ctor()]" + } + }, + "NestedTypes": { + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+HistogramBuckets;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Double[] ActiveInstancesBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] ActiveInstancesBuckets;IsInitOnly:True;IsStatic:True;" + }, + "Double[] RequestLatencyBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] RequestLatencyBuckets;IsInitOnly:True;IsStatic:True;" + }, + "Double[] RequestUnitBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] RequestUnitBuckets;IsInitOnly:True;IsStatic:True;" + }, + "Double[] RowCountBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] RowCountBuckets;IsInitOnly:True;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Description": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Name": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Unit": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "System.String MeterName": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String MeterName;IsInitOnly:False;IsStatic:True;" + }, + "System.String Version": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Version;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": { + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String ActiveInstances": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestCharge": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + }, + "System.String RowCount": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String ActiveInstances": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestCharge": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + }, + "System.String RowCount": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String Count": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Count;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestUnit": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestUnit;IsInitOnly:False;IsStatic:True;" + }, + "System.String Sec": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Sec;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + } + } + } + } + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+HistogramBuckets;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Double[] ActiveInstancesBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] ActiveInstancesBuckets;IsInitOnly:True;IsStatic:True;" + }, + "Double[] RequestLatencyBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] RequestLatencyBuckets;IsInitOnly:True;IsStatic:True;" + }, + "Double[] RequestUnitBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] RequestUnitBuckets;IsInitOnly:True;IsStatic:True;" + }, + "Double[] RowCountBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] RowCountBuckets;IsInitOnly:True;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Description": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Name": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Unit": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "System.String MeterName": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String MeterName;IsInitOnly:False;IsStatic:True;" + }, + "System.String Version": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Version;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": { + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String ActiveInstances": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestCharge": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + }, + "System.String RowCount": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String ActiveInstances": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestCharge": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + }, + "System.String RowCount": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String Count": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Count;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestUnit": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestUnit;IsInitOnly:False;IsStatic:True;" + }, + "System.String Sec": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Sec;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + } + } + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String ActiveInstances": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestCharge": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + }, + "System.String RowCount": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String ActiveInstances": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestCharge": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + }, + "System.String RowCount": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String Count": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Count;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestUnit": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestUnit;IsInitOnly:False;IsStatic:True;" + }, + "System.String Sec": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Sec;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.ThroughputProperties;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { From ee32e4376fc5e43f5085412cc17f44a392310f7f Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Mon, 14 Oct 2024 20:14:43 +0530 Subject: [PATCH 06/49] added documentation --- .../OpenTelemetryMetricsConstant.cs | 26 ++++++++++--------- .../Tracing/CustomOtelExporter.cs | 1 + 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs index 6cadb7efa0..506153ec75 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs @@ -2,10 +2,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Telemetry +namespace Microsoft.Azure.Cosmos { /// - /// OpenTelemetryMetricsConstant + /// The OpenTelemetryMetricsConstant class provides constants related to OpenTelemetry metrics for Azure Cosmos DB. + /// These metrics are useful for tracking various aspects of Cosmos DB client operations and compliant with Open Telemetry Semantic Conventions + /// It defines standardized names, units, descriptions, and histogram buckets for measuring and monitoring performance through OpenTelemetry. /// public sealed class OpenTelemetryMetricsConstant { @@ -15,12 +17,12 @@ public sealed class OpenTelemetryMetricsConstant public static class OperationMetrics { /// - /// OperationMetricName + /// the name of the operation meter /// public const string MeterName = "Azure.Cosmos.Client.Operation"; /// - /// MetricVersion100 + /// Version of the operation meter /// public const string Version = "1.0.0"; @@ -57,44 +59,44 @@ public static class Name public static class Unit { /// - /// Count + /// Unit representing a simple count /// public const string Count = "#"; /// - /// Milliseconds + /// Unit representing time in seconds /// public const string Sec = "s"; /// - /// RUUnit + /// Unit representing request units /// public const string RequestUnit = "# RU"; } /// - /// Metric Descriptions + /// Provides descriptions for metrics. /// public static class Description { /// - /// LatencyDesc + /// Description for operation duration /// public const string Latency = "Total end-to-end duration of the operation"; /// - /// RUDesc + /// Description for total request units per operation /// public const string RequestCharge = "Total request units per operation (sum of RUs for all requested needed when processing an operation)"; /// - /// ActualItemDesc + /// Description for the item count metric in responses /// public const string RowCount = "For feed operations (query, readAll, readMany, change feed) batch operations this meter capture the actual item count in responses from the service"; /// - /// InstanceMetricDesc + /// Description for the active SDK client instances metric /// public const string ActiveInstances = "Number of active SDK client instances."; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/CustomOtelExporter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/CustomOtelExporter.cs index 7522423fc2..7038ef3194 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/CustomOtelExporter.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/CustomOtelExporter.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Tracing using System; using System.Collections.Generic; using System.Diagnostics; + using Microsoft.Azure.Cosmos.Telemetry; using OpenTelemetry; using OpenTelemetry.Trace; From 1ddbcbe20757507590b42371ffb744c6e6a8ccf3 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 16 Oct 2024 10:55:22 +0530 Subject: [PATCH 07/49] emit metrics --- Microsoft.Azure.Cosmos/src/CosmosClient.cs | 4 ++ .../src/Microsoft.Azure.Cosmos.csproj | 4 +- .../src/Resource/ClientContextCore.cs | 2 +- .../OpenTelemetry/CosmosOperationMeter.cs | 45 ++++++++++++++----- .../OpenTelemetryAttributeKeys.cs | 5 +++ .../OpenTelemetryCoreRecorder.cs | 1 + 6 files changed, 47 insertions(+), 14 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/CosmosClient.cs b/Microsoft.Azure.Cosmos/src/CosmosClient.cs index 0c0cef4001..1bb971cdcb 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClient.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClient.cs @@ -1424,6 +1424,8 @@ private int IncrementNumberOfClientsCreated() { this.IncrementNumberOfActiveClients(); + CosmosOperationMeter.AddActiveInstance(this.Endpoint); + return Interlocked.Increment(ref numberOfClientsCreated); } @@ -1437,6 +1439,8 @@ private int DecrementNumberOfActiveClients() // In case dispose is called multiple times. Check if at least 1 active client is there if (NumberOfActiveClients > 0) { + CosmosOperationMeter.DisposeActiveInstance(this.Endpoint); + return Interlocked.Decrement(ref NumberOfActiveClients); } diff --git a/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj b/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj index bfe90ace04..55b2fba26a 100644 --- a/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj +++ b/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj @@ -114,7 +114,7 @@ - + @@ -127,7 +127,7 @@ - + diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index dbc4947c4b..9401ec8320 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -532,7 +532,7 @@ private async Task RunWithDiagnosticsHelperAsync( recorder.Record(response); CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, - accountName: this.client.Endpoint.Host, + accountName: this.client.Endpoint, containerName: containerName, databaseName: databaseName, attributes: response); diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index 39d17fb660..e45e694e70 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -7,13 +7,14 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System; using System.Collections.Generic; using System.Diagnostics.Metrics; + using System.Xml.Linq; internal static class CosmosOperationMeter { internal static Histogram RequestLatencyHistogram = null; internal static Histogram RequestUnitsHistogram = null; internal static Histogram ActualItemHistogram = null; - internal static Histogram InstanceCountHistogram = null; + internal static UpDownCounter InstanceCounter = null; private static Meter cosmosMeter; @@ -33,12 +34,12 @@ public static void Initialize() unit: OpenTelemetryMetricsConstant.OperationMetrics.Unit.Count, description: OpenTelemetryMetricsConstant.OperationMetrics.Description.RowCount); - CosmosOperationMeter.InstanceCountHistogram = cosmosMeter.CreateHistogram(name: OpenTelemetryMetricsConstant.OperationMetrics.Name.ActiveInstances, + CosmosOperationMeter.InstanceCounter = cosmosMeter.CreateUpDownCounter(name: OpenTelemetryMetricsConstant.OperationMetrics.Name.ActiveInstances, unit: OpenTelemetryMetricsConstant.OperationMetrics.Unit.Count, description: OpenTelemetryMetricsConstant.OperationMetrics.Description.ActiveInstances); } - public static void RecordTelemetry(string operationName, string accountName, string containerName, string databaseName, OpenTelemetryAttributes attributes) + public static void RecordTelemetry(string operationName, Uri accountName, string containerName, string databaseName, OpenTelemetryAttributes attributes) { Func[]> dimensionsFunc = () => new[] { @@ -46,10 +47,9 @@ public static void RecordTelemetry(string operationName, string accountName, str new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, operationName), new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName), new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName), - - //new KeyValuePair(OpenTelemetryAttributeKeys.Consistency, "consistency"), - + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName.Port), + new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes.ConsistencyLevel), new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)attributes.StatusCode), new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, (int)attributes.SubStatusCode) }; @@ -57,7 +57,6 @@ public static void RecordTelemetry(string operationName, string accountName, str CosmosOperationMeter.RecordActualItemCount(Convert.ToInt32(attributes.ItemCount), dimensionsFunc); CosmosOperationMeter.RecordRequestUnit(attributes.RequestCharge.Value, dimensionsFunc); CosmosOperationMeter.RecordRequestLatency(attributes.Diagnostics.GetClientElapsedTime(), dimensionsFunc); - CosmosOperationMeter.RecordActiveInstances(dimensionsFunc); } public static void RecordActualItemCount(int actualItemCount, Func[]> dimensionsFunc) @@ -90,14 +89,38 @@ internal static void RecordRequestLatency(TimeSpan? requestLatency, Func[]> dimensionsFunc) + public static void AddActiveInstance(Uri account) { - if (CosmosOperationMeter.InstanceCountHistogram == null || !CosmosOperationMeter.InstanceCountHistogram.Enabled) + if (CosmosOperationMeter.InstanceCounter == null || !CosmosOperationMeter.InstanceCounter.Enabled) { return; } - CosmosOperationMeter.InstanceCountHistogram.Record(CosmosClient.NumberOfActiveClients, dimensionsFunc()); + Func[]> dimensionsFunc = () => new[] + { + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, "cosmosdb"), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, account.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, account.Port), + }; + + CosmosOperationMeter.InstanceCounter.Add(1, dimensionsFunc()); + } + + public static void DisposeActiveInstance(Uri account) + { + if (CosmosOperationMeter.InstanceCounter == null || !CosmosOperationMeter.InstanceCounter.Enabled) + { + return; + } + + Func[]> dimensionsFunc = () => new[] + { + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, "cosmosdb"), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, account.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, account.Port), + }; + + CosmosOperationMeter.InstanceCounter.Add(-1, dimensionsFunc()); } } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs index 2d01b8864d..109bf4998a 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs @@ -58,6 +58,11 @@ internal sealed class OpenTelemetryAttributeKeys ///
public const string ServerAddress = "server.address"; + /// + /// Represents the server port. + /// + public const string ServerPort = "server.port"; + // Cosmos DB specific attributes /// diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs index f606fef57e..71d0fa2f85 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs @@ -157,6 +157,7 @@ public void Record( this.scope.AddAttribute(OpenTelemetryAttributeKeys.DbName, databaseName); this.scope.AddAttribute(OpenTelemetryAttributeKeys.ContainerName, containerName); this.scope.AddAttribute(OpenTelemetryAttributeKeys.ServerAddress, clientContext.Client?.Endpoint?.Host); + this.scope.AddAttribute(OpenTelemetryAttributeKeys.ServerPort, clientContext.Client?.Endpoint?.Port.ToString()); this.scope.AddAttribute(OpenTelemetryAttributeKeys.UserAgent, clientContext.UserAgent); } else From 7e9a3442a2b3b00a6faae404fbd530fd77194e1b Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Thu, 17 Oct 2024 12:14:29 +0530 Subject: [PATCH 08/49] fixed dimensions --- Microsoft.Azure.Cosmos/src/CosmosClient.cs | 4 - .../src/Microsoft.Azure.Cosmos.csproj | 4 +- .../src/Resource/ClientContextCore.cs | 31 +++++-- .../OpenTelemetry/CosmosOperationMeter.cs | 90 ++++++++----------- .../OpenTelemetryAttributeKeys.cs | 5 ++ .../OpenTelemetryMetricsConstant.cs | 24 +---- 6 files changed, 71 insertions(+), 87 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/CosmosClient.cs b/Microsoft.Azure.Cosmos/src/CosmosClient.cs index 1bb971cdcb..0c0cef4001 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClient.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClient.cs @@ -1424,8 +1424,6 @@ private int IncrementNumberOfClientsCreated() { this.IncrementNumberOfActiveClients(); - CosmosOperationMeter.AddActiveInstance(this.Endpoint); - return Interlocked.Increment(ref numberOfClientsCreated); } @@ -1439,8 +1437,6 @@ private int DecrementNumberOfActiveClients() // In case dispose is called multiple times. Check if at least 1 active client is there if (NumberOfActiveClients > 0) { - CosmosOperationMeter.DisposeActiveInstance(this.Endpoint); - return Interlocked.Decrement(ref NumberOfActiveClients); } diff --git a/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj b/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj index 55b2fba26a..bfe90ace04 100644 --- a/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj +++ b/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj @@ -114,7 +114,7 @@ - + @@ -127,7 +127,7 @@ - + diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index 9401ec8320..cc97f45cf0 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -531,11 +531,11 @@ private async Task RunWithDiagnosticsHelperAsync( OpenTelemetryAttributes response = openTelemetry?.Item2(result); recorder.Record(response); - CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, - accountName: this.client.Endpoint, - containerName: containerName, - databaseName: databaseName, - attributes: response); + CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, + accountName: this.client.Endpoint, + containerName: containerName, + databaseName: databaseName, + attributes: response); } return result; @@ -544,6 +544,11 @@ private async Task RunWithDiagnosticsHelperAsync( { CosmosOperationCanceledException operationCancelledException = new CosmosOperationCanceledException(oe, trace); recorder.MarkFailed(operationCancelledException); + CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, + accountName: this.client.Endpoint, + containerName: containerName, + databaseName: databaseName, + ex: oe); throw operationCancelledException; } @@ -554,6 +559,11 @@ private async Task RunWithDiagnosticsHelperAsync( this.client, trace); recorder.MarkFailed(objectDisposedException); + CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, + accountName: this.client.Endpoint, + containerName: containerName, + databaseName: databaseName, + ex: objectDisposed); throw objectDisposedException; } @@ -563,13 +573,22 @@ private async Task RunWithDiagnosticsHelperAsync( nullRefException, trace); recorder.MarkFailed(nullException); + CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, + accountName: this.client.Endpoint, + containerName: containerName, + databaseName: databaseName, + ex: nullRefException); throw nullException; } catch (Exception ex) { recorder.MarkFailed(ex); - + CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, + accountName: this.client.Endpoint, + containerName: containerName, + databaseName: databaseName, + ex: ex); throw; } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index e45e694e70..6381b581df 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -7,14 +7,12 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System; using System.Collections.Generic; using System.Diagnostics.Metrics; - using System.Xml.Linq; internal static class CosmosOperationMeter { internal static Histogram RequestLatencyHistogram = null; internal static Histogram RequestUnitsHistogram = null; internal static Histogram ActualItemHistogram = null; - internal static UpDownCounter InstanceCounter = null; private static Meter cosmosMeter; @@ -33,27 +31,49 @@ public static void Initialize() CosmosOperationMeter.ActualItemHistogram = cosmosMeter.CreateHistogram(name: OpenTelemetryMetricsConstant.OperationMetrics.Name.RowCount, unit: OpenTelemetryMetricsConstant.OperationMetrics.Unit.Count, description: OpenTelemetryMetricsConstant.OperationMetrics.Description.RowCount); - - CosmosOperationMeter.InstanceCounter = cosmosMeter.CreateUpDownCounter(name: OpenTelemetryMetricsConstant.OperationMetrics.Name.ActiveInstances, - unit: OpenTelemetryMetricsConstant.OperationMetrics.Unit.Count, - description: OpenTelemetryMetricsConstant.OperationMetrics.Description.ActiveInstances); } - public static void RecordTelemetry(string operationName, Uri accountName, string containerName, string databaseName, OpenTelemetryAttributes attributes) + public static void RecordTelemetry(string operationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes = null, + Exception ex = null) { - Func[]> dimensionsFunc = () => new[] + Func[]> dimensionsFunc = () => { - new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, "cosmosdb"), - new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, operationName), - new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName), - new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName.Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName.Port), - new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes.ConsistencyLevel), - new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)attributes.StatusCode), - new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, (int)attributes.SubStatusCode) + List> dimensions = new List>() + { + { new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, "cosmosdb")}, + { new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName)}, + { new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName)}, + { new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName.Host)}, + { new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName.Port)}, + { new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, operationName)} + }; + + if (attributes != null) + { + dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)attributes.StatusCode)); + dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, (int)attributes.SubStatusCode)); + dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes.ConsistencyLevel)); + } + + if (ex != null) + { + if (ex is CosmosException cosmosException) + { + dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)cosmosException.StatusCode)); + dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, (int)cosmosException.SubStatusCode)); + dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, cosmosException.Headers.ConsistencyLevel)); + } + + dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, ex.Message)); + } + + return dimensions.ToArray(); }; - + CosmosOperationMeter.RecordActualItemCount(Convert.ToInt32(attributes.ItemCount), dimensionsFunc); CosmosOperationMeter.RecordRequestUnit(attributes.RequestCharge.Value, dimensionsFunc); CosmosOperationMeter.RecordRequestLatency(attributes.Diagnostics.GetClientElapsedTime(), dimensionsFunc); @@ -88,39 +108,5 @@ internal static void RecordRequestLatency(TimeSpan? requestLatency, Func[]> dimensionsFunc = () => new[] - { - new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, "cosmosdb"), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, account.Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, account.Port), - }; - - CosmosOperationMeter.InstanceCounter.Add(1, dimensionsFunc()); - } - - public static void DisposeActiveInstance(Uri account) - { - if (CosmosOperationMeter.InstanceCounter == null || !CosmosOperationMeter.InstanceCounter.Enabled) - { - return; - } - - Func[]> dimensionsFunc = () => new[] - { - new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, "cosmosdb"), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, account.Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, account.Port), - }; - - CosmosOperationMeter.InstanceCounter.Add(-1, dimensionsFunc()); - } } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs index 109bf4998a..a179374314 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs @@ -163,5 +163,10 @@ internal sealed class OpenTelemetryAttributeKeys /// Represents the stack trace of the exception. /// public const string ExceptionStacktrace = "exception.stacktrace"; + + /// + /// Represents the type of error. + /// + public const string ErrorType = "error.type"; } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs index 506153ec75..3e0878227e 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs @@ -45,12 +45,6 @@ public static class Name /// For feed operations (query, readAll, readMany, change feed) batch operations this meter capture the actual item count in responses from the service. ///
public const string RowCount = "db.client.response.row_count"; - - /// - /// Number of active SDK client instances. - /// - public const string ActiveInstances = "db.cosmosdb.client.active_instances"; - } /// @@ -94,11 +88,6 @@ public static class Description /// Description for the item count metric in responses /// public const string RowCount = "For feed operations (query, readAll, readMany, change feed) batch operations this meter capture the actual item count in responses from the service"; - - /// - /// Description for the active SDK client instances metric - /// - public const string ActiveInstances = "Number of active SDK client instances."; } } @@ -138,18 +127,7 @@ public static class HistogramBuckets /// 2000, 5000: Larger Response Sizes, These boundaries capture scenarios where the query returns large datasets, often used in batch processing or in-depth analytical queries. These larger page sizes can potentially increase memory or CPU usage and may lead to longer query execution times, making it important to track performance in these ranges.

/// 10000: Very Large Response Sizes (Outliers), This boundary is included to capture rare, very large response sizes. Such queries can put significant strain on system resources, including memory, CPU, and network bandwidth, and can often lead to performance issues such as high latency or even network drops. /// - public static readonly double[] RowCountBuckets = new double[] { 10, 50, 100, 250, 500, 1000, 2000, 5000, 10000 }; - - /// - /// ExplicitBucketBoundaries for "db.cosmosdb.client.active_instances" Metrics - /// - /// - /// 1: Single Instance, This bucket represents the ideal scenario where there is only one active instance of the SDK client per process. Monitoring this level helps confirm that best practices are being followed.

- /// 2, 3, 5: Low Multi-Instance Usage, These buckets capture scenarios where there are a small number of instances (2 to 5). Monitoring these levels is important as having a couple of instances may be necessary for certain applications, but it can also start to introduce resource contention. Identifying any performance degradation at this level can help detect early signs of potential issues.

- /// 10, 20: Moderate Multi-Instance Usage, These boundaries capture situations where there are more than a few instances of the SDK client. As the number of active instances increases, the risk of CPU and memory-related issues also rises. Tracking these ranges can provide insight into whether the application is scaling appropriately or if optimizations are required.

- /// 50, 100: These buckets account for scenarios where the number of active instances is significant. Monitoring these higher values is critical, as having many instances may lead to severe performance impacts, including increased latency and resource exhaustion. This can help identify applications that may be misconfigured or operating beyond their intended limits. - ///
- public static readonly double[] ActiveInstancesBuckets = new double[] { 1, 2, 3, 5, 10, 50, 100 }; + public static readonly double[] RowCountBuckets = new double[] { 1, 10, 50, 100, 250, 500, 1000, 2000, 5000, 10000 }; } } } From bc6f27346fbaf21eb3ff2a738da15d44d28c3f23 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Thu, 17 Oct 2024 21:59:58 +0530 Subject: [PATCH 09/49] nonworking changes --- .../src/Resource/ClientContextCore.cs | 27 +---- ...nt.cs => CosmosDbClientMetricsConstant.cs} | 4 +- .../OpenTelemetry/CosmosOperationMeter.cs | 44 +++++--- .../Metrics/CustomMetricExporter.cs | 50 +++------ .../Metrics/OpenTelemetryMetricsTest.cs | 106 +++++++++--------- 5 files changed, 108 insertions(+), 123 deletions(-) rename Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/{OpenTelemetryMetricsConstant.cs => CosmosDbClientMetricsConstant.cs} (98%) diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index cc97f45cf0..c2345c2220 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -525,7 +525,7 @@ private async Task RunWithDiagnosticsHelperAsync( try { TResult result = await task(trace).ConfigureAwait(false); - if (openTelemetry != null && recorder.IsEnabled) + if (openTelemetry != null) { // Record request response information OpenTelemetryAttributes response = openTelemetry?.Item2(result); @@ -544,12 +544,7 @@ private async Task RunWithDiagnosticsHelperAsync( { CosmosOperationCanceledException operationCancelledException = new CosmosOperationCanceledException(oe, trace); recorder.MarkFailed(operationCancelledException); - CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, - accountName: this.client.Endpoint, - containerName: containerName, - databaseName: databaseName, - ex: oe); - + throw operationCancelledException; } catch (ObjectDisposedException objectDisposed) when (!(objectDisposed is CosmosObjectDisposedException)) @@ -559,11 +554,6 @@ private async Task RunWithDiagnosticsHelperAsync( this.client, trace); recorder.MarkFailed(objectDisposedException); - CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, - accountName: this.client.Endpoint, - containerName: containerName, - databaseName: databaseName, - ex: objectDisposed); throw objectDisposedException; } @@ -573,11 +563,6 @@ private async Task RunWithDiagnosticsHelperAsync( nullRefException, trace); recorder.MarkFailed(nullException); - CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, - accountName: this.client.Endpoint, - containerName: containerName, - databaseName: databaseName, - ex: nullRefException); throw nullException; } @@ -585,10 +570,10 @@ private async Task RunWithDiagnosticsHelperAsync( { recorder.MarkFailed(ex); CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, - accountName: this.client.Endpoint, - containerName: containerName, - databaseName: databaseName, - ex: ex); + accountName: this.client.Endpoint, + containerName: containerName, + databaseName: databaseName, + ex: ex); throw; } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetricsConstant.cs similarity index 98% rename from Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs rename to Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetricsConstant.cs index 3e0878227e..97ffbe2c26 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryMetricsConstant.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetricsConstant.cs @@ -5,11 +5,11 @@ namespace Microsoft.Azure.Cosmos { /// - /// The OpenTelemetryMetricsConstant class provides constants related to OpenTelemetry metrics for Azure Cosmos DB. + /// The CosmosDbClientMetricsConstant class provides constants related to OpenTelemetry metrics for Azure Cosmos DB. /// These metrics are useful for tracking various aspects of Cosmos DB client operations and compliant with Open Telemetry Semantic Conventions /// It defines standardized names, units, descriptions, and histogram buckets for measuring and monitoring performance through OpenTelemetry. /// - public sealed class OpenTelemetryMetricsConstant + public sealed class CosmosDbClientMetricsConstant { /// /// OperationMetrics diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index 6381b581df..65b1e994fc 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -14,23 +14,23 @@ internal static class CosmosOperationMeter internal static Histogram RequestUnitsHistogram = null; internal static Histogram ActualItemHistogram = null; - private static Meter cosmosMeter; + internal static Meter OperationMeter; public static void Initialize() { - cosmosMeter ??= new Meter(OpenTelemetryMetricsConstant.OperationMetrics.MeterName, OpenTelemetryMetricsConstant.OperationMetrics.Version); + OperationMeter ??= new Meter(CosmosDbClientMetricsConstant.OperationMetrics.MeterName, CosmosDbClientMetricsConstant.OperationMetrics.Version); - CosmosOperationMeter.RequestLatencyHistogram = cosmosMeter.CreateHistogram(name: OpenTelemetryMetricsConstant.OperationMetrics.Name.Latency, - unit: OpenTelemetryMetricsConstant.OperationMetrics.Unit.Sec, - description: OpenTelemetryMetricsConstant.OperationMetrics.Description.Latency); + CosmosOperationMeter.RequestLatencyHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetricsConstant.OperationMetrics.Name.Latency, + unit: CosmosDbClientMetricsConstant.OperationMetrics.Unit.Sec, + description: CosmosDbClientMetricsConstant.OperationMetrics.Description.Latency); - CosmosOperationMeter.RequestUnitsHistogram = cosmosMeter.CreateHistogram(name: OpenTelemetryMetricsConstant.OperationMetrics.Name.RequestCharge, - unit: OpenTelemetryMetricsConstant.OperationMetrics.Unit.RequestUnit, - description: OpenTelemetryMetricsConstant.OperationMetrics.Description.RequestCharge); + CosmosOperationMeter.RequestUnitsHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetricsConstant.OperationMetrics.Name.RequestCharge, + unit: CosmosDbClientMetricsConstant.OperationMetrics.Unit.RequestUnit, + description: CosmosDbClientMetricsConstant.OperationMetrics.Description.RequestCharge); - CosmosOperationMeter.ActualItemHistogram = cosmosMeter.CreateHistogram(name: OpenTelemetryMetricsConstant.OperationMetrics.Name.RowCount, - unit: OpenTelemetryMetricsConstant.OperationMetrics.Unit.Count, - description: OpenTelemetryMetricsConstant.OperationMetrics.Description.RowCount); + CosmosOperationMeter.ActualItemHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetricsConstant.OperationMetrics.Name.RowCount, + unit: CosmosDbClientMetricsConstant.OperationMetrics.Unit.Count, + description: CosmosDbClientMetricsConstant.OperationMetrics.Description.RowCount); } public static void RecordTelemetry(string operationName, @@ -40,6 +40,8 @@ public static void RecordTelemetry(string operationName, OpenTelemetryAttributes attributes = null, Exception ex = null) { + Console.WriteLine($"Recording telemetry for operation: {operationName}"); + Func[]> dimensionsFunc = () => { List> dimensions = new List>() @@ -73,10 +75,20 @@ public static void RecordTelemetry(string operationName, return dimensions.ToArray(); }; - - CosmosOperationMeter.RecordActualItemCount(Convert.ToInt32(attributes.ItemCount), dimensionsFunc); - CosmosOperationMeter.RecordRequestUnit(attributes.RequestCharge.Value, dimensionsFunc); - CosmosOperationMeter.RecordRequestLatency(attributes.Diagnostics.GetClientElapsedTime(), dimensionsFunc); + + if (attributes != null) + { + CosmosOperationMeter.RecordActualItemCount(Convert.ToInt32(attributes.ItemCount), dimensionsFunc); + CosmosOperationMeter.RecordRequestUnit(attributes.RequestCharge.Value, dimensionsFunc); + CosmosOperationMeter.RecordRequestLatency(attributes.Diagnostics.GetClientElapsedTime(), dimensionsFunc); + } + + if (ex != null && ex is CosmosException cosmosException) + { + CosmosOperationMeter.RecordActualItemCount(Convert.ToInt32(cosmosException.Headers.ItemCount), dimensionsFunc); + CosmosOperationMeter.RecordRequestUnit(cosmosException.Headers.RequestCharge, dimensionsFunc); + CosmosOperationMeter.RecordRequestLatency(cosmosException.Diagnostics.GetClientElapsedTime(), dimensionsFunc); + } } public static void RecordActualItemCount(int actualItemCount, Func[]> dimensionsFunc) @@ -96,7 +108,7 @@ internal static void RecordRequestUnit(double requestCharge, Func[]> dimensionsFunc) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs index 6e2e022591..099a516f10 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs @@ -6,56 +6,38 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics { using OpenTelemetry.Metrics; using OpenTelemetry; - using System; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Linq; + using System.Threading; + using System; public class CustomMetricExporter : BaseExporter { - private static readonly Dictionary expectedMetrics = new Dictionary() + private readonly ManualResetEventSlim manualResetEventSlim = null; + + public static Dictionary ActualMetrics = new Dictionary(); + + public CustomMetricExporter(ManualResetEventSlim manualResetEventSlim) { - { "db.client.operation.duration", MetricType.Histogram }, - { "db.client.response.row_count", MetricType.Histogram}, - { "db.cosmosdb.operation.request_charge", MetricType.Histogram }, - { "db.cosmosdb.client.active_instances", MetricType.Histogram }, - }; + this.manualResetEventSlim = manualResetEventSlim; + } // This method will be called periodically by OpenTelemetry SDK public override ExportResult Export(in Batch batch) { - Console.WriteLine("\n[Custom Exporter] Exporting metrics:"); - - Dictionary actualMetrics = new Dictionary(); + Console.WriteLine("Exporting metrics..."); foreach (Metric metric in batch) { - actualMetrics.Add(metric.Name, metric.MetricType); - - Console.WriteLine($"Metric: {metric.Name}, Type: {metric.MetricType}"); - foreach (MetricPoint metricPoint in metric.GetMetricPoints()) - { - switch (metric.MetricType) - { - case MetricType.LongSum: - Console.WriteLine($"Value (Counter): {metricPoint.GetSumLong()}"); - break; - case MetricType.LongGauge: - Console.WriteLine($"Value (Gauge): {metricPoint.GetGaugeLastValueLong()}"); - break; - case MetricType.Histogram: - Console.WriteLine($"Value (Histogram): {metricPoint.GetHistogramCount()}"); - - foreach (HistogramBucket bucket in metricPoint.GetHistogramBuckets()) - { - Console.WriteLine($"{bucket.ExplicitBound} : {bucket.BucketCount}"); - } - break; - } - } + ActualMetrics.Add(metric.Name, metric.MetricType); } - Assert.IsTrue(expectedMetrics.Count == actualMetrics.Count && !expectedMetrics.Except(actualMetrics).Any()); + if (ActualMetrics.Count > 0) + { + this.manualResetEventSlim.Set(); + } + Console.WriteLine($"Metrics Count {ActualMetrics.Count}"); return ExportResult.Success; } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs index 7154f6c4ee..7be3d0f642 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -9,13 +9,30 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenTelemetry.Metrics; using OpenTelemetry; - using System.Diagnostics; using OpenTelemetry.Resources; - using Microsoft.Azure.Cosmos.Telemetry; + using System.Threading; + using System.Diagnostics; + using System.Collections.Generic; + using Microsoft.Extensions.Options; [TestClass] public class OpenTelemetryMetricsTest : BaseCosmosClientHelper { + private const int AggregatingInterval = 1000; + private readonly ManualResetEventSlim manualResetEventSlim = new ManualResetEventSlim(false); + private static readonly Dictionary expectedMetrics = new Dictionary() + { + { "db.client.operation.duration", MetricType.Histogram }, + { "db.client.response.row_count", MetricType.Histogram}, + { "db.cosmosdb.operation.request_charge", MetricType.Histogram } + }; + + [TestInitialize] + public async Task Init() + { + await base.TestInit(); + } + [TestCleanup] public async Task Cleanup() { @@ -23,40 +40,36 @@ public async Task Cleanup() } [TestMethod] - public async Task OperationLevelMetrics() + public async Task OperationLevelMetricsGenerationTest() { - Console.WriteLine("Start => " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + // Initialize OpenTelemetry MeterProvider MeterProvider meterProvider = Sdk .CreateMeterProviderBuilder() - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("OperationLevelMetrics")) - .AddMeter("Azure.Cosmos.Client.Operation") - .AddView(instrumentName: OpenTelemetryMetricsConstant.OperationMetrics.Name.RequestCharge, new ExplicitBucketHistogramConfiguration // Define histogram buckets - { - Boundaries = OpenTelemetryMetricsConstant.HistogramBuckets.RequestUnitBuckets - }) - .AddView(instrumentName: OpenTelemetryMetricsConstant.OperationMetrics.Name.Latency, new ExplicitBucketHistogramConfiguration // Define histogram buckets - { - Boundaries = OpenTelemetryMetricsConstant.HistogramBuckets.RequestLatencyBuckets - }) - .AddView(instrumentName: OpenTelemetryMetricsConstant.OperationMetrics.Name.RowCount, new ExplicitBucketHistogramConfiguration // Define histogram buckets - { - Boundaries = OpenTelemetryMetricsConstant.HistogramBuckets.RowCountBuckets - }) - .AddView(instrumentName: OpenTelemetryMetricsConstant.OperationMetrics.Name.ActiveInstances, new ExplicitBucketHistogramConfiguration // Define histogram buckets - { - Boundaries = OpenTelemetryMetricsConstant.HistogramBuckets.ActiveInstancesBuckets - }) - /*.AddOtlpExporter((exporterOptions, metricReaderOptions) => - { - exporterOptions.Endpoint = new Uri("http://localhost:9090/api/v1/otlp/v1/metrics"); - exporterOptions.Protocol = OtlpExportProtocol.HttpProtobuf; - metricReaderOptions.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = 1000; - })*/ + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Azure Cosmos DB Operation Level Metrics")) + .AddMeter(CosmosDbClientMetricsConstant.OperationMetrics.MeterName) + .AddView( + instrumentName: CosmosDbClientMetricsConstant.OperationMetrics.Name.RequestCharge, + metricStreamConfiguration: new ExplicitBucketHistogramConfiguration + { + Boundaries = CosmosDbClientMetricsConstant.HistogramBuckets.RequestUnitBuckets + }) + .AddView( + instrumentName: CosmosDbClientMetricsConstant.OperationMetrics.Name.Latency, + metricStreamConfiguration: new ExplicitBucketHistogramConfiguration + { + Boundaries = CosmosDbClientMetricsConstant.HistogramBuckets.RequestLatencyBuckets + }) + .AddView( + instrumentName: CosmosDbClientMetricsConstant.OperationMetrics.Name.RowCount, + metricStreamConfiguration: new ExplicitBucketHistogramConfiguration + { + Boundaries = CosmosDbClientMetricsConstant.HistogramBuckets.RowCountBuckets + }) .AddConsoleExporter() - //.AddAzureMonitorMetricExporter( o => o.ConnectionString = "") - //.AddReader(new PeriodicExportingMetricReader(new CustomMetricExporter(), exportIntervalMilliseconds: 1000)) + //.AddReader(new PeriodicExportingMetricReader(exporter: new CustomMetricExporter(this.manualResetEventSlim), exportIntervalMilliseconds: AggregatingInterval)) .Build(); + // Intialize CosmosClient with Client Metrics enabled (string endpoint, string authKey) = TestCommon.GetAccountInfo(); CosmosClientOptions cosmosClientOptions = new CosmosClientOptions { @@ -65,20 +78,13 @@ public async Task OperationLevelMetrics() IsClientMetricsEnabled = true } }; - this.SetClient(new CosmosClient(endpoint, authKey, cosmosClientOptions)); - Database database = await this.GetClient().CreateDatabaseIfNotExistsAsync(Guid.NewGuid().ToString()); - Container container = await database.CreateContainerIfNotExistsAsync(Guid.NewGuid().ToString(), "/pk", throughput: 10000); - - Stopwatch sw = Stopwatch.StartNew(); - sw.Start(); - int counter = 1; - while(true) + Container container = await this.database.CreateContainerIfNotExistsAsync(Guid.NewGuid().ToString(), "/pk", throughput: 10000); + for (int count = 0; count < 10; count++) { string randomId = Guid.NewGuid().ToString(); string pk = "Status1"; - await container.CreateItemAsync(ToDoActivity.CreateRandomToDoActivity(id: randomId, pk: pk)); await container.ReadItemAsync(randomId, new PartitionKey(pk)); @@ -86,25 +92,25 @@ public async Task OperationLevelMetrics() { MaxItemCount = Random.Shared.Next(1, 100) }; - FeedIterator feedIterator = container.GetItemQueryIterator("SELECT * FROM c", requestOptions: queryRequestOptions); + FeedIterator feedIterator = container.GetItemQueryIterator(queryText: "SELECT * FROM c", requestOptions: queryRequestOptions); while (feedIterator.HasMoreResults) { - FeedResponse toDoActivities = await feedIterator.ReadNextAsync(); - Console.WriteLine($"{counter++} : Read {toDoActivities.Count} items"); - } - - if (sw.ElapsedMilliseconds > TimeSpan.FromSeconds(1).TotalMilliseconds) - { - break; + await feedIterator.ReadNextAsync(); } } - sw.Stop(); meterProvider.Dispose(); - await Task.Delay(TimeSpan.FromSeconds(1)); + await Task.Delay(1000); + + Console.WriteLine("Disposed............"); + //while (!this.manualResetEventSlim.IsSet) + //{ + // Console.WriteLine(this.manualResetEventSlim.IsSet); + //} - Console.WriteLine("End => " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + //Console.WriteLine("Asserting............"); + //Assert.AreEqual(CustomMetricExporter.ActualMetrics.Count, expectedMetrics.Count); } } } From 278c5c5befae3bc62d2aa51eeaf003709b1f1ed5 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 18 Oct 2024 08:19:49 +0530 Subject: [PATCH 10/49] final commit --- Microsoft.Azure.Cosmos/src/CosmosClient.cs | 2 + Microsoft.Azure.Cosmos/src/DocumentClient.cs | 2 + .../src/Resource/ClientContextCore.cs | 7 +- .../CosmosDbClientMetricsConstant.cs | 10 ++ .../OpenTelemetry/CosmosOperationMeter.cs | 114 ++++++++++++++++-- .../OpenTelemetryCoreRecorder.cs | 2 +- .../Metrics/CustomMetricExporter.cs | 41 ++++++- .../Metrics/OpenTelemetryMetricsTest.cs | 35 ++++-- ...icrosoft.Azure.Cosmos.EmulatorTests.csproj | 3 + 9 files changed, 188 insertions(+), 28 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/CosmosClient.cs b/Microsoft.Azure.Cosmos/src/CosmosClient.cs index 0c0cef4001..bbe53742c7 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClient.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClient.cs @@ -1437,6 +1437,8 @@ private int DecrementNumberOfActiveClients() // In case dispose is called multiple times. Check if at least 1 active client is there if (NumberOfActiveClients > 0) { + CosmosOperationMeter.RemoveInstanceCount(this.Endpoint); + return Interlocked.Decrement(ref NumberOfActiveClients); } diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index f24ed27dd5..bd29440d12 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -957,6 +957,8 @@ internal virtual void Initialize(Uri serviceEndpoint, if (this.cosmosClientTelemetryOptions.IsClientMetricsEnabled) { CosmosOperationMeter.Initialize(); + + CosmosOperationMeter.AddInstanceCount(this.ServiceEndpoint); } // Starting ClientTelemetry Job diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index c2345c2220..2dd699e73c 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -525,12 +525,16 @@ private async Task RunWithDiagnosticsHelperAsync( try { TResult result = await task(trace).ConfigureAwait(false); + // Checks if OpenTelemetry is configured for this operation. if (openTelemetry != null) { - // Record request response information + // Extracts and records telemetry data from the result of the operation. OpenTelemetryAttributes response = openTelemetry?.Item2(result); + + // Records the telemetry attributes for Distributed Tracing (if enabled) recorder.Record(response); + // Records metrics such as request units, latency, and item count for the operation. CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, accountName: this.client.Endpoint, containerName: containerName, @@ -569,6 +573,7 @@ private async Task RunWithDiagnosticsHelperAsync( catch (Exception ex) { recorder.MarkFailed(ex); + // Records telemetry data related to the exception. CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, accountName: this.client.Endpoint, containerName: containerName, diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetricsConstant.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetricsConstant.cs index 97ffbe2c26..adc35e7e45 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetricsConstant.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetricsConstant.cs @@ -45,6 +45,11 @@ public static class Name /// For feed operations (query, readAll, readMany, change feed) batch operations this meter capture the actual item count in responses from the service. /// public const string RowCount = "db.client.response.row_count"; + + /// + /// Number of active SDK client instances. + /// + public const string ActiveInstances = "db.cosmosdb.client.active_instances"; } /// @@ -88,6 +93,11 @@ public static class Description /// Description for the item count metric in responses /// public const string RowCount = "For feed operations (query, readAll, readMany, change feed) batch operations this meter capture the actual item count in responses from the service"; + + /// + /// Description for the active SDK client instances metric + /// + public const string ActiveInstances = "Number of active SDK client instances."; } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index 65b1e994fc..0e4cdc97d2 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -8,18 +8,42 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System.Collections.Generic; using System.Diagnostics.Metrics; + /// + /// CosmosOperationMeter is a utility class responsible for collecting and recording telemetry metrics related to Cosmos DB operations. + /// It includes histograms and counters that capture metrics like request latency, request units, item count, and active instances. + /// internal static class CosmosOperationMeter { + /// + /// Meter instance for capturing various metrics related to Cosmos DB operations. + /// + internal static Meter OperationMeter = new Meter(CosmosDbClientMetricsConstant.OperationMetrics.MeterName, CosmosDbClientMetricsConstant.OperationMetrics.Version); + + /// + /// Histogram to record request latency (in seconds) for Cosmos DB operations. + /// internal static Histogram RequestLatencyHistogram = null; + + /// + /// Histogram to record request units consumed during Cosmos DB operations. + /// internal static Histogram RequestUnitsHistogram = null; + + /// + /// Histogram to record the actual number of items involved in the operation. + /// internal static Histogram ActualItemHistogram = null; - internal static Meter OperationMeter; + /// + /// UpDownCounter to track the number of active instances interacting with Cosmos DB. + /// + internal static UpDownCounter ActiveInstanceCounter = null; - public static void Initialize() + /// + /// Initializes the histograms and counters for capturing Cosmos DB metrics. + /// + internal static void Initialize() { - OperationMeter ??= new Meter(CosmosDbClientMetricsConstant.OperationMetrics.MeterName, CosmosDbClientMetricsConstant.OperationMetrics.Version); - CosmosOperationMeter.RequestLatencyHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetricsConstant.OperationMetrics.Name.Latency, unit: CosmosDbClientMetricsConstant.OperationMetrics.Unit.Sec, description: CosmosDbClientMetricsConstant.OperationMetrics.Description.Latency); @@ -31,22 +55,33 @@ public static void Initialize() CosmosOperationMeter.ActualItemHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetricsConstant.OperationMetrics.Name.RowCount, unit: CosmosDbClientMetricsConstant.OperationMetrics.Unit.Count, description: CosmosDbClientMetricsConstant.OperationMetrics.Description.RowCount); + + CosmosOperationMeter.ActiveInstanceCounter ??= OperationMeter.CreateUpDownCounter(name: CosmosDbClientMetricsConstant.OperationMetrics.Name.ActiveInstances, + unit: CosmosDbClientMetricsConstant.OperationMetrics.Unit.Count, + description: CosmosDbClientMetricsConstant.OperationMetrics.Description.ActiveInstances); } - public static void RecordTelemetry(string operationName, + /// + /// Records telemetry data related to Cosmos DB operations. This includes request latency, request units, and item counts. + /// + /// Name of the operation being performed. + /// The URI of the Cosmos DB account. + /// The name of the container involved in the operation. + /// The name of the database involved in the operation. + /// Optional OpenTelemetry attributes related to the operation. + /// Optional exception object to capture error details. + internal static void RecordTelemetry(string operationName, Uri accountName, string containerName, string databaseName, OpenTelemetryAttributes attributes = null, Exception ex = null) { - Console.WriteLine($"Recording telemetry for operation: {operationName}"); - Func[]> dimensionsFunc = () => { List> dimensions = new List>() { - { new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, "cosmosdb")}, + { new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb)}, { new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName)}, { new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName)}, { new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName.Host)}, @@ -91,7 +126,12 @@ public static void RecordTelemetry(string operationName, } } - public static void RecordActualItemCount(int actualItemCount, Func[]> dimensionsFunc) + /// + /// Records the actual item count metric for a Cosmos DB operation. + /// + /// The number of items returned or affected by the operation. + /// A function providing telemetry dimensions for the metric. + internal static void RecordActualItemCount(int actualItemCount, Func[]> dimensionsFunc) { if (CosmosOperationMeter.ActualItemHistogram == null || !CosmosOperationMeter.ActualItemHistogram.Enabled) { @@ -101,6 +141,11 @@ public static void RecordActualItemCount(int actualItemCount, Func + /// Records the request units (RU/s) consumed by the operation. + /// + /// The RU/s value for the operation. + /// A function providing telemetry dimensions for the metric. internal static void RecordRequestUnit(double requestCharge, Func[]> dimensionsFunc) { if (CosmosOperationMeter.RequestUnitsHistogram == null || !CosmosOperationMeter.RequestUnitsHistogram.Enabled) @@ -111,14 +156,61 @@ internal static void RecordRequestUnit(double requestCharge, Func + /// Records the latency (in seconds) for a Cosmos DB operation. + /// + /// The latency of the operation. + /// A function providing telemetry dimensions for the metric. internal static void RecordRequestLatency(TimeSpan? requestLatency, Func[]> dimensionsFunc) { - if (CosmosOperationMeter.RequestLatencyHistogram == null || !CosmosOperationMeter.RequestLatencyHistogram.Enabled || !requestLatency.HasValue) + if (CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled) + { + return; + } + + CosmosOperationMeter.RequestLatencyHistogram.Record(requestLatency.Value.TotalMilliseconds / 1000, dimensionsFunc()); + } + + /// + /// Increases the count of active Cosmos DB instances. + /// + /// The URI of the account endpoint. + internal static void AddInstanceCount(Uri accountEndpoint) + { + if (CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled) + { + return; + } + + KeyValuePair[] dimensions = new[] + { + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountEndpoint.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) + }; + + CosmosOperationMeter.ActiveInstanceCounter.Add(1, dimensions); + } + + /// + /// Decreases the count of active Cosmos DB instances. + /// + /// The URI of the account endpoint. + internal static void RemoveInstanceCount(Uri accountEndpoint) + { + if (CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled) { return; } - CosmosOperationMeter.RequestLatencyHistogram.Record(requestLatency.Value.Milliseconds, dimensionsFunc()); + KeyValuePair[] dimensions = new[] + { + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountEndpoint.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) + }; + + CosmosOperationMeter.ActiveInstanceCounter.Add(-1, dimensions); } } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs index 71d0fa2f85..85b5d58352 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs @@ -17,7 +17,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry /// internal struct OpenTelemetryCoreRecorder : IDisposable { - private const string CosmosDb = "cosmosdb"; + internal const string CosmosDb = "cosmosdb"; private static readonly string otelStabilityMode = Environment.GetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN"); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs index 099a516f10..19771a02af 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs @@ -7,8 +7,6 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics using OpenTelemetry.Metrics; using OpenTelemetry; using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using System.Linq; using System.Threading; using System; @@ -29,7 +27,46 @@ public override ExportResult Export(in Batch batch) Console.WriteLine("Exporting metrics..."); foreach (Metric metric in batch) { + Console.WriteLine($"Metric Name: {metric.Name}, Metric Type: {metric.MetricType}"); ActualMetrics.Add(metric.Name, metric.MetricType); + + // Iterate over the data points for this metric + foreach (MetricPoint metricPoint in metric.GetMetricPoints()) + { + Console.WriteLine($" DataPoint - StartTime: {metricPoint.StartTime}, EndTime: {metricPoint.EndTime}"); + Console.WriteLine($" Attributes:"); + + foreach (KeyValuePair attribute in metricPoint.Tags) + { + Console.WriteLine($" {attribute.Key}: {attribute.Value}"); + } + + // Print different data depending on the MetricType + switch (metric.MetricType) + { + case MetricType.LongSum: + case MetricType.DoubleSum: + Console.WriteLine($" Sum: {metricPoint.GetSumLong()}{metricPoint.GetSumDouble()}"); + break; + case MetricType.LongGauge: + case MetricType.DoubleGauge: + Console.WriteLine($" Gauge: {metricPoint.GetGaugeLastValueLong()}{metricPoint.GetGaugeLastValueDouble()}"); + break; + case MetricType.LongSumNonMonotonic: // Non-monotonic sums for UpDownCounter + Console.WriteLine($" Non-Monotonic Long Sum: {metricPoint.GetSumLong()}"); + break; + case MetricType.DoubleSumNonMonotonic: // Non-monotonic sums for UpDownCounter (double version) + Console.WriteLine($" Non-Monotonic Double Sum: {metricPoint.GetSumDouble()}"); + break; + case MetricType.Histogram: + Console.WriteLine($" Histogram - Count: {metricPoint.GetHistogramCount()}, Sum: {metricPoint.GetHistogramSum()}"); + break; + default: + Console.WriteLine(" Unknown metric type."); + break; + } + } + } if (ActualMetrics.Count > 0) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs index 7be3d0f642..bf732c43e0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -11,9 +11,8 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics using OpenTelemetry; using OpenTelemetry.Resources; using System.Threading; - using System.Diagnostics; using System.Collections.Generic; - using Microsoft.Extensions.Options; + using System.Linq; [TestClass] public class OpenTelemetryMetricsTest : BaseCosmosClientHelper @@ -24,7 +23,8 @@ public class OpenTelemetryMetricsTest : BaseCosmosClientHelper { { "db.client.operation.duration", MetricType.Histogram }, { "db.client.response.row_count", MetricType.Histogram}, - { "db.cosmosdb.operation.request_charge", MetricType.Histogram } + { "db.cosmosdb.operation.request_charge", MetricType.Histogram }, + { "db.cosmosdb.client.active_instances", MetricType.LongSumNonMonotonic } }; [TestInitialize] @@ -65,8 +65,7 @@ public async Task OperationLevelMetricsGenerationTest() { Boundaries = CosmosDbClientMetricsConstant.HistogramBuckets.RowCountBuckets }) - .AddConsoleExporter() - //.AddReader(new PeriodicExportingMetricReader(exporter: new CustomMetricExporter(this.manualResetEventSlim), exportIntervalMilliseconds: AggregatingInterval)) + .AddReader(new PeriodicExportingMetricReader(exporter: new CustomMetricExporter(this.manualResetEventSlim), exportIntervalMilliseconds: AggregatingInterval)) .Build(); // Intialize CosmosClient with Client Metrics enabled @@ -101,16 +100,26 @@ public async Task OperationLevelMetricsGenerationTest() meterProvider.Dispose(); - await Task.Delay(1000); + while (!this.manualResetEventSlim.IsSet) + { + } + + Assert.IsTrue(IsEqual(expectedMetrics, CustomMetricExporter.ActualMetrics), string.Join(", ", CustomMetricExporter.ActualMetrics.Select(kv => $"{kv.Key}: {kv.Value}"))); + } - Console.WriteLine("Disposed............"); - //while (!this.manualResetEventSlim.IsSet) - //{ - // Console.WriteLine(this.manualResetEventSlim.IsSet); - //} + public static bool IsEqual(Dictionary expected, Dictionary actual) + { + if (expected.Count != actual.Count) + return false; + + // Compare both keys and values + foreach (KeyValuePair pair in expected) + { + if (!actual.TryGetValue(pair.Key, out TValue value) || !EqualityComparer.Default.Equals(pair.Value, value)) + return false; + } - //Console.WriteLine("Asserting............"); - //Assert.AreEqual(CustomMetricExporter.ActualMetrics.Count, expectedMetrics.Count); + return true; } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index d670bdcca8..15a0e404f4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -352,10 +352,13 @@ Documents\MillionSong1KDocuments.json PreserveNewest +<<<<<<< HEAD Documents\text-3properties-1536dimensions-100documents.json PreserveNewest +======= +>>>>>>> 4e4d7223e (final commit) From 8bae2a9188cd3fff58fc290401a727fb610289bd Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 18 Oct 2024 08:22:32 +0530 Subject: [PATCH 11/49] remove unnecessary dependencies --- .../Microsoft.Azure.Cosmos.EmulatorTests.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 15a0e404f4..d670bdcca8 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -352,13 +352,10 @@ Documents\MillionSong1KDocuments.json PreserveNewest -<<<<<<< HEAD Documents\text-3properties-1536dimensions-100documents.json PreserveNewest -======= ->>>>>>> 4e4d7223e (final commit) From cebace343c8d932244a86e1822f0605091a252ab Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 18 Oct 2024 10:58:39 +0530 Subject: [PATCH 12/49] contract update --- ...icrosoft.Azure.Cosmos.EmulatorTests.csproj | 2 +- .../Contracts/DotNetSDKAPI.json | 1112 ++++++++--------- 2 files changed, 552 insertions(+), 562 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index d670bdcca8..1382c54d37 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -34,7 +34,7 @@ - + diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 58aef55f21..af4dea3f73 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -3304,6 +3304,353 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+HistogramBuckets": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Void .ctor()": { + "Type": "Constructor", + "Attributes": [], + "MethodInfo": "[Void .ctor(), Void .ctor()]" + } + }, + "NestedTypes": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+HistogramBuckets;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Double[] RequestLatencyBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] RequestLatencyBuckets;IsInitOnly:True;IsStatic:True;" + }, + "Double[] RequestUnitBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] RequestUnitBuckets;IsInitOnly:True;IsStatic:True;" + }, + "Double[] RowCountBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] RowCountBuckets;IsInitOnly:True;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Description": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Name": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Unit": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "System.String MeterName": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String MeterName;IsInitOnly:False;IsStatic:True;" + }, + "System.String Version": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Version;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String ActiveInstances": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestCharge": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + }, + "System.String RowCount": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String ActiveInstances": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestCharge": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + }, + "System.String RowCount": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String Count": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Count;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestUnit": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestUnit;IsInitOnly:False;IsStatic:True;" + }, + "System.String Sec": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Sec;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + } + } + } + } + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+HistogramBuckets;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Double[] RequestLatencyBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] RequestLatencyBuckets;IsInitOnly:True;IsStatic:True;" + }, + "Double[] RequestUnitBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] RequestUnitBuckets;IsInitOnly:True;IsStatic:True;" + }, + "Double[] RowCountBuckets": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Double[] RowCountBuckets;IsInitOnly:True;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Description": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Name": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Unit": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "System.String MeterName": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String MeterName;IsInitOnly:False;IsStatic:True;" + }, + "System.String Version": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Version;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String ActiveInstances": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestCharge": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + }, + "System.String RowCount": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String ActiveInstances": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestCharge": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + }, + "System.String RowCount": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String Count": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Count;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestUnit": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestUnit;IsInitOnly:False;IsStatic:True;" + }, + "System.String Sec": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Sec;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + } + } + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String ActiveInstances": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestCharge": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + }, + "System.String RowCount": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String ActiveInstances": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestCharge": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + }, + "System.String RowCount": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String Count": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Count;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestUnit": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestUnit;IsInitOnly:False;IsStatic:True;" + }, + "System.String Sec": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Sec;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.CosmosDiagnostics;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { @@ -10808,664 +11155,307 @@ "Attributes": [], "MethodInfo": "Boolean Equals(System.Object);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Int32 GetHashCode()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Int32 GetHashCode();IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Microsoft.Azure.Cosmos.Spatial.Position get_Position()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Microsoft.Azure.Cosmos.Spatial.Position get_Position();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Microsoft.Azure.Cosmos.Spatial.Position Position[System.Runtime.Serialization.DataMemberAttribute(Name = \"coordinates\")]-[Newtonsoft.Json.JsonPropertyAttribute(\"coordinates\", Order = 1, Required = 2)]": { - "Type": "Property", - "Attributes": [ - "DataMemberAttribute", - "JsonPropertyAttribute" - ], - "MethodInfo": "Microsoft.Azure.Cosmos.Spatial.Position Position;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.Spatial.Position get_Position();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Void .ctor(Double, Double)": { - "Type": "Constructor", - "Attributes": [], - "MethodInfo": "[Void .ctor(Double, Double), Void .ctor(Double, Double)]" - }, - "Void .ctor(Microsoft.Azure.Cosmos.Spatial.Position, Microsoft.Azure.Cosmos.Spatial.GeometryParams)": { - "Type": "Constructor", - "Attributes": [], - "MethodInfo": "[Void .ctor(Microsoft.Azure.Cosmos.Spatial.Position, Microsoft.Azure.Cosmos.Spatial.GeometryParams), Void .ctor(Microsoft.Azure.Cosmos.Spatial.Position, Microsoft.Azure.Cosmos.Spatial.GeometryParams)]" - }, - "Void .ctor(Microsoft.Azure.Cosmos.Spatial.Position)": { - "Type": "Constructor", - "Attributes": [], - "MethodInfo": "[Void .ctor(Microsoft.Azure.Cosmos.Spatial.Position), Void .ctor(Microsoft.Azure.Cosmos.Spatial.Position)]" - } - }, - "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.Spatial.Polygon;Microsoft.Azure.Cosmos.Spatial.Geometry;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "Boolean Equals(Microsoft.Azure.Cosmos.Spatial.Polygon)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Boolean Equals(Microsoft.Azure.Cosmos.Spatial.Polygon);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:True;" - }, - "Boolean Equals(System.Object)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Boolean Equals(System.Object);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Int32 GetHashCode()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Int32 GetHashCode();IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] get_Rings()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] get_Rings();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] Rings[System.Runtime.Serialization.DataMemberAttribute(Name = \"coordinates\")]-[Newtonsoft.Json.JsonPropertyAttribute(\"coordinates\", Order = 1, Required = 2)]": { - "Type": "Property", - "Attributes": [ - "DataMemberAttribute", - "JsonPropertyAttribute" - ], - "MethodInfo": "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] Rings;CanRead:True;CanWrite:True;System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] get_Rings();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing], Microsoft.Azure.Cosmos.Spatial.GeometryParams)": { - "Type": "Constructor", - "Attributes": [], - "MethodInfo": "[Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing], Microsoft.Azure.Cosmos.Spatial.GeometryParams), Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing], Microsoft.Azure.Cosmos.Spatial.GeometryParams)]" - }, - "Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing])": { - "Type": "Constructor", - "Attributes": [], - "MethodInfo": "[Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing]), Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing])]" - }, - "Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.Position])": { - "Type": "Constructor", - "Attributes": [], - "MethodInfo": "[Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.Position]), Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.Position])]" - } - }, - "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.Spatial.PolygonCoordinates;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "Boolean Equals(Microsoft.Azure.Cosmos.Spatial.PolygonCoordinates)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Boolean Equals(Microsoft.Azure.Cosmos.Spatial.PolygonCoordinates);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:True;" - }, - "Boolean Equals(System.Object)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Boolean Equals(System.Object);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Int32 GetHashCode()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Int32 GetHashCode();IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] get_Rings()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] get_Rings();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] Rings[System.Runtime.Serialization.DataMemberAttribute(Name = \"rings\")]": { - "Type": "Property", - "Attributes": [ - "DataMemberAttribute" - ], - "MethodInfo": "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] Rings;CanRead:True;CanWrite:True;System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] get_Rings();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing])": { - "Type": "Constructor", - "Attributes": [], - "MethodInfo": "[Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing]), Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing])]" - } - }, - "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.Spatial.Position;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "Boolean Equals(Microsoft.Azure.Cosmos.Spatial.Position)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Boolean Equals(Microsoft.Azure.Cosmos.Spatial.Position);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:True;" - }, - "Boolean Equals(System.Object)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Boolean Equals(System.Object);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Double get_Latitude()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Double get_Latitude();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Double get_Longitude()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Double get_Longitude();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Double Latitude": { - "Type": "Property", - "Attributes": [], - "MethodInfo": "Double Latitude;CanRead:True;CanWrite:False;Double get_Latitude();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Double Longitude": { - "Type": "Property", - "Attributes": [], - "MethodInfo": "Double Longitude;CanRead:True;CanWrite:False;Double get_Longitude();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Int32 GetHashCode()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "Int32 GetHashCode();IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Collections.ObjectModel.ReadOnlyCollection`1[System.Double] Coordinates[System.Runtime.Serialization.DataMemberAttribute(Name = \"Coordinates\")]": { - "Type": "Property", - "Attributes": [ - "DataMemberAttribute" - ], - "MethodInfo": "System.Collections.ObjectModel.ReadOnlyCollection`1[System.Double] Coordinates;CanRead:True;CanWrite:True;System.Collections.ObjectModel.ReadOnlyCollection`1[System.Double] get_Coordinates();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Collections.ObjectModel.ReadOnlyCollection`1[System.Double] get_Coordinates()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "System.Collections.ObjectModel.ReadOnlyCollection`1[System.Double] get_Coordinates();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Nullable`1[System.Double] Altitude": { - "Type": "Property", - "Attributes": [], - "MethodInfo": "System.Nullable`1[System.Double] Altitude;CanRead:True;CanWrite:False;System.Nullable`1[System.Double] get_Altitude();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Nullable`1[System.Double] get_Altitude()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.Nullable`1[System.Double] get_Altitude();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Void .ctor(Double, Double, System.Nullable`1[System.Double])": { - "Type": "Constructor", - "Attributes": [], - "MethodInfo": "[Void .ctor(Double, Double, System.Nullable`1[System.Double]), Void .ctor(Double, Double, System.Nullable`1[System.Double])]" - }, - "Void .ctor(Double, Double)": { - "Type": "Constructor", - "Attributes": [], - "MethodInfo": "[Void .ctor(Double, Double), Void .ctor(Double, Double)]" - }, - "Void .ctor(System.Collections.Generic.IList`1[System.Double])": { - "Type": "Constructor", - "Attributes": [], - "MethodInfo": "[Void .ctor(System.Collections.Generic.IList`1[System.Double]), Void .ctor(System.Collections.Generic.IList`1[System.Double])]" - } - }, - "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.SpatialPath;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "Microsoft.Azure.Cosmos.BoundingBoxProperties BoundingBox[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"boundingBox\")]": { - "Type": "Property", - "Attributes": [ - "JsonPropertyAttribute" - ], - "MethodInfo": "Microsoft.Azure.Cosmos.BoundingBoxProperties BoundingBox;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.BoundingBoxProperties get_BoundingBox();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_BoundingBox(Microsoft.Azure.Cosmos.BoundingBoxProperties);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Microsoft.Azure.Cosmos.BoundingBoxProperties get_BoundingBox()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Microsoft.Azure.Cosmos.BoundingBoxProperties get_BoundingBox();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.SpatialType] get_SpatialTypes()": { + "Int32 GetHashCode()": { "Type": "Method", "Attributes": [], - "MethodInfo": "System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.SpatialType] get_SpatialTypes();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.SpatialType] SpatialTypes[Newtonsoft.Json.JsonPropertyAttribute(ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter), PropertyName = \"types\")]": { - "Type": "Property", - "Attributes": [ - "JsonPropertyAttribute" - ], - "MethodInfo": "System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.SpatialType] SpatialTypes;CanRead:True;CanWrite:True;System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.SpatialType] get_SpatialTypes();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Int32 GetHashCode();IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.String get_Path()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Microsoft.Azure.Cosmos.Spatial.Position get_Position()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "System.String get_Path();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Microsoft.Azure.Cosmos.Spatial.Position get_Position();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.String Path[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"path\")]": { + "Microsoft.Azure.Cosmos.Spatial.Position Position[System.Runtime.Serialization.DataMemberAttribute(Name = \"coordinates\")]-[Newtonsoft.Json.JsonPropertyAttribute(\"coordinates\", Order = 1, Required = 2)]": { "Type": "Property", "Attributes": [ + "DataMemberAttribute", "JsonPropertyAttribute" ], - "MethodInfo": "System.String Path;CanRead:True;CanWrite:True;System.String get_Path();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Path(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Microsoft.Azure.Cosmos.Spatial.Position Position;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.Spatial.Position get_Position();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Void .ctor()": { + "Void .ctor(Double, Double)": { "Type": "Constructor", "Attributes": [], - "MethodInfo": "[Void .ctor(), Void .ctor()]" + "MethodInfo": "[Void .ctor(Double, Double), Void .ctor(Double, Double)]" }, - "Void set_BoundingBox(Microsoft.Azure.Cosmos.BoundingBoxProperties)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Void set_BoundingBox(Microsoft.Azure.Cosmos.BoundingBoxProperties);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "Void .ctor(Microsoft.Azure.Cosmos.Spatial.Position, Microsoft.Azure.Cosmos.Spatial.GeometryParams)": { + "Type": "Constructor", + "Attributes": [], + "MethodInfo": "[Void .ctor(Microsoft.Azure.Cosmos.Spatial.Position, Microsoft.Azure.Cosmos.Spatial.GeometryParams), Void .ctor(Microsoft.Azure.Cosmos.Spatial.Position, Microsoft.Azure.Cosmos.Spatial.GeometryParams)]" }, - "Void set_Path(System.String)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Void set_Path(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "Void .ctor(Microsoft.Azure.Cosmos.Spatial.Position)": { + "Type": "Constructor", + "Attributes": [], + "MethodInfo": "[Void .ctor(Microsoft.Azure.Cosmos.Spatial.Position), Void .ctor(Microsoft.Azure.Cosmos.Spatial.Position)]" } }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.SpatialType;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { + "Microsoft.Azure.Cosmos.Spatial.Polygon;Microsoft.Azure.Cosmos.Spatial.Geometry;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Int32 value__": { - "Type": "Field", + "Boolean Equals(Microsoft.Azure.Cosmos.Spatial.Polygon)": { + "Type": "Method", "Attributes": [], - "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" + "MethodInfo": "Boolean Equals(Microsoft.Azure.Cosmos.Spatial.Polygon);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:True;" }, - "Microsoft.Azure.Cosmos.SpatialType LineString": { - "Type": "Field", + "Boolean Equals(System.Object)": { + "Type": "Method", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.SpatialType LineString;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "Boolean Equals(System.Object);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.SpatialType MultiPolygon": { - "Type": "Field", + "Int32 GetHashCode()": { + "Type": "Method", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.SpatialType MultiPolygon;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "Int32 GetHashCode();IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.SpatialType Point": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.SpatialType Point;IsInitOnly:False;IsStatic:True;" + "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] get_Rings()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] get_Rings();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.SpatialType Polygon": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.SpatialType Polygon;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+HistogramBuckets": { - "Type": "NestedType", + "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] Rings[System.Runtime.Serialization.DataMemberAttribute(Name = \"coordinates\")]-[Newtonsoft.Json.JsonPropertyAttribute(\"coordinates\", Order = 1, Required = 2)]": { + "Type": "Property", + "Attributes": [ + "DataMemberAttribute", + "JsonPropertyAttribute" + ], + "MethodInfo": "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] Rings;CanRead:True;CanWrite:True;System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] get_Rings();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing], Microsoft.Azure.Cosmos.Spatial.GeometryParams)": { + "Type": "Constructor", "Attributes": [], - "MethodInfo": null + "MethodInfo": "[Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing], Microsoft.Azure.Cosmos.Spatial.GeometryParams), Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing], Microsoft.Azure.Cosmos.Spatial.GeometryParams)]" }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics": { - "Type": "NestedType", + "Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing])": { + "Type": "Constructor", "Attributes": [], - "MethodInfo": null + "MethodInfo": "[Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing]), Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing])]" }, - "Void .ctor()": { + "Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.Position])": { "Type": "Constructor", "Attributes": [], - "MethodInfo": "[Void .ctor(), Void .ctor()]" + "MethodInfo": "[Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.Position]), Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.Position])]" } }, - "NestedTypes": { - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+HistogramBuckets;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "Double[] ActiveInstancesBuckets": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Double[] ActiveInstancesBuckets;IsInitOnly:True;IsStatic:True;" - }, - "Double[] RequestLatencyBuckets": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Double[] RequestLatencyBuckets;IsInitOnly:True;IsStatic:True;" - }, - "Double[] RequestUnitBuckets": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Double[] RequestUnitBuckets;IsInitOnly:True;IsStatic:True;" - }, - "Double[] RowCountBuckets": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Double[] RowCountBuckets;IsInitOnly:True;IsStatic:True;" - } - }, - "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Description": { - "Type": "NestedType", - "Attributes": [], - "MethodInfo": null - }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Name": { - "Type": "NestedType", - "Attributes": [], - "MethodInfo": null - }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Unit": { - "Type": "NestedType", - "Attributes": [], - "MethodInfo": null - }, - "System.String MeterName": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String MeterName;IsInitOnly:False;IsStatic:True;" - }, - "System.String Version": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String Version;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": { - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String ActiveInstances": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" - }, - "System.String Latency": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" - }, - "System.String RequestCharge": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" - }, - "System.String RowCount": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String ActiveInstances": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" - }, - "System.String Latency": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" - }, - "System.String RequestCharge": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" - }, - "System.String RowCount": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String Count": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String Count;IsInitOnly:False;IsStatic:True;" - }, - "System.String RequestUnit": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String RequestUnit;IsInitOnly:False;IsStatic:True;" - }, - "System.String Sec": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String Sec;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} - } - } - } - } + "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+HistogramBuckets;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.Spatial.PolygonCoordinates;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Double[] ActiveInstancesBuckets": { - "Type": "Field", + "Boolean Equals(Microsoft.Azure.Cosmos.Spatial.PolygonCoordinates)": { + "Type": "Method", "Attributes": [], - "MethodInfo": "Double[] ActiveInstancesBuckets;IsInitOnly:True;IsStatic:True;" + "MethodInfo": "Boolean Equals(Microsoft.Azure.Cosmos.Spatial.PolygonCoordinates);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:True;" }, - "Double[] RequestLatencyBuckets": { - "Type": "Field", + "Boolean Equals(System.Object)": { + "Type": "Method", "Attributes": [], - "MethodInfo": "Double[] RequestLatencyBuckets;IsInitOnly:True;IsStatic:True;" + "MethodInfo": "Boolean Equals(System.Object);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Double[] RequestUnitBuckets": { - "Type": "Field", + "Int32 GetHashCode()": { + "Type": "Method", "Attributes": [], - "MethodInfo": "Double[] RequestUnitBuckets;IsInitOnly:True;IsStatic:True;" + "MethodInfo": "Int32 GetHashCode();IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] get_Rings()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] get_Rings();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] Rings[System.Runtime.Serialization.DataMemberAttribute(Name = \"rings\")]": { + "Type": "Property", + "Attributes": [ + "DataMemberAttribute" + ], + "MethodInfo": "System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] Rings;CanRead:True;CanWrite:True;System.Collections.ObjectModel.ReadOnlyCollection`1[Microsoft.Azure.Cosmos.Spatial.LinearRing] get_Rings();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Double[] RowCountBuckets": { - "Type": "Field", + "Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing])": { + "Type": "Constructor", "Attributes": [], - "MethodInfo": "Double[] RowCountBuckets;IsInitOnly:True;IsStatic:True;" + "MethodInfo": "[Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing]), Void .ctor(System.Collections.Generic.IList`1[Microsoft.Azure.Cosmos.Spatial.LinearRing])]" } }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.Spatial.Position;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Description": { - "Type": "NestedType", + "Boolean Equals(Microsoft.Azure.Cosmos.Spatial.Position)": { + "Type": "Method", "Attributes": [], - "MethodInfo": null + "MethodInfo": "Boolean Equals(Microsoft.Azure.Cosmos.Spatial.Position);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:True;" }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Name": { - "Type": "NestedType", + "Boolean Equals(System.Object)": { + "Type": "Method", "Attributes": [], - "MethodInfo": null + "MethodInfo": "Boolean Equals(System.Object);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Unit": { - "Type": "NestedType", + "Double get_Latitude()": { + "Type": "Method", "Attributes": [], - "MethodInfo": null + "MethodInfo": "Double get_Latitude();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.String MeterName": { - "Type": "Field", + "Double get_Longitude()": { + "Type": "Method", "Attributes": [], - "MethodInfo": "System.String MeterName;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "Double get_Longitude();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.String Version": { - "Type": "Field", + "Double Latitude": { + "Type": "Property", "Attributes": [], - "MethodInfo": "System.String Version;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": { - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String ActiveInstances": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" - }, - "System.String Latency": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" - }, - "System.String RequestCharge": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" - }, - "System.String RowCount": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} + "MethodInfo": "Double Latitude;CanRead:True;CanWrite:False;Double get_Latitude();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String ActiveInstances": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" - }, - "System.String Latency": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" - }, - "System.String RequestCharge": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" - }, - "System.String RowCount": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} + "Double Longitude": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "Double Longitude;CanRead:True;CanWrite:False;Double get_Longitude();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String Count": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String Count;IsInitOnly:False;IsStatic:True;" - }, - "System.String RequestUnit": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String RequestUnit;IsInitOnly:False;IsStatic:True;" - }, - "System.String Sec": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String Sec;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} - } - } - }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String ActiveInstances": { - "Type": "Field", + "Int32 GetHashCode()": { + "Type": "Method", "Attributes": [], - "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "Int32 GetHashCode();IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.String Latency": { - "Type": "Field", + "System.Collections.ObjectModel.ReadOnlyCollection`1[System.Double] Coordinates[System.Runtime.Serialization.DataMemberAttribute(Name = \"Coordinates\")]": { + "Type": "Property", + "Attributes": [ + "DataMemberAttribute" + ], + "MethodInfo": "System.Collections.ObjectModel.ReadOnlyCollection`1[System.Double] Coordinates;CanRead:True;CanWrite:True;System.Collections.ObjectModel.ReadOnlyCollection`1[System.Double] get_Coordinates();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.Collections.ObjectModel.ReadOnlyCollection`1[System.Double] get_Coordinates()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.Collections.ObjectModel.ReadOnlyCollection`1[System.Double] get_Coordinates();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.Nullable`1[System.Double] Altitude": { + "Type": "Property", "Attributes": [], - "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "System.Nullable`1[System.Double] Altitude;CanRead:True;CanWrite:False;System.Nullable`1[System.Double] get_Altitude();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.String RequestCharge": { - "Type": "Field", + "System.Nullable`1[System.Double] get_Altitude()": { + "Type": "Method", "Attributes": [], - "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "System.Nullable`1[System.Double] get_Altitude();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.String RowCount": { - "Type": "Field", + "Void .ctor(Double, Double, System.Nullable`1[System.Double])": { + "Type": "Constructor", "Attributes": [], - "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "[Void .ctor(Double, Double, System.Nullable`1[System.Double]), Void .ctor(Double, Double, System.Nullable`1[System.Double])]" + }, + "Void .ctor(Double, Double)": { + "Type": "Constructor", + "Attributes": [], + "MethodInfo": "[Void .ctor(Double, Double), Void .ctor(Double, Double)]" + }, + "Void .ctor(System.Collections.Generic.IList`1[System.Double])": { + "Type": "Constructor", + "Attributes": [], + "MethodInfo": "[Void .ctor(System.Collections.Generic.IList`1[System.Double]), Void .ctor(System.Collections.Generic.IList`1[System.Double])]" } }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.SpatialPath;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "System.String ActiveInstances": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String ActiveInstances;IsInitOnly:False;IsStatic:True;" + "Microsoft.Azure.Cosmos.BoundingBoxProperties BoundingBox[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"boundingBox\")]": { + "Type": "Property", + "Attributes": [ + "JsonPropertyAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.BoundingBoxProperties BoundingBox;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.BoundingBoxProperties get_BoundingBox();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_BoundingBox(Microsoft.Azure.Cosmos.BoundingBoxProperties);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.String Latency": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + "Microsoft.Azure.Cosmos.BoundingBoxProperties get_BoundingBox()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.BoundingBoxProperties get_BoundingBox();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.String RequestCharge": { - "Type": "Field", + "System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.SpatialType] get_SpatialTypes()": { + "Type": "Method", "Attributes": [], - "MethodInfo": "System.String RequestCharge;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.SpatialType] get_SpatialTypes();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.String RowCount": { - "Type": "Field", + "System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.SpatialType] SpatialTypes[Newtonsoft.Json.JsonPropertyAttribute(ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter), PropertyName = \"types\")]": { + "Type": "Property", + "Attributes": [ + "JsonPropertyAttribute" + ], + "MethodInfo": "System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.SpatialType] SpatialTypes;CanRead:True;CanWrite:True;System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.SpatialType] get_SpatialTypes();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String get_Path()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.String get_Path();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String Path[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"path\")]": { + "Type": "Property", + "Attributes": [ + "JsonPropertyAttribute" + ], + "MethodInfo": "System.String Path;CanRead:True;CanWrite:True;System.String get_Path();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Path(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void .ctor()": { + "Type": "Constructor", "Attributes": [], - "MethodInfo": "System.String RowCount;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "[Void .ctor(), Void .ctor()]" + }, + "Void set_BoundingBox(Microsoft.Azure.Cosmos.BoundingBoxProperties)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_BoundingBox(Microsoft.Azure.Cosmos.BoundingBoxProperties);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void set_Path(System.String)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_Path(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" } }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.Telemetry.OpenTelemetryMetricsConstant+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.SpatialType;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { "Subclasses": {}, "Members": { - "System.String Count": { + "Int32 value__": { "Type": "Field", "Attributes": [], - "MethodInfo": "System.String Count;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" }, - "System.String RequestUnit": { + "Microsoft.Azure.Cosmos.SpatialType LineString": { "Type": "Field", "Attributes": [], - "MethodInfo": "System.String RequestUnit;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "Microsoft.Azure.Cosmos.SpatialType LineString;IsInitOnly:False;IsStatic:True;" }, - "System.String Sec": { + "Microsoft.Azure.Cosmos.SpatialType MultiPolygon": { "Type": "Field", "Attributes": [], - "MethodInfo": "System.String Sec;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "Microsoft.Azure.Cosmos.SpatialType MultiPolygon;IsInitOnly:False;IsStatic:True;" + }, + "Microsoft.Azure.Cosmos.SpatialType Point": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.SpatialType Point;IsInitOnly:False;IsStatic:True;" + }, + "Microsoft.Azure.Cosmos.SpatialType Polygon": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.SpatialType Polygon;IsInitOnly:False;IsStatic:True;" } }, "NestedTypes": {} From 6c32e52e7a84ed9f1d084e017d79f3f1296b54cb Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 18 Oct 2024 11:20:18 +0530 Subject: [PATCH 13/49] fix merges --- Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj b/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj index bfe90ace04..55b2fba26a 100644 --- a/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj +++ b/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj @@ -114,7 +114,7 @@ - + @@ -127,7 +127,7 @@ - + From f6974ea57abdbe8241d60e089fb4cdbe85892b2b Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 18 Oct 2024 11:25:49 +0530 Subject: [PATCH 14/49] remove console --- .../Metrics/CustomMetricExporter.cs | 41 ------------------- ...icrosoft.Azure.Cosmos.EmulatorTests.csproj | 15 ++----- 2 files changed, 3 insertions(+), 53 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs index 19771a02af..38900c4b23 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs @@ -24,49 +24,9 @@ public CustomMetricExporter(ManualResetEventSlim manualResetEventSlim) // This method will be called periodically by OpenTelemetry SDK public override ExportResult Export(in Batch batch) { - Console.WriteLine("Exporting metrics..."); foreach (Metric metric in batch) { - Console.WriteLine($"Metric Name: {metric.Name}, Metric Type: {metric.MetricType}"); ActualMetrics.Add(metric.Name, metric.MetricType); - - // Iterate over the data points for this metric - foreach (MetricPoint metricPoint in metric.GetMetricPoints()) - { - Console.WriteLine($" DataPoint - StartTime: {metricPoint.StartTime}, EndTime: {metricPoint.EndTime}"); - Console.WriteLine($" Attributes:"); - - foreach (KeyValuePair attribute in metricPoint.Tags) - { - Console.WriteLine($" {attribute.Key}: {attribute.Value}"); - } - - // Print different data depending on the MetricType - switch (metric.MetricType) - { - case MetricType.LongSum: - case MetricType.DoubleSum: - Console.WriteLine($" Sum: {metricPoint.GetSumLong()}{metricPoint.GetSumDouble()}"); - break; - case MetricType.LongGauge: - case MetricType.DoubleGauge: - Console.WriteLine($" Gauge: {metricPoint.GetGaugeLastValueLong()}{metricPoint.GetGaugeLastValueDouble()}"); - break; - case MetricType.LongSumNonMonotonic: // Non-monotonic sums for UpDownCounter - Console.WriteLine($" Non-Monotonic Long Sum: {metricPoint.GetSumLong()}"); - break; - case MetricType.DoubleSumNonMonotonic: // Non-monotonic sums for UpDownCounter (double version) - Console.WriteLine($" Non-Monotonic Double Sum: {metricPoint.GetSumDouble()}"); - break; - case MetricType.Histogram: - Console.WriteLine($" Histogram - Count: {metricPoint.GetHistogramCount()}, Sum: {metricPoint.GetHistogramSum()}"); - break; - default: - Console.WriteLine(" Unknown metric type."); - break; - } - } - } if (ActualMetrics.Count > 0) @@ -74,7 +34,6 @@ public override ExportResult Export(in Batch batch) this.manualResetEventSlim.Set(); } - Console.WriteLine($"Metrics Count {ActualMetrics.Count}"); return ExportResult.Success; } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 1382c54d37..4f70def22d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -34,7 +34,7 @@ - + @@ -51,19 +51,10 @@ - - - + - - - - - - - @@ -74,7 +65,7 @@ - + From 161abe9cfa10cdcfeb098405ef1374cfca1aacee Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 18 Oct 2024 12:27:30 +0530 Subject: [PATCH 15/49] add noops if disables --- .../OpenTelemetry/CosmosOperationMeter.cs | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index 0e4cdc97d2..e08cc82521 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -39,6 +39,11 @@ internal static class CosmosOperationMeter /// internal static UpDownCounter ActiveInstanceCounter = null; + /// + /// Flag to check if metrics is enabled + /// + private static bool IsEnabled = false; + /// /// Initializes the histograms and counters for capturing Cosmos DB metrics. /// @@ -59,6 +64,8 @@ internal static void Initialize() CosmosOperationMeter.ActiveInstanceCounter ??= OperationMeter.CreateUpDownCounter(name: CosmosDbClientMetricsConstant.OperationMetrics.Name.ActiveInstances, unit: CosmosDbClientMetricsConstant.OperationMetrics.Unit.Count, description: CosmosDbClientMetricsConstant.OperationMetrics.Description.ActiveInstances); + + IsEnabled = true; } /// @@ -77,6 +84,11 @@ internal static void RecordTelemetry(string operationName, OpenTelemetryAttributes attributes = null, Exception ex = null) { + if (!IsEnabled) + { + return; + } + Func[]> dimensionsFunc = () => { List> dimensions = new List>() @@ -113,16 +125,16 @@ internal static void RecordTelemetry(string operationName, if (attributes != null) { - CosmosOperationMeter.RecordActualItemCount(Convert.ToInt32(attributes.ItemCount), dimensionsFunc); - CosmosOperationMeter.RecordRequestUnit(attributes.RequestCharge.Value, dimensionsFunc); - CosmosOperationMeter.RecordRequestLatency(attributes.Diagnostics.GetClientElapsedTime(), dimensionsFunc); + CosmosOperationMeter.RecordActualItemCount(attributes.ItemCount, dimensionsFunc); + CosmosOperationMeter.RecordRequestUnit(attributes.RequestCharge, dimensionsFunc); + CosmosOperationMeter.RecordRequestLatency(attributes.Diagnostics?.GetClientElapsedTime(), dimensionsFunc); } if (ex != null && ex is CosmosException cosmosException) { - CosmosOperationMeter.RecordActualItemCount(Convert.ToInt32(cosmosException.Headers.ItemCount), dimensionsFunc); + CosmosOperationMeter.RecordActualItemCount(cosmosException.Headers.ItemCount, dimensionsFunc); CosmosOperationMeter.RecordRequestUnit(cosmosException.Headers.RequestCharge, dimensionsFunc); - CosmosOperationMeter.RecordRequestLatency(cosmosException.Diagnostics.GetClientElapsedTime(), dimensionsFunc); + CosmosOperationMeter.RecordRequestLatency(cosmosException.Diagnostics?.GetClientElapsedTime(), dimensionsFunc); } } @@ -131,14 +143,14 @@ internal static void RecordTelemetry(string operationName, /// /// The number of items returned or affected by the operation. /// A function providing telemetry dimensions for the metric. - internal static void RecordActualItemCount(int actualItemCount, Func[]> dimensionsFunc) + internal static void RecordActualItemCount(string actualItemCount, Func[]> dimensionsFunc) { - if (CosmosOperationMeter.ActualItemHistogram == null || !CosmosOperationMeter.ActualItemHistogram.Enabled) + if (CosmosOperationMeter.ActualItemHistogram == null || !CosmosOperationMeter.ActualItemHistogram.Enabled || string.IsNullOrEmpty(actualItemCount)) { return; } - CosmosOperationMeter.ActualItemHistogram.Record(actualItemCount, dimensionsFunc()); + CosmosOperationMeter.ActualItemHistogram.Record(Convert.ToInt32(actualItemCount), dimensionsFunc()); } /// @@ -146,14 +158,14 @@ internal static void RecordActualItemCount(int actualItemCount, Func /// The RU/s value for the operation. /// A function providing telemetry dimensions for the metric. - internal static void RecordRequestUnit(double requestCharge, Func[]> dimensionsFunc) + internal static void RecordRequestUnit(double? requestCharge, Func[]> dimensionsFunc) { - if (CosmosOperationMeter.RequestUnitsHistogram == null || !CosmosOperationMeter.RequestUnitsHistogram.Enabled) + if (CosmosOperationMeter.RequestUnitsHistogram == null || !CosmosOperationMeter.RequestUnitsHistogram.Enabled || !requestCharge.HasValue) { return; } - CosmosOperationMeter.RequestUnitsHistogram.Record(requestCharge, dimensionsFunc()); + CosmosOperationMeter.RequestUnitsHistogram.Record(requestCharge.Value, dimensionsFunc()); } /// @@ -163,7 +175,7 @@ internal static void RecordRequestUnit(double requestCharge, FuncA function providing telemetry dimensions for the metric. internal static void RecordRequestLatency(TimeSpan? requestLatency, Func[]> dimensionsFunc) { - if (CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled) + if (CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled || !requestLatency.HasValue) { return; } @@ -177,7 +189,7 @@ internal static void RecordRequestLatency(TimeSpan? requestLatency, FuncThe URI of the account endpoint. internal static void AddInstanceCount(Uri accountEndpoint) { - if (CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled) + if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled) { return; } @@ -198,7 +210,7 @@ internal static void AddInstanceCount(Uri accountEndpoint) /// The URI of the account endpoint. internal static void RemoveInstanceCount(Uri accountEndpoint) { - if (CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled) + if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled) { return; } From 2569ed9b0eba29ce54065c2ca3e81ce8dcad3ecc Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 18 Oct 2024 13:20:00 +0530 Subject: [PATCH 16/49] added null check --- .../src/Resource/ClientContextCore.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index 2dd699e73c..c7ece30d2c 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -573,12 +573,16 @@ private async Task RunWithDiagnosticsHelperAsync( catch (Exception ex) { recorder.MarkFailed(ex); - // Records telemetry data related to the exception. - CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, - accountName: this.client.Endpoint, - containerName: containerName, - databaseName: databaseName, - ex: ex); + if (openTelemetry != null) + { + // Records telemetry data related to the exception. + CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, + accountName: this.client.Endpoint, + containerName: containerName, + databaseName: databaseName, + ex: ex); + } + throw; } } From a3ee34dc32942978c3e184a0aeea101957614cea Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kolli Date: Fri, 18 Oct 2024 09:33:49 -0700 Subject: [PATCH 17/49] [INTERNAL] CI: Fixes emulator set-up to leverage central SDK teams scripts (#4813) [INTERNAL] CI: Fixes emulator set-up to leverage central SDK teams scripts Observations - Lessmsi: extraction taking longer (1M) then msiexec based model - Starting emulator from ProgramFolder path is faster - Unnecessary exponential retry logic (current one) --- templates/Cosmos-Emulator.ps1 | 151 ++++++++++++++++++++++++++++++++++ templates/emulator-setup.yml | 81 ++++-------------- tools/lessmsi-v2.1.1.zip | Bin 667300 -> 0 bytes 3 files changed, 165 insertions(+), 67 deletions(-) create mode 100644 templates/Cosmos-Emulator.ps1 delete mode 100644 tools/lessmsi-v2.1.1.zip diff --git a/templates/Cosmos-Emulator.ps1 b/templates/Cosmos-Emulator.ps1 new file mode 100644 index 0000000000..e510335e6b --- /dev/null +++ b/templates/Cosmos-Emulator.ps1 @@ -0,0 +1,151 @@ +<# +.SYNOPSIS +Script for installing and launching cosmos emulator + +.DESCRIPTION +This script downloads, installs and launches cosmosdb-emulator. + +.PARAMETER EmulatorMsiUrl +Uri for downloading the cosmosdb-emulator + +.PARAMETER StartParameters +Parameter with which to launch the cosmosdb-emulator\ + +.PARAMETER Emulator +Exact path to Microsoft.Azure.Cosmos.Emulator.exe + +.PARAMETER Stage +Determines what part of the script to run. Has to be either Install or Launch +#> +[CmdletBinding()] +Param ( + [string] $EmulatorMsiUrl = "https://aka.ms/cosmosdb-emulator", + [string] $StartParameters, + [string] $Emulator, + [Parameter(Mandatory=$True)] + [ValidateSet('Install', 'Launch')] + [string] $Stage +) + +$targetDir = Join-Path $Env:Temp AzureCosmosEmulator +$logFile = Join-Path $Env:Temp log.txt +$productName = "Azure Cosmos DB Emulator" + +if ([string]::IsNullOrEmpty($Emulator)) +{ + $Emulator = (Join-Path $targetDir (Join-Path $productName "Microsoft.Azure.Cosmos.Emulator.exe")) +} + +if ($Stage -eq "Install") +{ + $downloadTryCount = 0 + New-Item $targetDir -Type Directory + New-Item $logFile -Type File + do + { + # Download and Extract Public Cosmos DB Emulator + Write-Host "Downloading and extracting Cosmos DB Emulator - $EmulatorMsiUrl" + Write-Host "Target Directory $targetDir" + Write-Host "Log File $logFile" + + $downloadTryCount++ + Write-Host "Download Try Count: $downloadTryCount" + Remove-Item -Path (Join-Path $targetDir '*') -Recurse + Clear-Content -Path $logFile + + Add-MpPreference -ExclusionPath $targetDir + + $installProcess = Start-Process msiexec -Wait -PassThru -ArgumentList "/a $EmulatorMsiUrl TARGETDIR=$targetDir /qn /liew $logFile" + Get-Content $logFile + Write-Host "Exit Code: $($installProcess.ExitCode)" + } + while(($installProcess.ExitCode -ne 0) -and ($downloadTryCount -lt 3)) + + if(Test-Path (Join-Path $Env:LOCALAPPDATA CosmosDbEmulator)) + { + Write-Host "Deleting Cosmos DB Emulator data" + Remove-Item -Recurse -Force $Env:LOCALAPPDATA\CosmosDbEmulator + } + + Write-Host "Getting Cosmos DB Emulator Version" + $fileVersion = Get-ChildItem $Emulator + Write-Host $Emulator $fileVersion.VersionInfo +} + +if ($Stage -eq "Launch") +{ + Write-Host "Launching Cosmos DB Emulator" + if (!(Test-Path $Emulator)) { + Write-Error "The emulator is not installed where expected at '$Emulator'" + return + } + + $process = Start-Process $Emulator -ArgumentList "/getstatus" -PassThru -Wait + switch ($process.ExitCode) { + 1 { + Write-Host "The emulator is already starting" + return + } + 2 { + Write-Host "The emulator is already running" + return + } + 3 { + Write-Host "The emulator is stopped" + } + default { + Write-Host "Unrecognized exit code $($process.ExitCode)" + return + } + } + + $argumentList = "" + if (-not [string]::IsNullOrEmpty($StartParameters)) { + $argumentList += , $StartParameters + } else { + # Use the default params if none provided + $argumentList = "/noexplorer /noui /enablepreview /EnableSqlComputeEndpoint /disableratelimiting /partitioncount=10 /consistency=Strong" + } + + Write-Host "Starting emulator process: $Emulator $argumentList" + $process = Start-Process $Emulator -ArgumentList $argumentList -ErrorAction Stop -PassThru + Write-Host "Emulator process started: $($process.Name), $($process.FileVersion)" + + $Timeout = 600 + $result="NotYetStarted" + $complete = if ($Timeout -gt 0) { + $start = [DateTimeOffset]::Now + $stop = $start.AddSeconds($Timeout) + { + $result -eq "Running" -or [DateTimeOffset]::Now -ge $stop + } + } + else { + { + $result -eq "Running" + } + } + + do { + $process = Start-Process $Emulator -ArgumentList "/getstatus" -PassThru -Wait + switch ($process.ExitCode) { + 1 { + Write-Host "The emulator is starting" + } + 2 { + Write-Host "The emulator is running" + $result="Running" + return + } + 3 { + Write-Host "The emulator is stopped" + } + default { + Write-Host "Unrecognized exit code $($process.ExitCode)" + } + } + Start-Sleep -Seconds 5 + } + until ($complete.Invoke()) + Write-Error "The emulator failed to reach Running status within ${Timeout} seconds" +} \ No newline at end of file diff --git a/templates/emulator-setup.yml b/templates/emulator-setup.yml index 990ad068b6..7c3db39180 100644 --- a/templates/emulator-setup.yml +++ b/templates/emulator-setup.yml @@ -1,69 +1,16 @@ -# File: templates/emulator-setup.yml +parameters: + EmulatorInstallPath: "$(Agent.HomeDirectory)/../../Program Files/Azure Cosmos DB Emulator/Microsoft.Azure.Cosmos.Emulator.exe" + EmulatorMsiUrl: "https://aka.ms/cosmosdb-emulator" + StartParameters: '' steps: - - pwsh: | - Write-Host "Downloading Cosmos Emulator - $env:EMULATORMSIURL" -ForegroundColor green - Invoke-WebRequest "$env:EMULATORMSIURL" -OutFile "$env:temp\azure-cosmosdb-emulator.msi" - Write-Host "Finished Downloading Cosmos Emulator - $env:temp\azure-cosmosdb-emulator.msi" -ForegroundColor green - dir "$env:temp" - - function Remove-DirectoryIfExists { - param ([string]$Path) - - if (Test-Path -Path $Path -PathType Container) { - Remove-Item -Path $Path -Recurse -Force - Write-Output "Folder deleted: $Path" - } else { - Write-Output "Folder does not exist: $Path" - } - } - - $lessMsiDir="$env:temp\lessmsi" - $emulatorDir="$env:temp\Azure Cosmos DB Emulator\" - - Remove-DirectoryIfExists -Path $lessMsiDir - Remove-DirectoryIfExists -Path $emulatorDir - - Expand-Archive -LiteralPath 'tools\lessmsi-v2.1.1.zip' -DestinationPath $lessMsiDir - &"$env:temp\lessmsi\lessmsi.exe" x "$env:temp\azure-cosmosdb-emulator.msi" "$emulatorDir" - - Add-MpPreference -ExclusionPath "$emulatorDir\SourceDir\Azure Cosmos DB Emulator" - Add-MpPreference -ExclusionPath "$env:localappdata\CosmosDBEmulator" - displayName: Downloading and Installing Cosmos DB Emulator - failOnStderr: true - errorActionPreference: stop - - pwsh: | - Write-Host "Starting Cosmos DB Emulator" -ForegroundColor green - Import-Module "$env:temp\Azure Cosmos DB Emulator\SourceDir\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator" - Get-Item env:* | Sort-Object -Property Name - - for ($j=0; $j -lt 3; $j++) { - Write-Host "Attempt $j" - Start-Process "$env:temp\Azure Cosmos DB Emulator\SourceDir\Azure Cosmos DB Emulator\CosmosDB.Emulator.exe" "/NoExplorer /NoUI /DisableRateLimiting /PartitionCount=10 /Consistency=Strong /EnablePreview /EnableSqlComputeEndpoint" -Verb RunAs - for ($i=0; $i -lt (3+2*$j); $i++) { - $status = Get-CosmosDbEmulatorStatus - Write-Host "Cosmos DB Emulator Status: $status" - if ($status -ne "Running") { - sleep 30; - } - else { - break; - } - } - if ($status -ne "Running") { - Write-Host "Shutting down and restarting" - Start-Process "$env:temp\Azure Cosmos DB Emulator\SourceDir\Azure Cosmos DB Emulator\CosmosDB.Emulator.exe" "/Shutdown" -Verb RunAs - sleep 30; - } - else { - break; - } - } - - if ($status -ne "Running") { - Write-Error "Emulator failed to start" - } - - displayName: Waiting for Cosmos DB Emulator status - failOnStderr: true - errorActionPreference: stop + - task: Powershell@2 + inputs: + filePath: $(Build.SourcesDirectory)/templates/Cosmos-Emulator.ps1 + arguments: > + -EmulatorMsiUrl "${{ parameters.EmulatorMsiUrl }}" + -StartParameters "${{ parameters.StartParameters }}" + -Emulator "${{ parameters.EmulatorInstallPath }}" + -Stage "Launch" + pwsh: true + displayName: Launch Public Cosmos DB Emulator \ No newline at end of file diff --git a/tools/lessmsi-v2.1.1.zip b/tools/lessmsi-v2.1.1.zip deleted file mode 100644 index 15cc3d9f24541b4be553b83191ccd77b30e12c8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 667300 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 z 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,V}s*+D87q9Y6rTf0hl{U!jH@v!B$ODKP^?u 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
=%{sj;466KkS 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 Date: Fri, 18 Oct 2024 09:52:23 -0700 Subject: [PATCH 18/49] VectorIndexDefinition: Adds Support for Partitioned DiskANN (#4792) # Pull Request Template ## Description This PR adds optional attributes in the `VectorIndexDefinition` class to support partitioned DiskANN. A typical index definition would be something like the below: ``` { "indexingPolicy": { "automatic": true, "indexingMode": "Consistent", "includedPaths": [ { "path": "/*", "indexes": [] } ], "excludedPaths": [], "compositeIndexes": [], "spatialIndexes": [], "vectorIndexes": [ { "path": "/vector1", "type": "flat" }, { "path": "/vector2", "type": "quantizedFlat", "quantizationByteSize": 3, "vectorIndexShardKey": [ "/Country" ] }, { "path": "/vector3", "type": "diskANN", "quantizationByteSize": 2, "indexingSearchListSize": 100, "vectorIndexShardKey": [ "/ZipCode" ] } ] }, "vectorEmbeddingPolicy": { "vectorEmbeddings": [ { "path": "/vector1", "dataType": "int8", "dimensions": 1200, "distanceFunction": "dotproduct" }, { "path": "/vector2", "dataType": "uint8", "dimensions": 3, "distanceFunction": "cosine" }, { "path": "/vector3", "dataType": "float32", "dimensions": 400, "distanceFunction": "euclidean" } ] }, "id": "test_binary_vector_container_6", "partitionKey": { "paths": [ "/pk" ], "kind": "Hash" } } ``` ## Type of change Please delete options that are not relevant. - [x] New feature (non-breaking change which adds functionality) ## Closing issues To automatically close an issue: closes #4628 --------- Co-authored-by: Kiran Kumar Kolli --- .../Fluent/Settings/VectorIndexDefinition.cs | 45 ++++++++ .../src/Resource/Settings/Embedding.cs | 4 +- .../src/Resource/Settings/VectorDataType.cs | 6 - .../src/Resource/Settings/VectorIndexPath.cs | 43 ++++++- .../Fluent/ContainerSettingsTests.cs | 18 ++- .../Contracts/DotNetPreviewSDKAPI.json | 109 ++++++++++++++---- .../CosmosContainerSettingsTests.cs | 20 +++- .../SettingsContractTests.cs | 2 +- 8 files changed, 204 insertions(+), 43 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Fluent/Settings/VectorIndexDefinition.cs b/Microsoft.Azure.Cosmos/src/Fluent/Settings/VectorIndexDefinition.cs index 98870592d6..7d1688bc9c 100644 --- a/Microsoft.Azure.Cosmos/src/Fluent/Settings/VectorIndexDefinition.cs +++ b/Microsoft.Azure.Cosmos/src/Fluent/Settings/VectorIndexDefinition.cs @@ -55,6 +55,51 @@ public VectorIndexDefinition Path( return this; } + /// + /// Configures the quantization byte size for the current definition. + /// + /// + /// The number of bytes used in product quantization of the vectors. This is an optional parameter and applies to index + /// types DiskANN and quantizedFlat. Note that, the allowed range for this parameter is between 1 and 3. + /// + /// An instance of the current . + public VectorIndexDefinition WithQuantizationByteSize( + int quantizationByteSize) + { + this.vectorIndexPath.QuantizationByteSize = quantizationByteSize; + return this; + } + + /// + /// Configures the indexing search list size for the current definition. + /// + /// + /// This represents the size of the candidate list of approximate neighbors stored while building the DiskANN index as part of the optimization processes. + /// This is an optional parameter and applies to index type DiskANN only. The allowed range for this parameter is between 25 and 500. + /// + /// An instance of the current . + public VectorIndexDefinition WithIndexingSearchListSize( + int indexingSearchListSize) + { + this.vectorIndexPath.IndexingSearchListSize = indexingSearchListSize; + return this; + } + + /// + /// Configures the vector index shard key for the current definition. + /// + /// + /// A string array containing the shard keys used for partitioning the vector indexes. This is an optional parameter and + /// applies to index types DiskANN and quantizedFlat. + /// + /// An instance of the current . + public VectorIndexDefinition WithVectorIndexShardKey( + string[] vectorIndexShardKey) + { + this.vectorIndexPath.VectorIndexShardKey = vectorIndexShardKey ?? throw new ArgumentNullException(nameof(vectorIndexShardKey)); + return this; + } + /// /// Applies the current definition to the parent. /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/Embedding.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/Embedding.cs index c511299870..7b78bdbf1a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/Embedding.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/Embedding.cs @@ -35,10 +35,10 @@ class Embedding : IEquatable public VectorDataType DataType { get; set; } /// - /// Gets or sets a long integer representing the dimensions of a vector. + /// Gets or sets an integer representing the dimensions of a vector. /// [JsonProperty(PropertyName = "dimensions")] - public ulong Dimensions { get; set; } + public int Dimensions { get; set; } /// /// Gets or sets the which is used to calculate the respective distance between the vectors. diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/VectorDataType.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/VectorDataType.cs index 39355130d4..5fc9fd78a6 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/VectorDataType.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/VectorDataType.cs @@ -15,12 +15,6 @@ namespace Microsoft.Azure.Cosmos #endif enum VectorDataType { - /// - /// Represent a float16 data type. - /// - [EnumMember(Value = "float16")] - Float16, - /// /// Represent a float32 data type. /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/VectorIndexPath.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/VectorIndexPath.cs index a7d4a94f98..6de8b2d08a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/VectorIndexPath.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/VectorIndexPath.cs @@ -32,11 +32,16 @@ namespace Microsoft.Azure.Cosmos /// }, /// { /// "path": "/vector2", - /// "type": "flat" + /// "type": "quantizedFlat", + /// "quantizationByteSize": 3, + /// "vectorIndexShardKey": ["/ZipCode"] /// }, /// { /// "path": "/embeddings/vector", - /// "type": "flat" + /// "type": "DiskANN", + /// "quantizationByteSize": 2, + /// "indexingSearchListSize": 100, + /// "vectorIndexShardKey": ["/Country"] /// } /// ] /// } @@ -49,6 +54,12 @@ namespace Microsoft.Azure.Cosmos #endif sealed class VectorIndexPath { + [JsonProperty(PropertyName = "indexingSearchListSize", NullValueHandling = NullValueHandling.Ignore)] + private int? indexingSearchListSizeInternal; + + [JsonProperty(PropertyName = "quantizationByteSize", NullValueHandling = NullValueHandling.Ignore)] + private int? quantizationByteSizeInternal; + /// /// Gets or sets the full path in a document used for vector indexing. /// @@ -62,6 +73,34 @@ sealed class VectorIndexPath [JsonConverter(typeof(StringEnumConverter))] public VectorIndexType Type { get; set; } + /// + /// Gets or sets the quantization byte size for the vector index path. This is only applicable for the quantizedFlat and diskann vector index types. + /// The allowed range for this parameter is between 1 and 3. + /// + [JsonIgnore] + public int QuantizationByteSize + { + get => this.quantizationByteSizeInternal == null ? 0 : this.quantizationByteSizeInternal.Value; + set => this.quantizationByteSizeInternal = value; + } + + /// + /// Gets or sets the indexing search list size for the vector index path. This is only applicable for the diskann vector index type. + /// The allowed range for this parameter is between 25 and 500. + /// + [JsonIgnore] + public int IndexingSearchListSize + { + get => this.indexingSearchListSizeInternal == null ? 0 : this.indexingSearchListSizeInternal.Value; + set => this.indexingSearchListSizeInternal = value; + } + + /// + /// Gets or sets the vector index shard key for the vector index path. This is only applicable for the quantizedFlat and diskann vector index types. + /// + [JsonProperty(PropertyName = "vectorIndexShardKey", NullValueHandling = NullValueHandling.Ignore)] + public string[] VectorIndexShardKey { get; set; } + /// /// This contains additional values for scenarios where the SDK is not aware of new fields. /// This ensures that if resource is read and updated none of the fields will be lost in the process. diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs index a757aec147..a7ef72fece 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs @@ -585,10 +585,15 @@ await databaseForVectorEmbedding.DefineContainer(containerName, partitionKeyPath .Path(vector1Path, VectorIndexType.Flat) .Attach() .WithVectorIndex() - .Path(vector2Path, VectorIndexType.Flat) + .Path(vector2Path, VectorIndexType.QuantizedFlat) + .WithQuantizationByteSize(3) + .WithVectorIndexShardKey(new string[] { "/Country" }) .Attach() .WithVectorIndex() - .Path(vector3Path, VectorIndexType.Flat) + .Path(vector3Path, VectorIndexType.DiskANN) + .WithQuantizationByteSize(2) + .WithIndexingSearchListSize(5) + .WithVectorIndexShardKey(new string[] { "/ZipCode" }) .Attach() .Attach() .CreateAsync(); @@ -610,9 +615,14 @@ await databaseForVectorEmbedding.DefineContainer(containerName, partitionKeyPath Assert.AreEqual(vector1Path, containerSettings.IndexingPolicy.VectorIndexes[0].Path); Assert.AreEqual(VectorIndexType.Flat, containerSettings.IndexingPolicy.VectorIndexes[0].Type); Assert.AreEqual(vector2Path, containerSettings.IndexingPolicy.VectorIndexes[1].Path); - Assert.AreEqual(VectorIndexType.Flat, containerSettings.IndexingPolicy.VectorIndexes[1].Type); + Assert.AreEqual(VectorIndexType.QuantizedFlat, containerSettings.IndexingPolicy.VectorIndexes[1].Type); + Assert.AreEqual(3, containerSettings.IndexingPolicy.VectorIndexes[1].QuantizationByteSize); + CollectionAssert.AreEqual(new string[] { "/Country" }, containerSettings.IndexingPolicy.VectorIndexes[1].VectorIndexShardKey); Assert.AreEqual(vector3Path, containerSettings.IndexingPolicy.VectorIndexes[2].Path); - Assert.AreEqual(VectorIndexType.Flat, containerSettings.IndexingPolicy.VectorIndexes[2].Type); + Assert.AreEqual(VectorIndexType.DiskANN, containerSettings.IndexingPolicy.VectorIndexes[2].Type); + Assert.AreEqual(2, containerSettings.IndexingPolicy.VectorIndexes[2].QuantizationByteSize); + Assert.AreEqual(5, containerSettings.IndexingPolicy.VectorIndexes[2].IndexingSearchListSize); + CollectionAssert.AreEqual(new string[] { "/ZipCode" }, containerSettings.IndexingPolicy.VectorIndexes[2].VectorIndexShardKey); } finally { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index 17523aa5c9..94e38f480d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -458,6 +458,20 @@ "Attributes": [], "MethodInfo": "Boolean Equals(Microsoft.Azure.Cosmos.Embedding);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:True;" }, + "Int32 Dimensions[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"dimensions\")]": { + "Type": "Property", + "Attributes": [ + "JsonPropertyAttribute" + ], + "MethodInfo": "Int32 Dimensions;CanRead:True;CanWrite:True;Int32 get_Dimensions();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Dimensions(Int32);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Int32 get_Dimensions()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Int32 get_Dimensions();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Microsoft.Azure.Cosmos.DistanceFunction DistanceFunction[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"distanceFunction\")]-[Newtonsoft.Json.JsonConverterAttribute(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]": { "Type": "Property", "Attributes": [ @@ -502,20 +516,6 @@ ], "MethodInfo": "System.String Path;CanRead:True;CanWrite:True;System.String get_Path();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Path(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "UInt64 Dimensions[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"dimensions\")]": { - "Type": "Property", - "Attributes": [ - "JsonPropertyAttribute" - ], - "MethodInfo": "UInt64 Dimensions;CanRead:True;CanWrite:True;UInt64 get_Dimensions();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Dimensions(UInt64);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "UInt64 get_Dimensions()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "UInt64 get_Dimensions();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "Void .ctor()": { "Type": "Constructor", "Attributes": [], @@ -528,12 +528,12 @@ ], "MethodInfo": "Void set_DataType(Microsoft.Azure.Cosmos.VectorDataType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Void set_Dimensions(UInt64)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Void set_Dimensions(Int32)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "Void set_Dimensions(UInt64);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Void set_Dimensions(Int32);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "Void set_DistanceFunction(Microsoft.Azure.Cosmos.DistanceFunction)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", @@ -657,6 +657,21 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.VectorIndexDefinition`1[T] Path(System.String, Microsoft.Azure.Cosmos.VectorIndexType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Microsoft.Azure.Cosmos.Fluent.VectorIndexDefinition`1[T] WithIndexingSearchListSize(Int32)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.VectorIndexDefinition`1[T] WithIndexingSearchListSize(Int32);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Microsoft.Azure.Cosmos.Fluent.VectorIndexDefinition`1[T] WithQuantizationByteSize(Int32)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.VectorIndexDefinition`1[T] WithQuantizationByteSize(Int32);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Microsoft.Azure.Cosmos.Fluent.VectorIndexDefinition`1[T] WithVectorIndexShardKey(System.String[])": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.VectorIndexDefinition`1[T] WithVectorIndexShardKey(System.String[]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "T Attach()": { "Type": "Method", "Attributes": [], @@ -743,13 +758,6 @@ "Attributes": [], "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" }, - "Microsoft.Azure.Cosmos.VectorDataType Float16[System.Runtime.Serialization.EnumMemberAttribute(Value = \"float16\")]": { - "Type": "Field", - "Attributes": [ - "EnumMemberAttribute" - ], - "MethodInfo": "Microsoft.Azure.Cosmos.VectorDataType Float16;IsInitOnly:False;IsStatic:True;" - }, "Microsoft.Azure.Cosmos.VectorDataType Float32[System.Runtime.Serialization.EnumMemberAttribute(Value = \"float32\")]": { "Type": "Field", "Attributes": [ @@ -795,6 +803,30 @@ "Microsoft.Azure.Cosmos.VectorIndexPath;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { + "Int32 get_IndexingSearchListSize()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Int32 get_IndexingSearchListSize();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Int32 get_QuantizationByteSize()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Int32 get_QuantizationByteSize();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Int32 IndexingSearchListSize[Newtonsoft.Json.JsonIgnoreAttribute()]": { + "Type": "Property", + "Attributes": [ + "JsonIgnoreAttribute" + ], + "MethodInfo": "Int32 IndexingSearchListSize;CanRead:True;CanWrite:True;Int32 get_IndexingSearchListSize();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_IndexingSearchListSize(Int32);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Int32 QuantizationByteSize[Newtonsoft.Json.JsonIgnoreAttribute()]": { + "Type": "Property", + "Attributes": [ + "JsonIgnoreAttribute" + ], + "MethodInfo": "Int32 QuantizationByteSize;CanRead:True;CanWrite:True;Int32 get_QuantizationByteSize();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_QuantizationByteSize(Int32);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Microsoft.Azure.Cosmos.VectorIndexType get_Type()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ @@ -824,11 +856,30 @@ ], "MethodInfo": "System.String Path;CanRead:True;CanWrite:True;System.String get_Path();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Path(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "System.String[] get_VectorIndexShardKey()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.String[] get_VectorIndexShardKey();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.String[] VectorIndexShardKey[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"vectorIndexShardKey\")]": { + "Type": "Property", + "Attributes": [ + "JsonPropertyAttribute" + ], + "MethodInfo": "System.String[] VectorIndexShardKey;CanRead:True;CanWrite:True;System.String[] get_VectorIndexShardKey();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_VectorIndexShardKey(System.String[]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Void .ctor()": { "Type": "Constructor", "Attributes": [], "MethodInfo": "[Void .ctor(), Void .ctor()]" }, + "Void set_IndexingSearchListSize(Int32)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_IndexingSearchListSize(Int32);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Void set_Path(System.String)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ @@ -836,12 +887,24 @@ ], "MethodInfo": "Void set_Path(System.String);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Void set_QuantizationByteSize(Int32)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_QuantizationByteSize(Int32);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Void set_Type(Microsoft.Azure.Cosmos.VectorIndexType)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], "MethodInfo": "Void set_Type(Microsoft.Azure.Cosmos.VectorIndexType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void set_VectorIndexShardKey(System.String[])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_VectorIndexShardKey(System.String[]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" } }, "NestedTypes": {} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosContainerSettingsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosContainerSettingsTests.cs index a12f37fcfa..86b80fabe3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosContainerSettingsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosContainerSettingsTests.cs @@ -229,15 +229,19 @@ public void ValidateVectorEmbeddingsAndIndexes() new Cosmos.VectorIndexPath() { Path = "/vector2", - Type = Cosmos.VectorIndexType.Flat, + Type = Cosmos.VectorIndexType.QuantizedFlat, + VectorIndexShardKey = new[] { "/Country" }, + QuantizationByteSize = 3, }, new Cosmos.VectorIndexPath() { Path = "/vector3", - Type = Cosmos.VectorIndexType.Flat, + Type = Cosmos.VectorIndexType.DiskANN, + VectorIndexShardKey = new[] { "/ZipCode" }, + QuantizationByteSize = 2, + IndexingSearchListSize = 5, } }, - }, }; @@ -254,9 +258,15 @@ public void ValidateVectorEmbeddingsAndIndexes() Assert.AreEqual("/vector1", vectorIndexes[0].Path); Assert.AreEqual(Cosmos.VectorIndexType.Flat, vectorIndexes[0].Type); Assert.AreEqual("/vector2", vectorIndexes[1].Path); - Assert.AreEqual(Cosmos.VectorIndexType.Flat, vectorIndexes[1].Type); + Assert.AreEqual(Cosmos.VectorIndexType.QuantizedFlat, vectorIndexes[1].Type); + Assert.AreEqual(3, vectorIndexes[1].QuantizationByteSize); + CollectionAssert.AreEqual(new string[] { "/Country" }, vectorIndexes[1].VectorIndexShardKey); + Assert.AreEqual("/vector3", vectorIndexes[2].Path); - Assert.AreEqual(Cosmos.VectorIndexType.Flat, vectorIndexes[2].Type); + Assert.AreEqual(Cosmos.VectorIndexType.DiskANN, vectorIndexes[2].Type); + Assert.AreEqual(2, vectorIndexes[2].QuantizationByteSize); + Assert.AreEqual(5, vectorIndexes[2].IndexingSearchListSize); + CollectionAssert.AreEqual(new string[] { "/ZipCode" }, vectorIndexes[2].VectorIndexShardKey); } private static string SerializeDocumentCollection(DocumentCollection collection) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/SettingsContractTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/SettingsContractTests.cs index 3224a6618c..e01643dcb7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/SettingsContractTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/SettingsContractTests.cs @@ -401,7 +401,7 @@ public void ContainerPropertiesDeserializeWithAdditionalDataTest() Assert.AreEqual(3, containerProperties.VectorEmbeddingPolicy.Embeddings.Count); Assert.AreEqual("/vector1", containerProperties.VectorEmbeddingPolicy.Embeddings[0].Path); Assert.AreEqual(Cosmos.VectorDataType.Float32, containerProperties.VectorEmbeddingPolicy.Embeddings[0].DataType); - Assert.AreEqual((ulong)1200, containerProperties.VectorEmbeddingPolicy.Embeddings[0].Dimensions); + Assert.AreEqual(1200, containerProperties.VectorEmbeddingPolicy.Embeddings[0].Dimensions); Assert.AreEqual(Cosmos.DistanceFunction.Cosine, containerProperties.VectorEmbeddingPolicy.Embeddings[0].DistanceFunction); Assert.AreEqual(2, containerProperties.ComputedProperties.Count); From 2af0b05c93fe51d4c811fb736aa00af737695d73 Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kolli Date: Fri, 18 Oct 2024 12:32:11 -0700 Subject: [PATCH 19/49] Azurecore: Fixes upgrading azure core dependency to latest (#4819) Azure.Core: Fixed upgrading azure core dependency to 1.44.1 Changes - ResourceType: Conflict between Azure.Core and Microsoft.Azure.Cosmos.Documents - Microsoft.Bcl.AsyncInterfaces: Azure.Core needs at-least 6.0.0 (as Azure core upgraded part of minor version, we are good as well) - Microsoft.Azure.Cosmos.Encryption.Custom: Direct dependency on Azure.Core removed (now its transitive) - Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests: `NU1903` added Newtonsoft.Json dependency - Performance project: `NU1903` Newtonsoft dependency upgraded --- .../src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj | 3 +-- ....Azure.Cosmos.Encryption.Custom.Performance.Tests.csproj | 1 + .../src/Microsoft.Azure.Cosmos.Encryption.csproj | 1 - Microsoft.Azure.Cosmos/src/CosmosClient.cs | 1 + Microsoft.Azure.Cosmos/src/DocumentClient.cs | 1 + Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj | 4 ++-- .../Microsoft.Azure.Cosmos.EmulatorTests.csproj | 1 - .../Microsoft.Azure.Cosmos.Performance.Tests.csproj | 3 ++- .../Contracts/DirectContractTests.cs | 6 +++--- .../CosmosAuthorizationTests.cs | 1 + .../Microsoft.Azure.Cosmos.Tests.csproj | 1 - 11 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj index cca4d719bf..3b94db54c2 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj @@ -46,10 +46,9 @@ - - + diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests.csproj b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests.csproj index edcdac998b..c9cdfd365e 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests.csproj +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests/Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests.csproj @@ -18,6 +18,7 @@ + diff --git a/Microsoft.Azure.Cosmos.Encryption/src/Microsoft.Azure.Cosmos.Encryption.csproj b/Microsoft.Azure.Cosmos.Encryption/src/Microsoft.Azure.Cosmos.Encryption.csproj index 3421b335aa..d21b605ea9 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/Microsoft.Azure.Cosmos.Encryption.csproj +++ b/Microsoft.Azure.Cosmos.Encryption/src/Microsoft.Azure.Cosmos.Encryption.csproj @@ -42,7 +42,6 @@ - diff --git a/Microsoft.Azure.Cosmos/src/CosmosClient.cs b/Microsoft.Azure.Cosmos/src/CosmosClient.cs index bbe53742c7..3f9770ac64 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClient.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClient.cs @@ -23,6 +23,7 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Cosmos.Tracing.TraceData; using Microsoft.Azure.Documents; + using ResourceType = Documents.ResourceType; /// /// Provides a client-side logical representation of the Azure Cosmos DB account. diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index bd29440d12..144957de21 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -32,6 +32,7 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Documents.FaultInjection; using Microsoft.Azure.Documents.Routing; using Newtonsoft.Json; + using ResourceType = Documents.ResourceType; /// /// Provides a client-side logical representation for the Azure Cosmos DB service. diff --git a/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj b/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj index 55b2fba26a..5224f9632f 100644 --- a/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj +++ b/Microsoft.Azure.Cosmos/src/Microsoft.Azure.Cosmos.csproj @@ -112,11 +112,11 @@ - + - + diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 4f70def22d..37dab8a9ec 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -65,7 +65,6 @@ - diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj index b1f0419dee..736b507310 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj @@ -1,4 +1,4 @@ - + Exe @@ -21,6 +21,7 @@ + diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DirectContractTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DirectContractTests.cs index e5e8901788..111856813d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DirectContractTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DirectContractTests.cs @@ -119,7 +119,7 @@ public void ProjectPackageDependenciesTest() { "System.Collections.Immutable", new Version(1, 7, 0) }, { "System.Numerics.Vectors", new Version(4, 5, 0) }, { "Newtonsoft.Json", new Version(10, 0, 2) }, - { "Microsoft.Bcl.AsyncInterfaces", new Version(1, 0, 0) }, + { "Microsoft.Bcl.AsyncInterfaces", new Version(6, 0, 0) }, { "System.Configuration.ConfigurationManager", new Version(6, 0, 0) }, { "System.Memory", new Version(4, 5, 5) }, { "System.Buffers", new Version(4, 5, 1) }, @@ -127,7 +127,7 @@ public void ProjectPackageDependenciesTest() { "System.Threading.Tasks.Extensions", new Version(4, 5, 4) }, { "System.ValueTuple", new Version(4, 5, 0) }, { "Microsoft.Bcl.HashCode", new Version(1, 1, 0) }, - { "Azure.Core", new Version(1, 19, 0) }, + { "Azure.Core", new Version(1, 44, 1) }, { "System.Diagnostics.DiagnosticSource", new Version(8, 0, 1) }, { "System.Net.Http", new Version(4, 3, 4) }, { "System.Text.RegularExpressions", new Version(4, 3, 1) }, @@ -142,7 +142,7 @@ public void ProjectPackageDependenciesTest() } /// - /// Ignoring HybridRow dependency check as it is using System.Runtime.CompilerServices.Unsafe 4.5.3 and Azure.Core 1.19.0 needs >=4.6.0 version of the same + /// Ignoring HybridRow dependency check as it is using System.Runtime.CompilerServices.Unsafe 4.5.3 and Azure.Core 1.44.1 needs >=4.6.0 version of the same /// [TestMethod] public void PackageDependenciesTest() diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosAuthorizationTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosAuthorizationTests.cs index 339e61914c..ba71293c41 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosAuthorizationTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosAuthorizationTests.cs @@ -21,6 +21,7 @@ namespace Microsoft.Azure.Cosmos.Tests using Microsoft.Azure.Documents.Collections; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; + using ResourceType = Documents.ResourceType; [TestClass] public class CosmosAuthorizationTests diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj index fd41eef471..40aff3569c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj @@ -49,7 +49,6 @@ - From 8d80c1c0532a308354b7bec8859b8726b87b9c1a Mon Sep 17 00:00:00 2001 From: Kiran Kumar Kolli Date: Fri, 18 Oct 2024 13:02:26 -0700 Subject: [PATCH 20/49] DeleteAllItemsByPartitionKeyStreamAsync: Adds DeleteAllItemsByPartitionKeyStreamAsync API to GA (#4814) DeleteAllItemsByPartitionKeyStreamAsync: Adds DeleteAllItemsByPartitionKeyStreamAsync API to GA Changes - DeleteAllItemsByPartitionKeyStreamAsync: Marked virtual with default NotSupportedException (Dependent libraries will continue to work) - Encryption package related changes will be in a follow-up PR after the package is published --- .../src/Resource/Container/Container.cs | 9 ++++++--- .../src/Resource/Container/ContainerCore.Items.cs | 2 +- .../src/Resource/Container/ContainerInlineCore.cs | 2 +- .../src/Resource/Container/ContainerInternal.cs | 5 ----- .../Contracts/DotNetPreviewSDKAPI.json | 5 ----- .../Contracts/DotNetSDKAPI.json | 5 +++++ 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs index 6b3f78f91a..379ee407f3 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/Container.cs @@ -1679,7 +1679,6 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManu string processorName, ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate); -#if PREVIEW /// /// Deletes all items in the Container with the specified value. /// Starts an asynchronous Cosmos DB background operation which deletes all items in the Container with the specified value. @@ -1691,11 +1690,15 @@ public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManu /// /// A containing a . /// - public abstract Task DeleteAllItemsByPartitionKeyStreamAsync( + public virtual Task DeleteAllItemsByPartitionKeyStreamAsync( Cosmos.PartitionKey partitionKey, RequestOptions requestOptions = null, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default) + { + throw new NotSupportedException("Deriving classes are expected to override this method with a valid implementation"); + } +#if PREVIEW /// /// Gets the list of Partition Key Range identifiers for a . /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 34a4ee0b3a..48567603f6 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -985,7 +985,7 @@ public Task DeleteAllItemsByPartitionKeyStreamAsync( Cosmos.PartitionKey partitionKey, ITrace trace, RequestOptions requestOptions = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { PartitionKey? resultingPartitionKey = requestOptions != null && requestOptions.IsEffectivePartitionKeyRouting ? null : (PartitionKey?)partitionKey; ContainerCore.ValidatePartitionKey(resultingPartitionKey, requestOptions); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs index 11a3b866c4..44a409eed1 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInlineCore.cs @@ -666,7 +666,7 @@ public override IAsyncEnumerable> GetReadFeedAsyncEnumera public override Task DeleteAllItemsByPartitionKeyStreamAsync( Cosmos.PartitionKey partitionKey, RequestOptions requestOptions = null, - CancellationToken cancellationToken = default(CancellationToken)) + CancellationToken cancellationToken = default) { return this.ClientContext.OperationHelperAsync( operationName: nameof(DeleteAllItemsByPartitionKeyStreamAsync), diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs index 84fb20480c..c5545ecf9e 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerInternal.cs @@ -139,11 +139,6 @@ public abstract Task PatchItemStreamAsync( CancellationToken cancellationToken = default); #if !PREVIEW - public abstract Task DeleteAllItemsByPartitionKeyStreamAsync( - Cosmos.PartitionKey partitionKey, - RequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - public abstract Task> GetPartitionKeyRangesAsync( FeedRange feedRange, CancellationToken cancellationToken = default); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index 94e38f480d..6ca79014f5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -318,11 +318,6 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes[T](System.String, ChangeFeedHandler`1);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" }, - "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] DeleteAllItemsByPartitionKeyStreamAsync(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.RequestOptions, System.Threading.CancellationToken)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] DeleteAllItemsByPartitionKeyStreamAsync(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.RequestOptions, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "System.Threading.Tasks.Task`1[System.Boolean] IsFeedRangePartOfAsync(Microsoft.Azure.Cosmos.FeedRange, Microsoft.Azure.Cosmos.FeedRange, System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index af4dea3f73..a594650bcc 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -1582,6 +1582,11 @@ "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] CreateItemStreamAsync(System.IO.Stream, Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.ItemRequestOptions, System.Threading.CancellationToken);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] DeleteAllItemsByPartitionKeyStreamAsync(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.RequestOptions, System.Threading.CancellationToken)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] DeleteAllItemsByPartitionKeyStreamAsync(Microsoft.Azure.Cosmos.PartitionKey, Microsoft.Azure.Cosmos.RequestOptions, System.Threading.CancellationToken);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] DeleteContainerStreamAsync(Microsoft.Azure.Cosmos.ContainerRequestOptions, System.Threading.CancellationToken)": { "Type": "Method", "Attributes": [], From c6a33b0efc306976021873878f79753b6129e6f5 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Tue, 22 Oct 2024 16:51:59 +0530 Subject: [PATCH 21/49] rename file --- .../src/Resource/ClientContextCore.cs | 5 ++-- ...csConstant.cs => CosmosDbClientMetrics.cs} | 4 +-- .../OpenTelemetry/CosmosOperationMeter.cs | 26 +++++++++---------- .../Metrics/OpenTelemetryMetricsTest.cs | 14 +++++----- 4 files changed, 25 insertions(+), 24 deletions(-) rename Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/{CosmosDbClientMetricsConstant.cs => CosmosDbClientMetrics.cs} (98%) diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index c7ece30d2c..109315acf8 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -525,8 +525,9 @@ private async Task RunWithDiagnosticsHelperAsync( try { TResult result = await task(trace).ConfigureAwait(false); - // Checks if OpenTelemetry is configured for this operation. - if (openTelemetry != null) + // Checks if OpenTelemetry is configured for this operation and either Trace or Metrics are enabled by customer + if (openTelemetry != null && + (!this.ClientOptions.CosmosClientTelemetryOptions.DisableDistributedTracing || this.ClientOptions.CosmosClientTelemetryOptions.IsClientMetricsEnabled)) { // Extracts and records telemetry data from the result of the operation. OpenTelemetryAttributes response = openTelemetry?.Item2(result); diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetricsConstant.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs similarity index 98% rename from Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetricsConstant.cs rename to Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs index adc35e7e45..f88a3a9f03 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetricsConstant.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs @@ -5,11 +5,11 @@ namespace Microsoft.Azure.Cosmos { /// - /// The CosmosDbClientMetricsConstant class provides constants related to OpenTelemetry metrics for Azure Cosmos DB. + /// The CosmosDbClientMetrics class provides constants related to OpenTelemetry metrics for Azure Cosmos DB. /// These metrics are useful for tracking various aspects of Cosmos DB client operations and compliant with Open Telemetry Semantic Conventions /// It defines standardized names, units, descriptions, and histogram buckets for measuring and monitoring performance through OpenTelemetry. /// - public sealed class CosmosDbClientMetricsConstant + public sealed class CosmosDbClientMetrics { /// /// OperationMetrics diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index e08cc82521..d097ae72a2 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -17,7 +17,7 @@ internal static class CosmosOperationMeter /// /// Meter instance for capturing various metrics related to Cosmos DB operations. /// - internal static Meter OperationMeter = new Meter(CosmosDbClientMetricsConstant.OperationMetrics.MeterName, CosmosDbClientMetricsConstant.OperationMetrics.Version); + internal static Meter OperationMeter = new Meter(CosmosDbClientMetrics.OperationMetrics.MeterName, CosmosDbClientMetrics.OperationMetrics.Version); /// /// Histogram to record request latency (in seconds) for Cosmos DB operations. @@ -49,21 +49,21 @@ internal static class CosmosOperationMeter /// internal static void Initialize() { - CosmosOperationMeter.RequestLatencyHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetricsConstant.OperationMetrics.Name.Latency, - unit: CosmosDbClientMetricsConstant.OperationMetrics.Unit.Sec, - description: CosmosDbClientMetricsConstant.OperationMetrics.Description.Latency); + CosmosOperationMeter.RequestLatencyHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetrics.OperationMetrics.Name.Latency, + unit: CosmosDbClientMetrics.OperationMetrics.Unit.Sec, + description: CosmosDbClientMetrics.OperationMetrics.Description.Latency); - CosmosOperationMeter.RequestUnitsHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetricsConstant.OperationMetrics.Name.RequestCharge, - unit: CosmosDbClientMetricsConstant.OperationMetrics.Unit.RequestUnit, - description: CosmosDbClientMetricsConstant.OperationMetrics.Description.RequestCharge); + CosmosOperationMeter.RequestUnitsHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetrics.OperationMetrics.Name.RequestCharge, + unit: CosmosDbClientMetrics.OperationMetrics.Unit.RequestUnit, + description: CosmosDbClientMetrics.OperationMetrics.Description.RequestCharge); - CosmosOperationMeter.ActualItemHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetricsConstant.OperationMetrics.Name.RowCount, - unit: CosmosDbClientMetricsConstant.OperationMetrics.Unit.Count, - description: CosmosDbClientMetricsConstant.OperationMetrics.Description.RowCount); + CosmosOperationMeter.ActualItemHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetrics.OperationMetrics.Name.RowCount, + unit: CosmosDbClientMetrics.OperationMetrics.Unit.Count, + description: CosmosDbClientMetrics.OperationMetrics.Description.RowCount); - CosmosOperationMeter.ActiveInstanceCounter ??= OperationMeter.CreateUpDownCounter(name: CosmosDbClientMetricsConstant.OperationMetrics.Name.ActiveInstances, - unit: CosmosDbClientMetricsConstant.OperationMetrics.Unit.Count, - description: CosmosDbClientMetricsConstant.OperationMetrics.Description.ActiveInstances); + CosmosOperationMeter.ActiveInstanceCounter ??= OperationMeter.CreateUpDownCounter(name: CosmosDbClientMetrics.OperationMetrics.Name.ActiveInstances, + unit: CosmosDbClientMetrics.OperationMetrics.Unit.Count, + description: CosmosDbClientMetrics.OperationMetrics.Description.ActiveInstances); IsEnabled = true; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs index bf732c43e0..be427c38ca 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -46,24 +46,24 @@ public async Task OperationLevelMetricsGenerationTest() MeterProvider meterProvider = Sdk .CreateMeterProviderBuilder() .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Azure Cosmos DB Operation Level Metrics")) - .AddMeter(CosmosDbClientMetricsConstant.OperationMetrics.MeterName) + .AddMeter(CosmosDbClientMetrics.OperationMetrics.MeterName) .AddView( - instrumentName: CosmosDbClientMetricsConstant.OperationMetrics.Name.RequestCharge, + instrumentName: CosmosDbClientMetrics.OperationMetrics.Name.RequestCharge, metricStreamConfiguration: new ExplicitBucketHistogramConfiguration { - Boundaries = CosmosDbClientMetricsConstant.HistogramBuckets.RequestUnitBuckets + Boundaries = CosmosDbClientMetrics.HistogramBuckets.RequestUnitBuckets }) .AddView( - instrumentName: CosmosDbClientMetricsConstant.OperationMetrics.Name.Latency, + instrumentName: CosmosDbClientMetrics.OperationMetrics.Name.Latency, metricStreamConfiguration: new ExplicitBucketHistogramConfiguration { - Boundaries = CosmosDbClientMetricsConstant.HistogramBuckets.RequestLatencyBuckets + Boundaries = CosmosDbClientMetrics.HistogramBuckets.RequestLatencyBuckets }) .AddView( - instrumentName: CosmosDbClientMetricsConstant.OperationMetrics.Name.RowCount, + instrumentName: CosmosDbClientMetrics.OperationMetrics.Name.RowCount, metricStreamConfiguration: new ExplicitBucketHistogramConfiguration { - Boundaries = CosmosDbClientMetricsConstant.HistogramBuckets.RowCountBuckets + Boundaries = CosmosDbClientMetrics.HistogramBuckets.RowCountBuckets }) .AddReader(new PeriodicExportingMetricReader(exporter: new CustomMetricExporter(this.manualResetEventSlim), exportIntervalMilliseconds: AggregatingInterval)) .Build(); From 4fd11934446e74a1944a1893ab55ea823c0c9396 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 23 Oct 2024 17:24:28 +0530 Subject: [PATCH 22/49] refactor code --- .../OpenTelemetry/CosmosOperationMeter.cs | 55 +++++++++---------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index d097ae72a2..ca3cd8c545 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System; using System.Collections.Generic; using System.Diagnostics.Metrics; + using System.Net; /// /// CosmosOperationMeter is a utility class responsible for collecting and recording telemetry metrics related to Cosmos DB operations. @@ -91,42 +92,36 @@ internal static void RecordTelemetry(string operationName, Func[]> dimensionsFunc = () => { - List> dimensions = new List>() - { - { new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb)}, - { new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName)}, - { new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName)}, - { new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName.Host)}, - { new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName.Port)}, - { new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, operationName)} - }; + HttpStatusCode? statusCode = attributes?.StatusCode; + int? subStatusCode = attributes?.SubStatusCode; + string consistencyLevel = attributes?.ConsistencyLevel; - if (attributes != null) + if (ex is not null && ex is CosmosException cosmosException) { - dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)attributes.StatusCode)); - dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, (int)attributes.SubStatusCode)); - dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes.ConsistencyLevel)); + statusCode = cosmosException.StatusCode; + subStatusCode = cosmosException.SubStatusCode; + consistencyLevel = cosmosException.Headers.ConsistencyLevel; } - if (ex != null) + return new KeyValuePair[] { - if (ex is CosmosException cosmosException) - { - dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)cosmosException.StatusCode)); - dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, (int)cosmosException.SubStatusCode)); - dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, cosmosException.Headers.ConsistencyLevel)); - } - - dimensions.Add(new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, ex.Message)); - } - - return dimensions.ToArray(); + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), + new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName), + new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName.Port), + new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, operationName), + new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)statusCode.Value), + new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, subStatusCode), + new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, consistencyLevel), + new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, ex.Message) + }; }; if (attributes != null) { - CosmosOperationMeter.RecordActualItemCount(attributes.ItemCount, dimensionsFunc); - CosmosOperationMeter.RecordRequestUnit(attributes.RequestCharge, dimensionsFunc); + CosmosOperationMeter.RecordActualItemCount(attributes?.ItemCount, dimensionsFunc); + CosmosOperationMeter.RecordRequestUnit(attributes?.RequestCharge, dimensionsFunc); CosmosOperationMeter.RecordRequestLatency(attributes.Diagnostics?.GetClientElapsedTime(), dimensionsFunc); } @@ -145,7 +140,7 @@ internal static void RecordTelemetry(string operationName, /// A function providing telemetry dimensions for the metric. internal static void RecordActualItemCount(string actualItemCount, Func[]> dimensionsFunc) { - if (CosmosOperationMeter.ActualItemHistogram == null || !CosmosOperationMeter.ActualItemHistogram.Enabled || string.IsNullOrEmpty(actualItemCount)) + if (!IsEnabled || CosmosOperationMeter.ActualItemHistogram == null || !CosmosOperationMeter.ActualItemHistogram.Enabled || string.IsNullOrEmpty(actualItemCount)) { return; } @@ -160,7 +155,7 @@ internal static void RecordActualItemCount(string actualItemCount, FuncA function providing telemetry dimensions for the metric. internal static void RecordRequestUnit(double? requestCharge, Func[]> dimensionsFunc) { - if (CosmosOperationMeter.RequestUnitsHistogram == null || !CosmosOperationMeter.RequestUnitsHistogram.Enabled || !requestCharge.HasValue) + if (!IsEnabled || CosmosOperationMeter.RequestUnitsHistogram == null || !CosmosOperationMeter.RequestUnitsHistogram.Enabled || !requestCharge.HasValue) { return; } @@ -175,7 +170,7 @@ internal static void RecordRequestUnit(double? requestCharge, FuncA function providing telemetry dimensions for the metric. internal static void RecordRequestLatency(TimeSpan? requestLatency, Func[]> dimensionsFunc) { - if (CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled || !requestLatency.HasValue) + if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled || !requestLatency.HasValue) { return; } From 033fda4639fe685eb7a68cb24dcc0b45ddef82c9 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 23 Oct 2024 17:28:20 +0530 Subject: [PATCH 23/49] refactor code --- .../src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index ca3cd8c545..4f85ae579a 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -50,6 +50,12 @@ internal static class CosmosOperationMeter /// internal static void Initialize() { + // If already initialized, do not initialize again + if (IsEnabled) + { + return; + } + CosmosOperationMeter.RequestLatencyHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetrics.OperationMetrics.Name.Latency, unit: CosmosDbClientMetrics.OperationMetrics.Unit.Sec, description: CosmosDbClientMetrics.OperationMetrics.Description.Latency); From e92477a0d2ed18e4a3c20d5e2288264fbce941e5 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 23 Oct 2024 23:44:39 +0530 Subject: [PATCH 24/49] perf tests --- .../Benchmarks/MockedItemBenchmark.cs | 17 ++++-- .../Benchmarks/MockedItemBenchmarkHelper.cs | 13 ++++- .../Bulk/MockedItemBulkBenchmark.cs | 6 ++- ...soft.Azure.Cosmos.Performance.Tests.csproj | 3 +- .../Mocks/MockDocumentClient.cs | 52 +------------------ .../Program.cs | 13 +++++ 6 files changed, 44 insertions(+), 60 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/MockedItemBenchmark.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/MockedItemBenchmark.cs index f660ed930d..51070253d6 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/MockedItemBenchmark.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/MockedItemBenchmark.cs @@ -15,7 +15,8 @@ public enum ScenarioType OfT = 1, OfTCustom = 2, OfTWithDiagnosticsToString = 3, - OfTWithClientTelemetryEnabled = 4 + OfTWithDistributedTracingEnabled = 4, + OfTWithClientMetricsEnabled = 5 } [Config(typeof(SdkBenchmarkConfiguration))] @@ -32,11 +33,16 @@ public class MockedItemBenchmark : IItemBenchmark BenchmarkHelper = new MockedItemBenchmarkHelper( useCustomSerializer: false, includeDiagnosticsToString: true) }, - new MockedItemOfTBenchmark() { + new MockedItemOfTBenchmark() { BenchmarkHelper = new MockedItemBenchmarkHelper( - useCustomSerializer: false, + useCustomSerializer: false, + includeDiagnosticsToString: false, + isDistributedTracingEnabled: true) }, + new MockedItemOfTBenchmark() { + BenchmarkHelper = new MockedItemBenchmarkHelper( + useCustomSerializer: false, includeDiagnosticsToString: false, - isClientTelemetryEnabled: true) } + isClientMetricsEnabled: true) } }; [Params( @@ -44,7 +50,8 @@ public class MockedItemBenchmark : IItemBenchmark ScenarioType.OfT, ScenarioType.OfTWithDiagnosticsToString, ScenarioType.OfTCustom, - ScenarioType.OfTWithClientTelemetryEnabled)] + ScenarioType.OfTWithDistributedTracingEnabled, + ScenarioType.OfTWithClientMetricsEnabled)] public ScenarioType Type { get; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/MockedItemBenchmarkHelper.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/MockedItemBenchmarkHelper.cs index f50d082b53..c3d473f079 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/MockedItemBenchmarkHelper.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/MockedItemBenchmarkHelper.cs @@ -34,9 +34,18 @@ public MockedItemBenchmarkHelper( bool useCustomSerializer = false, bool includeDiagnosticsToString = false, bool useBulk = false, - bool? isClientTelemetryEnabled = null) + bool isDistributedTracingEnabled = false, + bool isClientMetricsEnabled = false) { - this.TestClient = MockDocumentClient.CreateMockCosmosClient(useCustomSerializer, isClientTelemetryEnabled, (builder) => builder.WithBulkExecution(useBulk)); + this.TestClient = MockDocumentClient.CreateMockCosmosClient(useCustomSerializer, + (builder) => builder + .WithBulkExecution(useBulk) + .WithClientTelemetryOptions(new CosmosClientTelemetryOptions() + { + DisableDistributedTracing = !isDistributedTracingEnabled, + IsClientMetricsEnabled = isClientMetricsEnabled + })); + this.TestContainer = this.TestClient.GetDatabase("myDB").GetContainer("myColl"); this.IncludeDiagnosticsToString = includeDiagnosticsToString; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemBulkBenchmark.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemBulkBenchmark.cs index 72a7e5ef24..068da40280 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemBulkBenchmark.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemBulkBenchmark.cs @@ -16,14 +16,16 @@ public class MockedItemBulkBenchmark : IItemBulkBenchmark new MockedItemOfTBulkBenchmark() { BenchmarkHelper = new MockedItemBenchmarkHelper(useBulk: true) }, new MockedItemOfTBulkBenchmark() { BenchmarkHelper = new MockedItemBenchmarkHelper(useCustomSerializer: true, useBulk: true) }, new MockedItemOfTBulkBenchmark() { BenchmarkHelper = new MockedItemBenchmarkHelper(useCustomSerializer: false, includeDiagnosticsToString: true, useBulk: true) }, - new MockedItemOfTBulkBenchmark() { BenchmarkHelper = new MockedItemBenchmarkHelper(useCustomSerializer: false, includeDiagnosticsToString: true, isClientTelemetryEnabled:true, useBulk: true) } + new MockedItemOfTBulkBenchmark() { BenchmarkHelper = new MockedItemBenchmarkHelper(useCustomSerializer: false, includeDiagnosticsToString: true, isDistributedTracingEnabled:true, useBulk: true) }, + new MockedItemOfTBulkBenchmark() { BenchmarkHelper = new MockedItemBenchmarkHelper(useCustomSerializer: false, includeDiagnosticsToString: true, isClientMetricsEnabled:true, useBulk: true) } }; [Params(ScenarioType.Stream, ScenarioType.OfT, ScenarioType.OfTWithDiagnosticsToString, ScenarioType.OfTCustom, - ScenarioType.OfTWithClientTelemetryEnabled)] + ScenarioType.OfTWithDistributedTracingEnabled, + ScenarioType.OfTWithClientMetricsEnabled)] public ScenarioType Type { get; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj index 736b507310..cf55ea0ab9 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj @@ -17,11 +17,12 @@ - + + 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 c6b59b8cea..a383fb6bf5 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 @@ -41,66 +41,18 @@ internal class MockDocumentClient : DocumentClient, ICosmosAuthorizationTokenPro }; string[] dummyHeaderNames; - private IComputeHash authKeyHashFunction; + private readonly IComputeHash authKeyHashFunction; public static CosmosClient CreateMockCosmosClient( bool useCustomSerializer = false, - bool? isClientTelemetryEnabled = null, Action < CosmosClientBuilder> customizeClientBuilder = null) { - ConnectionPolicy policy = new ConnectionPolicy(); - - if (isClientTelemetryEnabled.HasValue) - { - policy = new ConnectionPolicy - { - CosmosClientTelemetryOptions = new CosmosClientTelemetryOptions - { - DisableSendingMetricsToService = !isClientTelemetryEnabled.Value - } - }; - } - - MockDocumentClient documentClient = new MockDocumentClient(policy); + MockDocumentClient documentClient = new MockDocumentClient(new ConnectionPolicy()); CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder("http://localhost", Convert.ToBase64String(Guid.NewGuid().ToByteArray())); cosmosClientBuilder.WithConnectionModeDirect(); Uri telemetryServiceEndpoint = new Uri("https://dummy.endpoint.com/"); - if (isClientTelemetryEnabled.HasValue) - { - // mock external calls - HttpClientHandlerHelper httpHandler = new HttpClientHandlerHelper - { - RequestCallBack = (request, cancellation) => - { - if (request.RequestUri.AbsoluteUri.Equals(telemetryServiceEndpoint.AbsoluteUri)) - { - return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NoContent)); // In Emulator test, send hardcoded response status code as there is no real communication happens with client telemetry service - } - else if (request.RequestUri.AbsoluteUri.Contains(Paths.ClientConfigPathSegment)) - { - HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK); - AccountClientConfiguration clientConfigProperties = new AccountClientConfiguration - { - ClientTelemetryConfiguration = new ClientTelemetryConfiguration - { - IsEnabled = isClientTelemetryEnabled.Value, - Endpoint = isClientTelemetryEnabled.Value?telemetryServiceEndpoint.AbsoluteUri: null - } - }; - string payload = JsonConvert.SerializeObject(clientConfigProperties); - result.Content = new StringContent(payload, Encoding.UTF8, "application/json"); - return Task.FromResult(result); - } - - return null; - } - }; - - cosmosClientBuilder.WithHttpClientFactory(() => new HttpClient(httpHandler)); - } - customizeClientBuilder?.Invoke(cosmosClientBuilder); if (useCustomSerializer) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Program.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Program.cs index 9b109428b7..e685d9a718 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Program.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Program.cs @@ -7,6 +7,9 @@ namespace Microsoft.Azure.Cosmos.Performance.Tests using System.Collections.Generic; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; + using OpenTelemetry; + using OpenTelemetry.Metrics; + using OpenTelemetry.Trace; class Program { @@ -22,6 +25,16 @@ static int Main(string[] args) bool validateBaseline = argsList.Remove("--BaselineValidation"); string[] updatedArgs = argsList.ToArray(); + using TracerProvider tracebuilder = Sdk.CreateTracerProviderBuilder() + .AddSource("Azure.Cosmos.*") + .AddConsoleExporter() + .Build(); + + using MeterProvider metricsBuilder = Sdk.CreateMeterProviderBuilder() + .AddMeter("Azure.Cosmos.Client.*") + .AddConsoleExporter() + .Build(); + if (validateBaseline) { SortedDictionary operationToAllocatedMemory = new SortedDictionary(); From cf5bb03490ced5469243758f91bed98c7a587e7b Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Thu, 24 Oct 2024 15:31:34 +0530 Subject: [PATCH 25/49] updated contracts --- .../OTelExporter/CustomMetricExporter.cs | 26 +++++++++++ .../OTelExporter/CustomTraceExporter.cs | 43 ++++++++++++++++++ .../Program.cs | 6 ++- .../Contracts/DotNetSDKAPI.json | 44 +++++++++---------- 4 files changed, 95 insertions(+), 24 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/OTelExporter/CustomMetricExporter.cs create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/OTelExporter/CustomTraceExporter.cs diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/OTelExporter/CustomMetricExporter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/OTelExporter/CustomMetricExporter.cs new file mode 100644 index 0000000000..a5daf11f6e --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/OTelExporter/CustomMetricExporter.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics +{ + using OpenTelemetry.Metrics; + using OpenTelemetry; + + public class CustomMetricExporter : BaseExporter + { + public CustomMetricExporter() + { + } + + // This method will be called periodically by OpenTelemetry SDK + public override ExportResult Export(in Batch batch) + { + //NoOP + return ExportResult.Success; + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/OTelExporter/CustomTraceExporter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/OTelExporter/CustomTraceExporter.cs new file mode 100644 index 0000000000..95c27c0357 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/OTelExporter/CustomTraceExporter.cs @@ -0,0 +1,43 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Tracing +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using OpenTelemetry; + using OpenTelemetry.Trace; + + internal class CustomTraceExporter : BaseExporter + { + private readonly string _name; + + public static List CollectedActivities; + + public CustomTraceExporter() + { + } + + public override ExportResult Export(in Batch batch) + { + // NoOp + + return ExportResult.Success; + } + } + + internal static class OTelExtensions + { + public static TracerProviderBuilder AddCustomOtelExporter(this TracerProviderBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.AddProcessor(new SimpleActivityExportProcessor(new CustomTraceExporter())); + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Program.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Program.cs index e685d9a718..aa0bf561ba 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Program.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Program.cs @@ -7,6 +7,8 @@ namespace Microsoft.Azure.Cosmos.Performance.Tests using System.Collections.Generic; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; + using Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics; + using Microsoft.Azure.Cosmos.Tracing; using OpenTelemetry; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; @@ -27,12 +29,12 @@ static int Main(string[] args) using TracerProvider tracebuilder = Sdk.CreateTracerProviderBuilder() .AddSource("Azure.Cosmos.*") - .AddConsoleExporter() + .AddCustomOtelExporter() .Build(); using MeterProvider metricsBuilder = Sdk.CreateMeterProviderBuilder() .AddMeter("Azure.Cosmos.Client.*") - .AddConsoleExporter() + .AddReader(new PeriodicExportingMetricReader(exporter: new CustomMetricExporter(), 10000)) .Build(); if (validateBaseline) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index a594650bcc..31a5b49564 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -3309,15 +3309,15 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+HistogramBuckets": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+HistogramBuckets": { "Type": "NestedType", "Attributes": [], "MethodInfo": null }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics": { "Type": "NestedType", "Attributes": [], "MethodInfo": null @@ -3329,7 +3329,7 @@ } }, "NestedTypes": { - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+HistogramBuckets;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+HistogramBuckets;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "Double[] RequestLatencyBuckets": { @@ -3350,20 +3350,20 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Description": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Description": { "Type": "NestedType", "Attributes": [], "MethodInfo": null }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Name": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Name": { "Type": "NestedType", "Attributes": [], "MethodInfo": null }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Unit": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Unit": { "Type": "NestedType", "Attributes": [], "MethodInfo": null @@ -3380,7 +3380,7 @@ } }, "NestedTypes": { - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "System.String ActiveInstances": { @@ -3406,7 +3406,7 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "System.String ActiveInstances": { @@ -3432,7 +3432,7 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "System.String Count": { @@ -3457,7 +3457,7 @@ } } }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+HistogramBuckets;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+HistogramBuckets;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "Double[] RequestLatencyBuckets": { @@ -3478,20 +3478,20 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Description": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Description": { "Type": "NestedType", "Attributes": [], "MethodInfo": null }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Name": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Name": { "Type": "NestedType", "Attributes": [], "MethodInfo": null }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Unit": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Unit": { "Type": "NestedType", "Attributes": [], "MethodInfo": null @@ -3508,7 +3508,7 @@ } }, "NestedTypes": { - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "System.String ActiveInstances": { @@ -3534,7 +3534,7 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "System.String ActiveInstances": { @@ -3560,7 +3560,7 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "System.String Count": { @@ -3583,7 +3583,7 @@ } } }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Description;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "System.String ActiveInstances": { @@ -3609,7 +3609,7 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Name;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "System.String ActiveInstances": { @@ -3635,7 +3635,7 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosDbClientMetricsConstant+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics+Unit;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:True;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "System.String Count": { From 63389080fa3366ec102d037dd692d1e47ea5a91c Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 25 Oct 2024 01:12:39 +0530 Subject: [PATCH 26/49] code refactor --- .../Contracts/BenchmarkResults.json | 137 ++++++++---------- 1 file changed, 60 insertions(+), 77 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/BenchmarkResults.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/BenchmarkResults.json index e11a346aff..9f26553a93 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/BenchmarkResults.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/BenchmarkResults.json @@ -1,79 +1,62 @@ { - "MasterKeyAuthorizationBenchmark.CreateSignatureGeneration;": 554, - "MasterKeyAuthorizationBenchmark.ReadSignatureGeneration;": 544, - "MockedItemBenchmark.CreateItem;[Type=OfT]": 37261.25, - "MockedItemBenchmark.CreateItem;[Type=OfTCustom]": 37285, - "MockedItemBenchmark.CreateItem;[Type=OfTWithClientTelemetryEnabled]": 37280.25, - "MockedItemBenchmark.CreateItem;[Type=OfTWithDiagnosticsToString]": 58900.5, - "MockedItemBenchmark.CreateItem;[Type=Stream]": 25212.75, - "MockedItemBenchmark.DeleteItemExists;[Type=OfT]": 33015.25, - "MockedItemBenchmark.DeleteItemExists;[Type=OfTCustom]": 33007.75, - "MockedItemBenchmark.DeleteItemExists;[Type=OfTWithClientTelemetryEnabled]": 33015.75, - "MockedItemBenchmark.DeleteItemExists;[Type=OfTWithDiagnosticsToString]": 54402.75, - "MockedItemBenchmark.DeleteItemExists;[Type=Stream]": 25229.25, - "MockedItemBenchmark.DeleteItemNotExists;[Type=OfT]": 43035, - "MockedItemBenchmark.DeleteItemNotExists;[Type=OfTCustom]": 43030.5, - "MockedItemBenchmark.DeleteItemNotExists;[Type=OfTWithClientTelemetryEnabled]": 43034.25, - "MockedItemBenchmark.DeleteItemNotExists;[Type=OfTWithDiagnosticsToString]": 63338, - "MockedItemBenchmark.DeleteItemNotExists;[Type=Stream]": 38373.5, - "MockedItemBenchmark.QuerySinglePartitionMultiplePages;[Type=OfT]": 13293777.25, - "MockedItemBenchmark.QuerySinglePartitionMultiplePages;[Type=OfTCustom]": 13298034.75, - "MockedItemBenchmark.QuerySinglePartitionMultiplePages;[Type=OfTWithClientTelemetryEnabled]": 13298733.75, - "MockedItemBenchmark.QuerySinglePartitionMultiplePages;[Type=OfTWithDiagnosticsToString]": 13517039.75, - "MockedItemBenchmark.QuerySinglePartitionMultiplePages;[Type=Stream]": 5920952, - "MockedItemBenchmark.QuerySinglePartitionOnePage;[Type=OfT]": 2241814, - "MockedItemBenchmark.QuerySinglePartitionOnePage;[Type=OfTCustom]": 2241810, - "MockedItemBenchmark.QuerySinglePartitionOnePage;[Type=OfTWithClientTelemetryEnabled]": 2244658, - "MockedItemBenchmark.QuerySinglePartitionOnePage;[Type=OfTWithDiagnosticsToString]": 2251235.75, - "MockedItemBenchmark.QuerySinglePartitionOnePage;[Type=Stream]": 978459.25, - "MockedItemBenchmark.ReadFeed;[Type=OfT]": 560692, - "MockedItemBenchmark.ReadFeed;[Type=OfTCustom]": 555716, - "MockedItemBenchmark.ReadFeed;[Type=OfTWithClientTelemetryEnabled]": 550898.25, - "MockedItemBenchmark.ReadFeed;[Type=OfTWithDiagnosticsToString]": 573734.5, - "MockedItemBenchmark.ReadFeed;[Type=Stream]": 34192, - "MockedItemBenchmark.ReadItemExists;[Type=OfT]": 34509.75, - "MockedItemBenchmark.ReadItemExists;[Type=OfTCustom]": 34516, - "MockedItemBenchmark.ReadItemExists;[Type=OfTWithClientTelemetryEnabled]": 34515.5, - "MockedItemBenchmark.ReadItemExists;[Type=OfTWithDiagnosticsToString]": 56524.5, - "MockedItemBenchmark.ReadItemExists;[Type=Stream]": 26752.5, - "MockedItemBenchmark.ReadItemNotExists;[Type=OfT]": 43489.25, - "MockedItemBenchmark.ReadItemNotExists;[Type=OfTCustom]": 43490, - "MockedItemBenchmark.ReadItemNotExists;[Type=OfTWithClientTelemetryEnabled]": 43489.25, - "MockedItemBenchmark.ReadItemNotExists;[Type=OfTWithDiagnosticsToString]": 64764.75, - "MockedItemBenchmark.ReadItemNotExists;[Type=Stream]": 39876, - "MockedItemBenchmark.UpdateItem;[Type=OfT]": 37505.5, - "MockedItemBenchmark.UpdateItem;[Type=OfTCustom]": 37496.25, - "MockedItemBenchmark.UpdateItem;[Type=OfTWithClientTelemetryEnabled]": 37516.5, - "MockedItemBenchmark.UpdateItem;[Type=OfTWithDiagnosticsToString]": 59152, - "MockedItemBenchmark.UpdateItem;[Type=Stream]": 25428, - "MockedItemBenchmark.UpsertItem;[Type=OfT]": 37487.5, - "MockedItemBenchmark.UpsertItem;[Type=OfTCustom]": 37486, - "MockedItemBenchmark.UpsertItem;[Type=OfTWithClientTelemetryEnabled]": 37490, - "MockedItemBenchmark.UpsertItem;[Type=OfTWithDiagnosticsToString]": 59613, - "MockedItemBenchmark.UpsertItem;[Type=Stream]": 25445.5, - "MockedItemBulkBenchmark.CreateItem;[Type=OfT]": 1196168, - "MockedItemBulkBenchmark.CreateItem;[Type=OfTCustom]": 1195808, - "MockedItemBulkBenchmark.CreateItem;[Type=OfTWithClientTelemetryEnabled]": 1235418, - "MockedItemBulkBenchmark.CreateItem;[Type=OfTWithDiagnosticsToString]": 1231120, - "MockedItemBulkBenchmark.CreateItem;[Type=Stream]": 772810, - "MockedItemBulkBenchmark.DeleteItem;[Type=OfT]": 1187168, - "MockedItemBulkBenchmark.DeleteItem;[Type=OfTCustom]": 1187224, - "MockedItemBulkBenchmark.DeleteItem;[Type=OfTWithClientTelemetryEnabled]": 1226488, - "MockedItemBulkBenchmark.DeleteItem;[Type=OfTWithDiagnosticsToString]": 1222124, - "MockedItemBulkBenchmark.DeleteItem;[Type=Stream]": 771414, - "MockedItemBulkBenchmark.ReadItem;[Type=OfT]": 1186594, - "MockedItemBulkBenchmark.ReadItem;[Type=OfTCustom]": 1187192, - "MockedItemBulkBenchmark.ReadItem;[Type=OfTWithClientTelemetryEnabled]": 1226502, - "MockedItemBulkBenchmark.ReadItem;[Type=OfTWithDiagnosticsToString]": 1222342, - "MockedItemBulkBenchmark.ReadItem;[Type=Stream]": 770894, - "MockedItemBulkBenchmark.UpdateItem;[Type=OfT]": 1196464, - "MockedItemBulkBenchmark.UpdateItem;[Type=OfTCustom]": 1195778, - "MockedItemBulkBenchmark.UpdateItem;[Type=OfTWithClientTelemetryEnabled]": 1235738, - "MockedItemBulkBenchmark.UpdateItem;[Type=OfTWithDiagnosticsToString]": 1231486, - "MockedItemBulkBenchmark.UpdateItem;[Type=Stream]": 773284, - "MockedItemBulkBenchmark.UpsertItem;[Type=OfT]": 1196210, - "MockedItemBulkBenchmark.UpsertItem;[Type=OfTCustom]": 1195590, - "MockedItemBulkBenchmark.UpsertItem;[Type=OfTWithClientTelemetryEnabled]": 1235596, - "MockedItemBulkBenchmark.UpsertItem;[Type=OfTWithDiagnosticsToString]": 1231380, - "MockedItemBulkBenchmark.UpsertItem;[Type=Stream]": 773312 + "MockedItemBenchmark.CreateItem;[Type=OfT]": "37261.25", + "MockedItemBenchmark.CreateItem;[Type=OfTCustom]": "37285", + "MockedItemBenchmark.CreateItem;[Type=OfTWithClientMetricsEnabled]": "37751", + "MockedItemBenchmark.CreateItem;[Type=OfTWithDiagnosticsToString]": "61599.25", + "MockedItemBenchmark.CreateItem;[Type=OfTWithDistributedTracingEnabled]": "38351.5", + "MockedItemBenchmark.CreateItem;[Type=Stream]": "25793.75", + "MockedItemBenchmark.DeleteItemExists;[Type=OfT]": "33015.25", + "MockedItemBenchmark.DeleteItemExists;[Type=OfTCustom]": "33007.75", + "MockedItemBenchmark.DeleteItemExists;[Type=OfTWithClientMetricsEnabled]": "33730.5", + "MockedItemBenchmark.DeleteItemExists;[Type=OfTWithDiagnosticsToString]": "57928.75", + "MockedItemBenchmark.DeleteItemExists;[Type=OfTWithDistributedTracingEnabled]": "34352", + "MockedItemBenchmark.DeleteItemExists;[Type=Stream]": "25829.5", + "MockedItemBenchmark.DeleteItemNotExists;[Type=OfT]": "43035", + "MockedItemBenchmark.DeleteItemNotExists;[Type=OfTCustom]": "43030.5", + "MockedItemBenchmark.DeleteItemNotExists;[Type=OfTWithClientMetricsEnabled]": "43543.75", + "MockedItemBenchmark.DeleteItemNotExists;[Type=OfTWithDiagnosticsToString]": "67387.5", + "MockedItemBenchmark.DeleteItemNotExists;[Type=OfTWithDistributedTracingEnabled]": "44155.75", + "MockedItemBenchmark.DeleteItemNotExists;[Type=Stream]": "38373.5", + "MockedItemBenchmark.QuerySinglePartitionMultiplePages;[Type=OfT]": "13293777.25", + "MockedItemBenchmark.QuerySinglePartitionMultiplePages;[Type=OfTCustom]": "13298034.75", + "MockedItemBenchmark.QuerySinglePartitionMultiplePages;[Type=OfTWithClientMetricsEnabled]": "13198472.5", + "MockedItemBenchmark.QuerySinglePartitionMultiplePages;[Type=OfTWithDiagnosticsToString]": "13517039.75", + "MockedItemBenchmark.QuerySinglePartitionMultiplePages;[Type=OfTWithDistributedTracingEnabled]": "13201964", + "MockedItemBenchmark.QuerySinglePartitionMultiplePages;[Type=Stream]": "5920952", + "MockedItemBenchmark.QuerySinglePartitionOnePage;[Type=OfT]": "2241814", + "MockedItemBenchmark.QuerySinglePartitionOnePage;[Type=OfTCustom]": "2241810", + "MockedItemBenchmark.QuerySinglePartitionOnePage;[Type=OfTWithClientMetricsEnabled]": "2216526.75", + "MockedItemBenchmark.QuerySinglePartitionOnePage;[Type=OfTWithDiagnosticsToString]": "2251235.75", + "MockedItemBenchmark.QuerySinglePartitionOnePage;[Type=OfTWithDistributedTracingEnabled]": "2217249.25", + "MockedItemBenchmark.QuerySinglePartitionOnePage;[Type=Stream]": "978459.25", + "MockedItemBenchmark.ReadFeed;[Type=OfT]": "536077", + "MockedItemBenchmark.ReadFeed;[Type=OfTCustom]": "536082.75", + "MockedItemBenchmark.ReadFeed;[Type=OfTWithClientMetricsEnabled]": "536267.5", + "MockedItemBenchmark.ReadFeed;[Type=OfTWithDiagnosticsToString]": "557967.75", + "MockedItemBenchmark.ReadFeed;[Type=OfTWithDistributedTracingEnabled]": "536887.5", + "MockedItemBenchmark.ReadFeed;[Type=Stream]": "34192", + "MockedItemBenchmark.ReadItemExists;[Type=OfT]": "34509.75", + "MockedItemBenchmark.ReadItemExists;[Type=OfTCustom]": "34516", + "MockedItemBenchmark.ReadItemExists;[Type=OfTWithClientMetricsEnabled]": "35012.75", + "MockedItemBenchmark.ReadItemExists;[Type=OfTWithDiagnosticsToString]": "58859", + "MockedItemBenchmark.ReadItemExists;[Type=OfTWithDistributedTracingEnabled]": "35629.5", + "MockedItemBenchmark.ReadItemExists;[Type=Stream]": "26752.5", + "MockedItemBenchmark.ReadItemNotExists;[Type=OfT]": "44817.25", + "MockedItemBenchmark.ReadItemNotExists;[Type=OfTCustom]": "44810.75", + "MockedItemBenchmark.ReadItemNotExists;[Type=OfTWithClientMetricsEnabled]": "44819.5", + "MockedItemBenchmark.ReadItemNotExists;[Type=OfTWithDiagnosticsToString]": "66800", + "MockedItemBenchmark.ReadItemNotExists;[Type=OfTWithDistributedTracingEnabled]": "45423", + "MockedItemBenchmark.ReadItemNotExists;[Type=Stream]": "39876", + "MockedItemBenchmark.UpdateItem;[Type=OfT]": "37505.5", + "MockedItemBenchmark.UpdateItem;[Type=OfTCustom]": "37496.25", + "MockedItemBenchmark.UpdateItem;[Type=OfTWithClientMetricsEnabled]": "37965", + "MockedItemBenchmark.UpdateItem;[Type=OfTWithDiagnosticsToString]": "62701.5", + "MockedItemBenchmark.UpdateItem;[Type=OfTWithDistributedTracingEnabled]": "38580", + "MockedItemBenchmark.UpdateItem;[Type=Stream]": "26024", + "MockedItemBenchmark.UpsertItem;[Type=OfT]": "37487.5", + "MockedItemBenchmark.UpsertItem;[Type=OfTCustom]": "37486", + "MockedItemBenchmark.UpsertItem;[Type=OfTWithClientMetricsEnabled]": "37958.75", + "MockedItemBenchmark.UpsertItem;[Type=OfTWithDiagnosticsToString]": "62991.25", + "MockedItemBenchmark.UpsertItem;[Type=OfTWithDistributedTracingEnabled]": "38568", + "MockedItemBenchmark.UpsertItem;[Type=Stream]": "26017.25" } From 321520cb25d0b83f800532862a33091fcf3796c7 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 25 Oct 2024 13:57:45 +0530 Subject: [PATCH 27/49] refactored code --- .../src/Resource/ClientContextCore.cs | 52 ++++++------- .../OpenTelemetry/CosmosDbClientMetrics.cs | 4 +- .../OpenTelemetry/CosmosOperationMeter.cs | 75 +++++++------------ .../Metrics/CustomMetricExporter.cs | 3 +- .../Metrics/OpenTelemetryMetricsTest.cs | 45 +++++------ .../Utils/BaseCosmosClientHelper.cs | 6 ++ 6 files changed, 83 insertions(+), 102 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index 109315acf8..79a47158e9 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -13,6 +13,7 @@ namespace Microsoft.Azure.Cosmos using System.Text; using System.Threading; using System.Threading.Tasks; + using global::Azure; using Microsoft.Azure.Cosmos.Handlers; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; @@ -498,22 +499,24 @@ private async Task RunWithDiagnosticsHelperAsync( RequestOptions requestOptions, ResourceType? resourceType = null) { + Func getOperationName = () => + { + // If opentelemetry is not enabled then return null operation name, so that no activity is created. + if (openTelemetry == null) + { + return null; + } + + if (resourceType is not null && this.IsBulkOperationSupported(resourceType.Value, operationType)) + { + return OpenTelemetryConstants.Operations.ExecuteBulkPrefix + openTelemetry.Item1; + } + return openTelemetry.Item1; + }; + using (OpenTelemetryCoreRecorder recorder = OpenTelemetryRecorderFactory.CreateRecorder( - getOperationName: () => - { - // If opentelemetry is not enabled then return null operation name, so that no activity is created. - if (openTelemetry == null) - { - return null; - } - - if (resourceType is not null && this.IsBulkOperationSupported(resourceType.Value, operationType)) - { - return OpenTelemetryConstants.Operations.ExecuteBulkPrefix + openTelemetry.Item1; - } - return openTelemetry.Item1; - }, + getOperationName: getOperationName, containerName: containerName, databaseName: databaseName, operationType: operationType, @@ -526,8 +529,8 @@ private async Task RunWithDiagnosticsHelperAsync( { TResult result = await task(trace).ConfigureAwait(false); // Checks if OpenTelemetry is configured for this operation and either Trace or Metrics are enabled by customer - if (openTelemetry != null && - (!this.ClientOptions.CosmosClientTelemetryOptions.DisableDistributedTracing || this.ClientOptions.CosmosClientTelemetryOptions.IsClientMetricsEnabled)) + if (openTelemetry != null + && (!this.ClientOptions.CosmosClientTelemetryOptions.DisableDistributedTracing || this.ClientOptions.CosmosClientTelemetryOptions.IsClientMetricsEnabled)) { // Extracts and records telemetry data from the result of the operation. OpenTelemetryAttributes response = openTelemetry?.Item2(result); @@ -536,13 +539,12 @@ private async Task RunWithDiagnosticsHelperAsync( recorder.Record(response); // Records metrics such as request units, latency, and item count for the operation. - CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, - accountName: this.client.Endpoint, - containerName: containerName, - databaseName: databaseName, - attributes: response); + CosmosOperationMeter.RecordTelemetry(getOperationName: getOperationName, + accountName: this.client.Endpoint, + containerName: containerName, + databaseName: databaseName, + attributes: response); } - return result; } catch (OperationCanceledException oe) when (!(oe is CosmosOperationCanceledException)) @@ -574,14 +576,14 @@ private async Task RunWithDiagnosticsHelperAsync( catch (Exception ex) { recorder.MarkFailed(ex); - if (openTelemetry != null) + if (openTelemetry != null && ex is CosmosException cosmosException) { // Records telemetry data related to the exception. - CosmosOperationMeter.RecordTelemetry(operationName: openTelemetry.Item1, + CosmosOperationMeter.RecordTelemetry(getOperationName: getOperationName, accountName: this.client.Endpoint, containerName: containerName, databaseName: databaseName, - ex: ex); + ex: cosmosException); } throw; diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs index f88a3a9f03..f295022504 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs @@ -34,7 +34,7 @@ public static class Name /// /// Total request units per operation (sum of RUs for all requested needed when processing an operation) /// - public const string RequestCharge = "db.cosmosdb.operation.request_charge"; + public const string RequestCharge = "db.client.cosmosdb.operation.request_charge"; /// /// Total end-to-end duration of the operation @@ -49,7 +49,7 @@ public static class Name /// /// Number of active SDK client instances. /// - public const string ActiveInstances = "db.cosmosdb.client.active_instances"; + public const string ActiveInstances = "db.client.cosmosdb.active_instance.count"; } /// diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index 4f85ae579a..74a67da6fa 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -7,7 +7,6 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System; using System.Collections.Generic; using System.Diagnostics.Metrics; - using System.Net; /// /// CosmosOperationMeter is a utility class responsible for collecting and recording telemetry metrics related to Cosmos DB operations. @@ -78,65 +77,41 @@ internal static void Initialize() /// /// Records telemetry data related to Cosmos DB operations. This includes request latency, request units, and item counts. /// - /// Name of the operation being performed. + /// Generate operation name /// The URI of the Cosmos DB account. /// The name of the container involved in the operation. /// The name of the database involved in the operation. /// Optional OpenTelemetry attributes related to the operation. /// Optional exception object to capture error details. - internal static void RecordTelemetry(string operationName, + internal static void RecordTelemetry(Func getOperationName, Uri accountName, string containerName, string databaseName, OpenTelemetryAttributes attributes = null, - Exception ex = null) + CosmosException ex = null) { if (!IsEnabled) { return; } - Func[]> dimensionsFunc = () => + Func[]> dimensionsFunc = () => new KeyValuePair[] { - HttpStatusCode? statusCode = attributes?.StatusCode; - int? subStatusCode = attributes?.SubStatusCode; - string consistencyLevel = attributes?.ConsistencyLevel; - - if (ex is not null && ex is CosmosException cosmosException) - { - statusCode = cosmosException.StatusCode; - subStatusCode = cosmosException.SubStatusCode; - consistencyLevel = cosmosException.Headers.ConsistencyLevel; - } - - return new KeyValuePair[] - { - new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), - new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName), - new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName.Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName.Port), - new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, operationName), - new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)statusCode.Value), - new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, subStatusCode), - new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, consistencyLevel), - new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, ex.Message) - }; + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), + new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName), + new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName.Port), + new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, getOperationName()), + new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), + new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode), + new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes?.ConsistencyLevel ?? ex?.Headers?.ConsistencyLevel), + new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, ex?.Message) }; - if (attributes != null) - { - CosmosOperationMeter.RecordActualItemCount(attributes?.ItemCount, dimensionsFunc); - CosmosOperationMeter.RecordRequestUnit(attributes?.RequestCharge, dimensionsFunc); - CosmosOperationMeter.RecordRequestLatency(attributes.Diagnostics?.GetClientElapsedTime(), dimensionsFunc); - } - - if (ex != null && ex is CosmosException cosmosException) - { - CosmosOperationMeter.RecordActualItemCount(cosmosException.Headers.ItemCount, dimensionsFunc); - CosmosOperationMeter.RecordRequestUnit(cosmosException.Headers.RequestCharge, dimensionsFunc); - CosmosOperationMeter.RecordRequestLatency(cosmosException.Diagnostics?.GetClientElapsedTime(), dimensionsFunc); - } + CosmosOperationMeter.RecordActualItemCount(attributes?.ItemCount ?? ex?.Headers?.ItemCount, dimensionsFunc); + CosmosOperationMeter.RecordRequestUnit(attributes?.RequestCharge ?? ex?.Headers?.RequestCharge, dimensionsFunc); + CosmosOperationMeter.RecordRequestLatency(attributes?.Diagnostics?.GetClientElapsedTime() ?? ex?.Diagnostics?.GetClientElapsedTime(), dimensionsFunc); } /// @@ -146,7 +121,8 @@ internal static void RecordTelemetry(string operationName, /// A function providing telemetry dimensions for the metric. internal static void RecordActualItemCount(string actualItemCount, Func[]> dimensionsFunc) { - if (!IsEnabled || CosmosOperationMeter.ActualItemHistogram == null || !CosmosOperationMeter.ActualItemHistogram.Enabled || string.IsNullOrEmpty(actualItemCount)) + if (!IsEnabled || CosmosOperationMeter.ActualItemHistogram == null || + !CosmosOperationMeter.ActualItemHistogram.Enabled || string.IsNullOrEmpty(actualItemCount)) { return; } @@ -161,7 +137,8 @@ internal static void RecordActualItemCount(string actualItemCount, FuncA function providing telemetry dimensions for the metric. internal static void RecordRequestUnit(double? requestCharge, Func[]> dimensionsFunc) { - if (!IsEnabled || CosmosOperationMeter.RequestUnitsHistogram == null || !CosmosOperationMeter.RequestUnitsHistogram.Enabled || !requestCharge.HasValue) + if (!IsEnabled || CosmosOperationMeter.RequestUnitsHistogram == null || + !CosmosOperationMeter.RequestUnitsHistogram.Enabled || !requestCharge.HasValue) { return; } @@ -176,7 +153,8 @@ internal static void RecordRequestUnit(double? requestCharge, FuncA function providing telemetry dimensions for the metric. internal static void RecordRequestLatency(TimeSpan? requestLatency, Func[]> dimensionsFunc) { - if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled || !requestLatency.HasValue) + if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || + !CosmosOperationMeter.ActiveInstanceCounter.Enabled || !requestLatency.HasValue) { return; } @@ -190,7 +168,8 @@ internal static void RecordRequestLatency(TimeSpan? requestLatency, FuncThe URI of the account endpoint. internal static void AddInstanceCount(Uri accountEndpoint) { - if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled) + if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || + !CosmosOperationMeter.ActiveInstanceCounter.Enabled) { return; } @@ -202,6 +181,7 @@ internal static void AddInstanceCount(Uri accountEndpoint) new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) }; + Console.WriteLine("active instance metriuc collected"); CosmosOperationMeter.ActiveInstanceCounter.Add(1, dimensions); } @@ -211,7 +191,8 @@ internal static void AddInstanceCount(Uri accountEndpoint) /// The URI of the account endpoint. internal static void RemoveInstanceCount(Uri accountEndpoint) { - if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled) + if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || + !CosmosOperationMeter.ActiveInstanceCounter.Enabled) { return; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs index 38900c4b23..c8b2dc0d21 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs @@ -8,7 +8,6 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics using OpenTelemetry; using System.Collections.Generic; using System.Threading; - using System; public class CustomMetricExporter : BaseExporter { @@ -26,7 +25,7 @@ public override ExportResult Export(in Batch batch) { foreach (Metric metric in batch) { - ActualMetrics.Add(metric.Name, metric.MetricType); + ActualMetrics.TryAdd(metric.Name, metric.MetricType); } if (ActualMetrics.Count > 0) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs index be427c38ca..4c614ddc47 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -23,27 +23,16 @@ public class OpenTelemetryMetricsTest : BaseCosmosClientHelper { { "db.client.operation.duration", MetricType.Histogram }, { "db.client.response.row_count", MetricType.Histogram}, - { "db.cosmosdb.operation.request_charge", MetricType.Histogram }, - { "db.cosmosdb.client.active_instances", MetricType.LongSumNonMonotonic } + { "db.client.cosmosdb.operation.request_charge", MetricType.Histogram }, + { "db.client.cosmosdb.active_instance.count", MetricType.LongSumNonMonotonic } }; + private MeterProvider meterProvider; [TestInitialize] public async Task Init() - { - await base.TestInit(); - } - - [TestCleanup] - public async Task Cleanup() - { - await base.TestCleanup(); - } - - [TestMethod] - public async Task OperationLevelMetricsGenerationTest() { // Initialize OpenTelemetry MeterProvider - MeterProvider meterProvider = Sdk + this.meterProvider = Sdk .CreateMeterProviderBuilder() .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Azure Cosmos DB Operation Level Metrics")) .AddMeter(CosmosDbClientMetrics.OperationMetrics.MeterName) @@ -68,17 +57,23 @@ public async Task OperationLevelMetricsGenerationTest() .AddReader(new PeriodicExportingMetricReader(exporter: new CustomMetricExporter(this.manualResetEventSlim), exportIntervalMilliseconds: AggregatingInterval)) .Build(); - // Intialize CosmosClient with Client Metrics enabled - (string endpoint, string authKey) = TestCommon.GetAccountInfo(); - CosmosClientOptions cosmosClientOptions = new CosmosClientOptions + await base.TestInit((builder) => builder.WithClientTelemetryOptions(new CosmosClientTelemetryOptions() { - CosmosClientTelemetryOptions = new CosmosClientTelemetryOptions() - { - IsClientMetricsEnabled = true - } - }; - this.SetClient(new CosmosClient(endpoint, authKey, cosmosClientOptions)); + IsClientMetricsEnabled = true + })); + } + + [TestCleanup] + public async Task Cleanup() + { + this.meterProvider.Dispose(); + + await base.TestCleanup(); + } + [TestMethod] + public async Task OperationLevelMetricsGenerationTest() + { Container container = await this.database.CreateContainerIfNotExistsAsync(Guid.NewGuid().ToString(), "/pk", throughput: 10000); for (int count = 0; count < 10; count++) { @@ -98,8 +93,6 @@ public async Task OperationLevelMetricsGenerationTest() } } - meterProvider.Dispose(); - while (!this.manualResetEventSlim.IsSet) { } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/BaseCosmosClientHelper.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/BaseCosmosClientHelper.cs index f035711d77..237702f2c9 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/BaseCosmosClientHelper.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/BaseCosmosClientHelper.cs @@ -47,6 +47,12 @@ public async Task TestInit( customizeClientBuilder: customizeClientBuilder, accountEndpointOverride: accountEndpointOverride); await this.BaseInit(this.cosmosClient); + } + + public async Task TestInit(Action customizeClientBuilder = null) + { + this.cosmosClient = TestCommon.CreateCosmosClient(customizeClientBuilder: customizeClientBuilder); + await this.BaseInit(this.cosmosClient); } public async Task TestCleanup() From 9081e1ae2001116a514b2c1130d550fbe50f8d62 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Sat, 2 Nov 2024 00:59:46 +0530 Subject: [PATCH 28/49] added region contacted as dimension --- .../src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index 74a67da6fa..fbdcd1ee33 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -106,6 +106,7 @@ internal static void RecordTelemetry(Func getOperationName, new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode), new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes?.ConsistencyLevel ?? ex?.Headers?.ConsistencyLevel), + new KeyValuePair(OpenTelemetryAttributeKeys.Region, string.Join(",", attributes.Diagnostics.GetContactedRegions())), new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, ex?.Message) }; From 736c292f7e744c07d1c8fe72cf81ed61de164167 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Thu, 14 Nov 2024 20:46:32 +0530 Subject: [PATCH 29/49] perf fix --- .../ClientCreateAndInitializeTest.cs | 2 +- .../Microsoft.Azure.Cosmos.Performance.Tests.csproj | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs index e9111e6327..1813700e05 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientCreateAndInitializeTest.cs @@ -15,7 +15,7 @@ using Moq.Protected; [TestClass] - public class OpenTelemetryMetricsTest : BaseCosmosClientHelper + public class ClientCreateAndInitializeTest : BaseCosmosClientHelper { private ContainerInternal Container = null; private const string PartitionKey = "/pk"; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj index 0d12e1f397..b8d65cf141 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj @@ -17,13 +17,12 @@ - + - From 6d6957bf2998733f9024a1f1d5e9804970adf8ee Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Thu, 14 Nov 2024 20:51:37 +0530 Subject: [PATCH 30/49] inc perf test --- .../Microsoft.Azure.Cosmos.Performance.Tests.csproj | 3 ++- azure-pipelines.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj index b8d65cf141..8ef2a239f5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Microsoft.Azure.Cosmos.Performance.Tests.csproj @@ -17,12 +17,13 @@ - + + diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e892a2881f..7b36ba1938 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -51,7 +51,7 @@ jobs: Arguments: $(ReleaseArguments) VmImage: $(VmImage) MultiRegionConnectionString: $(COSMOSDB_MULTI_REGION) - IncludePerformance: false + IncludePerformance: true IncludeCoverage: false - template: templates/build-internal.yml From 36d0ee18c15edfc204577dacf507fb23aa85bcc3 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Thu, 14 Nov 2024 22:00:34 +0530 Subject: [PATCH 31/49] perf results --- .../Contracts/BenchmarkResults.json | 64 ++++++++++++++----- azure-pipelines.yml | 2 +- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/BenchmarkResults.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/BenchmarkResults.json index 9f26553a93..dc611ee286 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/BenchmarkResults.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/BenchmarkResults.json @@ -1,20 +1,22 @@ { - "MockedItemBenchmark.CreateItem;[Type=OfT]": "37261.25", - "MockedItemBenchmark.CreateItem;[Type=OfTCustom]": "37285", + "MasterKeyAuthorizationBenchmark.CreateSignatureGeneration;": "544", + "MasterKeyAuthorizationBenchmark.ReadSignatureGeneration;": "530", + "MockedItemBenchmark.CreateItem;[Type=OfT]": "38127.25", + "MockedItemBenchmark.CreateItem;[Type=OfTCustom]": "38132.25", "MockedItemBenchmark.CreateItem;[Type=OfTWithClientMetricsEnabled]": "37751", - "MockedItemBenchmark.CreateItem;[Type=OfTWithDiagnosticsToString]": "61599.25", + "MockedItemBenchmark.CreateItem;[Type=OfTWithDiagnosticsToString]": "59899.5", "MockedItemBenchmark.CreateItem;[Type=OfTWithDistributedTracingEnabled]": "38351.5", - "MockedItemBenchmark.CreateItem;[Type=Stream]": "25793.75", + "MockedItemBenchmark.CreateItem;[Type=Stream]": "26697.5", "MockedItemBenchmark.DeleteItemExists;[Type=OfT]": "33015.25", "MockedItemBenchmark.DeleteItemExists;[Type=OfTCustom]": "33007.75", "MockedItemBenchmark.DeleteItemExists;[Type=OfTWithClientMetricsEnabled]": "33730.5", - "MockedItemBenchmark.DeleteItemExists;[Type=OfTWithDiagnosticsToString]": "57928.75", + "MockedItemBenchmark.DeleteItemExists;[Type=OfTWithDiagnosticsToString]": "55086.5", "MockedItemBenchmark.DeleteItemExists;[Type=OfTWithDistributedTracingEnabled]": "34352", "MockedItemBenchmark.DeleteItemExists;[Type=Stream]": "25829.5", "MockedItemBenchmark.DeleteItemNotExists;[Type=OfT]": "43035", "MockedItemBenchmark.DeleteItemNotExists;[Type=OfTCustom]": "43030.5", "MockedItemBenchmark.DeleteItemNotExists;[Type=OfTWithClientMetricsEnabled]": "43543.75", - "MockedItemBenchmark.DeleteItemNotExists;[Type=OfTWithDiagnosticsToString]": "67387.5", + "MockedItemBenchmark.DeleteItemNotExists;[Type=OfTWithDiagnosticsToString]": "64886", "MockedItemBenchmark.DeleteItemNotExists;[Type=OfTWithDistributedTracingEnabled]": "44155.75", "MockedItemBenchmark.DeleteItemNotExists;[Type=Stream]": "38373.5", "MockedItemBenchmark.QuerySinglePartitionMultiplePages;[Type=OfT]": "13293777.25", @@ -34,11 +36,11 @@ "MockedItemBenchmark.ReadFeed;[Type=OfTWithClientMetricsEnabled]": "536267.5", "MockedItemBenchmark.ReadFeed;[Type=OfTWithDiagnosticsToString]": "557967.75", "MockedItemBenchmark.ReadFeed;[Type=OfTWithDistributedTracingEnabled]": "536887.5", - "MockedItemBenchmark.ReadFeed;[Type=Stream]": "34192", + "MockedItemBenchmark.ReadFeed;[Type=Stream]": "35859 (4.88%)", "MockedItemBenchmark.ReadItemExists;[Type=OfT]": "34509.75", "MockedItemBenchmark.ReadItemExists;[Type=OfTCustom]": "34516", "MockedItemBenchmark.ReadItemExists;[Type=OfTWithClientMetricsEnabled]": "35012.75", - "MockedItemBenchmark.ReadItemExists;[Type=OfTWithDiagnosticsToString]": "58859", + "MockedItemBenchmark.ReadItemExists;[Type=OfTWithDiagnosticsToString]": "55978.25", "MockedItemBenchmark.ReadItemExists;[Type=OfTWithDistributedTracingEnabled]": "35629.5", "MockedItemBenchmark.ReadItemExists;[Type=Stream]": "26752.5", "MockedItemBenchmark.ReadItemNotExists;[Type=OfT]": "44817.25", @@ -47,16 +49,46 @@ "MockedItemBenchmark.ReadItemNotExists;[Type=OfTWithDiagnosticsToString]": "66800", "MockedItemBenchmark.ReadItemNotExists;[Type=OfTWithDistributedTracingEnabled]": "45423", "MockedItemBenchmark.ReadItemNotExists;[Type=Stream]": "39876", - "MockedItemBenchmark.UpdateItem;[Type=OfT]": "37505.5", - "MockedItemBenchmark.UpdateItem;[Type=OfTCustom]": "37496.25", + "MockedItemBenchmark.UpdateItem;[Type=OfT]": "38345", + "MockedItemBenchmark.UpdateItem;[Type=OfTCustom]": "38347.25", "MockedItemBenchmark.UpdateItem;[Type=OfTWithClientMetricsEnabled]": "37965", - "MockedItemBenchmark.UpdateItem;[Type=OfTWithDiagnosticsToString]": "62701.5", + "MockedItemBenchmark.UpdateItem;[Type=OfTWithDiagnosticsToString]": "60124", "MockedItemBenchmark.UpdateItem;[Type=OfTWithDistributedTracingEnabled]": "38580", - "MockedItemBenchmark.UpdateItem;[Type=Stream]": "26024", - "MockedItemBenchmark.UpsertItem;[Type=OfT]": "37487.5", - "MockedItemBenchmark.UpsertItem;[Type=OfTCustom]": "37486", + "MockedItemBenchmark.UpdateItem;[Type=Stream]": "26924.5", + "MockedItemBenchmark.UpsertItem;[Type=OfT]": "38337.25", + "MockedItemBenchmark.UpsertItem;[Type=OfTCustom]": "38346.75", "MockedItemBenchmark.UpsertItem;[Type=OfTWithClientMetricsEnabled]": "37958.75", - "MockedItemBenchmark.UpsertItem;[Type=OfTWithDiagnosticsToString]": "62991.25", + "MockedItemBenchmark.UpsertItem;[Type=OfTWithDiagnosticsToString]": "60108.75", "MockedItemBenchmark.UpsertItem;[Type=OfTWithDistributedTracingEnabled]": "38568", - "MockedItemBenchmark.UpsertItem;[Type=Stream]": "26017.25" + "MockedItemBenchmark.UpsertItem;[Type=Stream]": "26919.25", + "MockedItemBulkBenchmark.CreateItem;[Type=OfT]": "1171140.75", + "MockedItemBulkBenchmark.CreateItem;[Type=OfTCustom]": "1170647.75", + "MockedItemBulkBenchmark.CreateItem;[Type=OfTWithClientMetricsEnabled]": "1207163.5", + "MockedItemBulkBenchmark.CreateItem;[Type=OfTWithDiagnosticsToString]": "1204316", + "MockedItemBulkBenchmark.CreateItem;[Type=OfTWithDistributedTracingEnabled]": "1207044", + "MockedItemBulkBenchmark.CreateItem;[Type=Stream]": "768930.25", + "MockedItemBulkBenchmark.DeleteItem;[Type=OfT]": "1165915.5", + "MockedItemBulkBenchmark.DeleteItem;[Type=OfTCustom]": "1165887", + "MockedItemBulkBenchmark.DeleteItem;[Type=OfTWithClientMetricsEnabled]": "1200824.25", + "MockedItemBulkBenchmark.DeleteItem;[Type=OfTWithDiagnosticsToString]": "1199540.25", + "MockedItemBulkBenchmark.DeleteItem;[Type=OfTWithDistributedTracingEnabled]": "1201890.25", + "MockedItemBulkBenchmark.DeleteItem;[Type=Stream]": "766362.5", + "MockedItemBulkBenchmark.ReadItem;[Type=OfT]": "1165960.25", + "MockedItemBulkBenchmark.ReadItem;[Type=OfTCustom]": "1165911.75", + "MockedItemBulkBenchmark.ReadItem;[Type=OfTWithClientMetricsEnabled]": "1200802.25", + "MockedItemBulkBenchmark.ReadItem;[Type=OfTWithDiagnosticsToString]": "1199006.5", + "MockedItemBulkBenchmark.ReadItem;[Type=OfTWithDistributedTracingEnabled]": "1201347.25", + "MockedItemBulkBenchmark.ReadItem;[Type=Stream]": "766475.75", + "MockedItemBulkBenchmark.UpdateItem;[Type=OfT]": "1171345", + "MockedItemBulkBenchmark.UpdateItem;[Type=OfTCustom]": "1170838.5", + "MockedItemBulkBenchmark.UpdateItem;[Type=OfTWithClientMetricsEnabled]": "1206330", + "MockedItemBulkBenchmark.UpdateItem;[Type=OfTWithDiagnosticsToString]": "1204520.75", + "MockedItemBulkBenchmark.UpdateItem;[Type=OfTWithDistributedTracingEnabled]": "1207243.25", + "MockedItemBulkBenchmark.UpdateItem;[Type=Stream]": "769129.75", + "MockedItemBulkBenchmark.UpsertItem;[Type=OfT]": "1170951.25", + "MockedItemBulkBenchmark.UpsertItem;[Type=OfTCustom]": "1170593.75", + "MockedItemBulkBenchmark.UpsertItem;[Type=OfTWithClientMetricsEnabled]": "1206121.5", + "MockedItemBulkBenchmark.UpsertItem;[Type=OfTWithDiagnosticsToString]": "1204822", + "MockedItemBulkBenchmark.UpsertItem;[Type=OfTWithDistributedTracingEnabled]": "1208600.25", + "MockedItemBulkBenchmark.UpsertItem;[Type=Stream]": "768928.75" } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7b36ba1938..e892a2881f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -51,7 +51,7 @@ jobs: Arguments: $(ReleaseArguments) VmImage: $(VmImage) MultiRegionConnectionString: $(COSMOSDB_MULTI_REGION) - IncludePerformance: true + IncludePerformance: false IncludeCoverage: false - template: templates/build-internal.yml From 36079d4640fe3a01bad5b5f85b57a5f6aadbd585 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Mon, 18 Nov 2024 12:22:19 +0530 Subject: [PATCH 32/49] refactor according to versioning --- .../AppInsightClassicAttributeKeys.cs | 15 ++++++++++++ .../OpenTelemetry/CosmosOperationMeter.cs | 23 ++++++------------ .../OpenTelemetry/DatabaseDupAttributeKeys.cs | 24 +++++++++++++++++++ .../IActivityAttributePopulator.cs | 10 +++++++- .../OpenTelemetryAttributeKeys.cs | 22 ++++++++++++++++- 5 files changed, 76 insertions(+), 18 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs index 5c65b5d71d..daf1b31cbe 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry { using System; + using System.Collections.Generic; using global::Azure.Core; internal sealed class AppInsightClassicAttributeKeys : IActivityAttributePopulator @@ -160,5 +161,19 @@ public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMo } } } + + public KeyValuePair[] PopulateOperationMeterDimensions(string operationName, string containerName, string databaseName, Uri accountName, OpenTelemetryAttributes attributes, CosmosException ex) + { + return new KeyValuePair[] + { + new KeyValuePair(AppInsightClassicAttributeKeys.ContainerName, containerName), + new KeyValuePair(AppInsightClassicAttributeKeys.DbName, databaseName), + new KeyValuePair(AppInsightClassicAttributeKeys.ServerAddress, accountName.Host), + new KeyValuePair(AppInsightClassicAttributeKeys.DbOperation, operationName), + new KeyValuePair(AppInsightClassicAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), + new KeyValuePair(AppInsightClassicAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode), + new KeyValuePair(AppInsightClassicAttributeKeys.Region, string.Join(",", attributes.Diagnostics.GetContactedRegions())) + }; + } } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index fbdcd1ee33..a5646299f4 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System; using System.Collections.Generic; using System.Diagnostics.Metrics; + using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; /// /// CosmosOperationMeter is a utility class responsible for collecting and recording telemetry metrics related to Cosmos DB operations. @@ -44,6 +45,8 @@ internal static class CosmosOperationMeter /// private static bool IsEnabled = false; + private static IActivityAttributePopulator activityAttributePopulator; + /// /// Initializes the histograms and counters for capturing Cosmos DB metrics. /// @@ -55,6 +58,8 @@ internal static void Initialize() return; } + activityAttributePopulator = TracesStabilityFactory.GetAttributePopulator(); + CosmosOperationMeter.RequestLatencyHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetrics.OperationMetrics.Name.Latency, unit: CosmosDbClientMetrics.OperationMetrics.Unit.Sec, description: CosmosDbClientMetrics.OperationMetrics.Description.Latency); @@ -95,21 +100,8 @@ internal static void RecordTelemetry(Func getOperationName, return; } - Func[]> dimensionsFunc = () => new KeyValuePair[] - { - new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), - new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName), - new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName.Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName.Port), - new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, getOperationName()), - new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), - new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode), - new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes?.ConsistencyLevel ?? ex?.Headers?.ConsistencyLevel), - new KeyValuePair(OpenTelemetryAttributeKeys.Region, string.Join(",", attributes.Diagnostics.GetContactedRegions())), - new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, ex?.Message) - }; - + Func[]> dimensionsFunc = () => activityAttributePopulator.PopulateOperationMeterDimensions(getOperationName(), containerName, databaseName, accountName, attributes, ex); + CosmosOperationMeter.RecordActualItemCount(attributes?.ItemCount ?? ex?.Headers?.ItemCount, dimensionsFunc); CosmosOperationMeter.RecordRequestUnit(attributes?.RequestCharge ?? ex?.Headers?.RequestCharge, dimensionsFunc); CosmosOperationMeter.RecordRequestLatency(attributes?.Diagnostics?.GetClientElapsedTime() ?? ex?.Diagnostics?.GetClientElapsedTime(), dimensionsFunc); @@ -182,7 +174,6 @@ internal static void AddInstanceCount(Uri accountEndpoint) new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) }; - Console.WriteLine("active instance metriuc collected"); CosmosOperationMeter.ActiveInstanceCounter.Add(1, dimensions); } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/DatabaseDupAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/DatabaseDupAttributeKeys.cs index 6116abbe55..9cbb173ad8 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/DatabaseDupAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/DatabaseDupAttributeKeys.cs @@ -5,6 +5,8 @@ namespace Microsoft.Azure.Cosmos.Telemetry { using System; + using System.Collections.Generic; + using System.Linq; using global::Azure.Core; internal class DatabaseDupAttributeKeys : IActivityAttributePopulator @@ -35,5 +37,27 @@ public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMo this.appInsightPopulator.PopulateAttributes(scope, queryTextMode, operationType, response); this.otelPopulator.PopulateAttributes(scope, queryTextMode, operationType, response); } + + public KeyValuePair[] PopulateOperationMeterDimensions(string operationName, + string containerName, + string databaseName, + Uri accountName, + OpenTelemetryAttributes attributes, + CosmosException ex) + { + KeyValuePair[] appInsightDimensions = this.appInsightPopulator + .PopulateOperationMeterDimensions(operationName, containerName, databaseName, accountName, attributes, ex) + .ToArray(); + KeyValuePair[] otelDimensions = this.otelPopulator + .PopulateOperationMeterDimensions(operationName, containerName, databaseName, accountName, attributes, ex) + .ToArray(); + + KeyValuePair[] dimensions + = new KeyValuePair[appInsightDimensions.Length + otelDimensions.Length]; + dimensions + .Concat(appInsightDimensions) + .Concat(otelDimensions); + return dimensions; + } } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs index 677e793797..f7ad05f36a 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs @@ -5,6 +5,8 @@ namespace Microsoft.Azure.Cosmos.Telemetry { using System; + using System.Collections.Generic; + using System.Xml.Linq; using global::Azure.Core; internal interface IActivityAttributePopulator @@ -25,5 +27,11 @@ public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMode, string operationType, OpenTelemetryAttributes response); - } + + public KeyValuePair[] PopulateOperationMeterDimensions(string operationName, + string containerName, + string databaseName, + Uri accountName, + OpenTelemetryAttributes attributes, CosmosException ex); + } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs index 92c73adaa6..8c6e97b428 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs @@ -4,7 +4,8 @@ namespace Microsoft.Azure.Cosmos.Telemetry { - using System; + using System; + using System.Collections.Generic; using global::Azure.Core; /// @@ -244,5 +245,24 @@ public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMo } } + + public KeyValuePair[] PopulateOperationMeterDimensions(string operationName, + string containerName, string databaseName, Uri accountName, OpenTelemetryAttributes attributes, CosmosException ex) + { + return new KeyValuePair[] + { + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), + new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName), + new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName.Port), + new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, operationName), + new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), + new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode), + new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes?.ConsistencyLevel ?? ex?.Headers?.ConsistencyLevel), + new KeyValuePair(OpenTelemetryAttributeKeys.Region, string.Join(",", attributes.Diagnostics.GetContactedRegions())), + new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, ex?.Message) + }; + } } } From e0efe5b34aceaeafa40635d7f21fa00a006d4909 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 20 Nov 2024 08:18:35 +0530 Subject: [PATCH 33/49] fix test --- .../OpenTelemetryAttributeKeys.cs | 7 ++- .../Metrics/CustomMetricExporter.cs | 63 +++++++++++++++++-- .../Metrics/OpenTelemetryMetricsTest.cs | 2 +- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs index 8c6e97b428..3c544c303f 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry { using System; using System.Collections.Generic; + using System.Linq; using global::Azure.Core; /// @@ -254,13 +255,13 @@ public KeyValuePair[] PopulateOperationMeterDimensions(string op new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName), new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName.Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName.Port), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName?.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName?.Port), new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, operationName), new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode), new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes?.ConsistencyLevel ?? ex?.Headers?.ConsistencyLevel), - new KeyValuePair(OpenTelemetryAttributeKeys.Region, string.Join(",", attributes.Diagnostics.GetContactedRegions())), + new KeyValuePair(OpenTelemetryAttributeKeys.Region, attributes?.Diagnostics?.GetContactedRegions()?.ToArray()), new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, ex?.Message) }; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs index c8b2dc0d21..19024ae825 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs @@ -8,10 +8,35 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics using OpenTelemetry; using System.Collections.Generic; using System.Threading; + using Microsoft.Azure.Cosmos.Telemetry; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using System.Linq; public class CustomMetricExporter : BaseExporter { private readonly ManualResetEventSlim manualResetEventSlim = null; + private readonly static List expectedDimensions = new() + { + OpenTelemetryAttributeKeys.DbSystemName, + OpenTelemetryAttributeKeys.ContainerName, + OpenTelemetryAttributeKeys.DbName, + OpenTelemetryAttributeKeys.ServerAddress, + OpenTelemetryAttributeKeys.ServerPort, + OpenTelemetryAttributeKeys.DbOperation, + OpenTelemetryAttributeKeys.StatusCode, + OpenTelemetryAttributeKeys.SubStatusCode, + OpenTelemetryAttributeKeys.ConsistencyLevel, + OpenTelemetryAttributeKeys.Region, + OpenTelemetryAttributeKeys.ErrorType + }; + + private readonly static List expectedDimensionsForInstanceCountMetrics = new() + { + OpenTelemetryAttributeKeys.DbSystemName, + OpenTelemetryAttributeKeys.ServerAddress, + OpenTelemetryAttributeKeys.ServerPort + }; public static Dictionary ActualMetrics = new Dictionary(); @@ -20,19 +45,47 @@ public CustomMetricExporter(ManualResetEventSlim manualResetEventSlim) this.manualResetEventSlim = manualResetEventSlim; } - // This method will be called periodically by OpenTelemetry SDK public override ExportResult Export(in Batch batch) { - foreach (Metric metric in batch) + try { - ActualMetrics.TryAdd(metric.Name, metric.MetricType); - } + foreach (Metric metric in batch) + { + ActualMetrics.TryAdd(metric.Name, metric.MetricType); + + HashSet actualDimensions = new(); + foreach (MetricPoint metricPoint in metric.GetMetricPoints()) + { + foreach (KeyValuePair dimension in metricPoint.Tags) + { + actualDimensions.Add(dimension.Key); + } + } - if (ActualMetrics.Count > 0) + if (metric.Name == CosmosDbClientMetrics.OperationMetrics.Name.ActiveInstances) + { + CollectionAssert.AreEquivalent(expectedDimensionsForInstanceCountMetrics, actualDimensions.ToList(), $"Dimensions are not matching for {metric.Name}"); + } + else + { + CollectionAssert.AreEquivalent(expectedDimensions, actualDimensions.ToList(), $"Dimensions are not matching for {metric.Name}"); + } + } + + if (ActualMetrics.Count > 0) + { + this.manualResetEventSlim.Set(); + } + } + catch (Exception ex) { + Console.WriteLine(ex); + this.manualResetEventSlim.Set(); + return ExportResult.Failure; } + return ExportResult.Success; } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs index 4c614ddc47..2f8ade2029 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -17,7 +17,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics [TestClass] public class OpenTelemetryMetricsTest : BaseCosmosClientHelper { - private const int AggregatingInterval = 1000; + private const int AggregatingInterval = 500; private readonly ManualResetEventSlim manualResetEventSlim = new ManualResetEventSlim(false); private static readonly Dictionary expectedMetrics = new Dictionary() { From 369160b07fb6813a7efabc8618ad05a1e43cc703 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 20 Nov 2024 11:25:00 +0530 Subject: [PATCH 34/49] refactor code --- .../OpenTelemetryAttributeKeys.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs index 3c544c303f..e6b822ca90 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System.Collections.Generic; using System.Linq; using global::Azure.Core; + using Microsoft.Azure.Cosmos.Diagnostics; /// /// Contains constant string values representing OpenTelemetry attribute keys for monitoring and tracing Cosmos DB operations. @@ -242,7 +243,9 @@ public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMo if (response.Diagnostics != null) { - scope.AddAttribute(OpenTelemetryAttributeKeys.Region, ClientTelemetryHelper.GetContactedRegions(response.Diagnostics.GetContactedRegions())); + scope.AddAttribute( + OpenTelemetryAttributeKeys.Region, + GetRegions(response.Diagnostics), (input) => string.Join(",", input)); } } @@ -261,9 +264,22 @@ public KeyValuePair[] PopulateOperationMeterDimensions(string op new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode), new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes?.ConsistencyLevel ?? ex?.Headers?.ConsistencyLevel), - new KeyValuePair(OpenTelemetryAttributeKeys.Region, attributes?.Diagnostics?.GetContactedRegions()?.ToArray()), + new KeyValuePair(OpenTelemetryAttributeKeys.Region, GetRegions(attributes?.Diagnostics)), new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, ex?.Message) }; } + + private static string[] GetRegions(CosmosDiagnostics diagnostics) + { + if (diagnostics?.GetContactedRegions() is not IReadOnlyList<(string regionName, Uri uri)> contactedRegions) + { + return null; + } + + return contactedRegions + .Select(region => region.regionName) + .Distinct() + .ToArray(); + } } } From 1f6489969fae744ffd9cc95d1851c5efb3f35df8 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 20 Nov 2024 12:00:31 +0530 Subject: [PATCH 35/49] added try catch --- .../OpenTelemetry/CosmosOperationMeter.cs | 76 ++++++++++++------- .../Metrics/OpenTelemetryMetricsTest.cs | 8 +- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index a5646299f4..04ed1f6333 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System; using System.Collections.Generic; using System.Diagnostics.Metrics; + using Microsoft.Azure.Cosmos.Core.Trace; using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; /// @@ -18,27 +19,27 @@ internal static class CosmosOperationMeter /// /// Meter instance for capturing various metrics related to Cosmos DB operations. /// - internal static Meter OperationMeter = new Meter(CosmosDbClientMetrics.OperationMetrics.MeterName, CosmosDbClientMetrics.OperationMetrics.Version); + private static readonly Meter OperationMeter = new Meter(CosmosDbClientMetrics.OperationMetrics.MeterName, CosmosDbClientMetrics.OperationMetrics.Version); /// /// Histogram to record request latency (in seconds) for Cosmos DB operations. /// - internal static Histogram RequestLatencyHistogram = null; + private static Histogram RequestLatencyHistogram = null; /// /// Histogram to record request units consumed during Cosmos DB operations. /// - internal static Histogram RequestUnitsHistogram = null; + private static Histogram RequestUnitsHistogram = null; /// /// Histogram to record the actual number of items involved in the operation. /// - internal static Histogram ActualItemHistogram = null; + private static Histogram ActualItemHistogram = null; /// /// UpDownCounter to track the number of active instances interacting with Cosmos DB. /// - internal static UpDownCounter ActiveInstanceCounter = null; + private static UpDownCounter ActiveInstanceCounter = null; /// /// Flag to check if metrics is enabled @@ -100,11 +101,18 @@ internal static void RecordTelemetry(Func getOperationName, return; } - Func[]> dimensionsFunc = () => activityAttributePopulator.PopulateOperationMeterDimensions(getOperationName(), containerName, databaseName, accountName, attributes, ex); - - CosmosOperationMeter.RecordActualItemCount(attributes?.ItemCount ?? ex?.Headers?.ItemCount, dimensionsFunc); - CosmosOperationMeter.RecordRequestUnit(attributes?.RequestCharge ?? ex?.Headers?.RequestCharge, dimensionsFunc); - CosmosOperationMeter.RecordRequestLatency(attributes?.Diagnostics?.GetClientElapsedTime() ?? ex?.Diagnostics?.GetClientElapsedTime(), dimensionsFunc); + try + { + Func[]> dimensionsFunc = () => activityAttributePopulator.PopulateOperationMeterDimensions(getOperationName(), containerName, databaseName, accountName, attributes, ex); + + CosmosOperationMeter.RecordActualItemCount(attributes?.ItemCount ?? ex?.Headers?.ItemCount, dimensionsFunc); + CosmosOperationMeter.RecordRequestUnit(attributes?.RequestCharge ?? ex?.Headers?.RequestCharge, dimensionsFunc); + CosmosOperationMeter.RecordRequestLatency(attributes?.Diagnostics?.GetClientElapsedTime() ?? ex?.Diagnostics?.GetClientElapsedTime(), dimensionsFunc); + } + catch (Exception exception) + { + DefaultTrace.TraceWarning($"Failed to record telemetry data for Cosmos DB operation. {exception.StackTrace}"); + } } /// @@ -112,7 +120,7 @@ internal static void RecordTelemetry(Func getOperationName, /// /// The number of items returned or affected by the operation. /// A function providing telemetry dimensions for the metric. - internal static void RecordActualItemCount(string actualItemCount, Func[]> dimensionsFunc) + private static void RecordActualItemCount(string actualItemCount, Func[]> dimensionsFunc) { if (!IsEnabled || CosmosOperationMeter.ActualItemHistogram == null || !CosmosOperationMeter.ActualItemHistogram.Enabled || string.IsNullOrEmpty(actualItemCount)) @@ -128,7 +136,7 @@ internal static void RecordActualItemCount(string actualItemCount, Func /// The RU/s value for the operation. /// A function providing telemetry dimensions for the metric. - internal static void RecordRequestUnit(double? requestCharge, Func[]> dimensionsFunc) + private static void RecordRequestUnit(double? requestCharge, Func[]> dimensionsFunc) { if (!IsEnabled || CosmosOperationMeter.RequestUnitsHistogram == null || !CosmosOperationMeter.RequestUnitsHistogram.Enabled || !requestCharge.HasValue) @@ -144,7 +152,7 @@ internal static void RecordRequestUnit(double? requestCharge, Func /// The latency of the operation. /// A function providing telemetry dimensions for the metric. - internal static void RecordRequestLatency(TimeSpan? requestLatency, Func[]> dimensionsFunc) + private static void RecordRequestLatency(TimeSpan? requestLatency, Func[]> dimensionsFunc) { if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || !CosmosOperationMeter.ActiveInstanceCounter.Enabled || !requestLatency.HasValue) @@ -167,14 +175,21 @@ internal static void AddInstanceCount(Uri accountEndpoint) return; } - KeyValuePair[] dimensions = new[] + try { - new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountEndpoint.Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) - }; - - CosmosOperationMeter.ActiveInstanceCounter.Add(1, dimensions); + KeyValuePair[] dimensions = new[] + { + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountEndpoint.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) + }; + + CosmosOperationMeter.ActiveInstanceCounter.Add(1, dimensions); + } + catch (Exception exception) + { + DefaultTrace.TraceWarning($"Failed to record InstanceCount telemetry data for Cosmos DB operation. {exception.StackTrace}"); + } } /// @@ -189,14 +204,21 @@ internal static void RemoveInstanceCount(Uri accountEndpoint) return; } - KeyValuePair[] dimensions = new[] + try { - new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountEndpoint.Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) - }; - - CosmosOperationMeter.ActiveInstanceCounter.Add(-1, dimensions); + KeyValuePair[] dimensions = new[] + { + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountEndpoint.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) + }; + + CosmosOperationMeter.ActiveInstanceCounter.Add(-1, dimensions); + } + catch (Exception exception) + { + DefaultTrace.TraceWarning($"Failed to record InstanceCount telemetry data for Cosmos DB operation. {exception.StackTrace}"); + } } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs index 2f8ade2029..860c1c1faf 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -54,7 +54,9 @@ public async Task Init() { Boundaries = CosmosDbClientMetrics.HistogramBuckets.RowCountBuckets }) - .AddReader(new PeriodicExportingMetricReader(exporter: new CustomMetricExporter(this.manualResetEventSlim), exportIntervalMilliseconds: AggregatingInterval)) + .AddReader(new PeriodicExportingMetricReader( + exporter: new CustomMetricExporter(this.manualResetEventSlim), + exportIntervalMilliseconds: AggregatingInterval)) .Build(); await base.TestInit((builder) => builder.WithClientTelemetryOptions(new CosmosClientTelemetryOptions() @@ -66,9 +68,9 @@ await base.TestInit((builder) => builder.WithClientTelemetryOptions(new CosmosCl [TestCleanup] public async Task Cleanup() { - this.meterProvider.Dispose(); - await base.TestCleanup(); + + this.meterProvider.Dispose(); } [TestMethod] From 79b73079f3cc24267539b65760817757e7595ed4 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 20 Nov 2024 12:02:19 +0530 Subject: [PATCH 36/49] added console --- .../src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index 04ed1f6333..94b7e9ef49 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -76,7 +76,7 @@ internal static void Initialize() CosmosOperationMeter.ActiveInstanceCounter ??= OperationMeter.CreateUpDownCounter(name: CosmosDbClientMetrics.OperationMetrics.Name.ActiveInstances, unit: CosmosDbClientMetrics.OperationMetrics.Unit.Count, description: CosmosDbClientMetrics.OperationMetrics.Description.ActiveInstances); - + Console.WriteLine("initiized successfully"); IsEnabled = true; } @@ -111,6 +111,7 @@ internal static void RecordTelemetry(Func getOperationName, } catch (Exception exception) { + Console.WriteLine(exception.ToString()); DefaultTrace.TraceWarning($"Failed to record telemetry data for Cosmos DB operation. {exception.StackTrace}"); } } @@ -188,6 +189,7 @@ internal static void AddInstanceCount(Uri accountEndpoint) } catch (Exception exception) { + Console.WriteLine(exception.ToString()); DefaultTrace.TraceWarning($"Failed to record InstanceCount telemetry data for Cosmos DB operation. {exception.StackTrace}"); } } @@ -217,6 +219,7 @@ internal static void RemoveInstanceCount(Uri accountEndpoint) } catch (Exception exception) { + Console.WriteLine(exception.ToString()); DefaultTrace.TraceWarning($"Failed to record InstanceCount telemetry data for Cosmos DB operation. {exception.StackTrace}"); } } From f18481336c6c5b769c2f23532072b8e26443ffe3 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 20 Nov 2024 15:45:50 +0530 Subject: [PATCH 37/49] updated contract --- ...riterBaselineTests.BulkOperationsAsync.xml | 100 ++++++++++++++++++ ...eWriterBaselineTests.MiscellanousAsync.xml | 4 + 2 files changed, 104 insertions(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.BulkOperationsAsync.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.BulkOperationsAsync.xml index 6b84642855..511d0143bc 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.BulkOperationsAsync.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.BulkOperationsAsync.xml @@ -187,6 +187,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -212,6 +213,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -237,6 +239,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -262,6 +265,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -287,6 +291,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -312,6 +317,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -337,6 +343,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -362,6 +369,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -387,6 +395,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -412,6 +421,7 @@ Some Value Some Value Some Value + @@ -594,6 +604,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -619,6 +630,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -644,6 +656,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -669,6 +682,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -694,6 +708,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -719,6 +734,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -744,6 +760,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -769,6 +786,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -794,6 +812,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -819,6 +838,7 @@ Some Value Some Value Some Value + @@ -1001,6 +1021,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1026,6 +1047,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1051,6 +1073,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1076,6 +1099,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1101,6 +1125,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1126,6 +1151,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1151,6 +1177,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1176,6 +1203,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1201,6 +1229,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1226,6 +1255,7 @@ Some Value Some Value Some Value + @@ -1408,6 +1438,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1433,6 +1464,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1458,6 +1490,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1483,6 +1516,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1508,6 +1542,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1533,6 +1568,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1558,6 +1594,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1583,6 +1620,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1608,6 +1646,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1633,6 +1672,7 @@ Some Value Some Value Some Value + @@ -1815,6 +1855,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1840,6 +1881,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1865,6 +1907,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1890,6 +1933,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1915,6 +1959,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1940,6 +1985,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1965,6 +2011,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -1990,6 +2037,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2015,6 +2063,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2040,6 +2089,7 @@ Some Value Some Value Some Value + @@ -2222,6 +2272,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2247,6 +2298,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2272,6 +2324,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2297,6 +2350,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2322,6 +2376,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2347,6 +2402,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2372,6 +2428,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2397,6 +2454,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2422,6 +2480,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2447,6 +2506,7 @@ Some Value Some Value Some Value + @@ -2629,6 +2689,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2654,6 +2715,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2679,6 +2741,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2704,6 +2767,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2729,6 +2793,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2754,6 +2819,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2779,6 +2845,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2804,6 +2871,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2829,6 +2897,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -2854,6 +2923,7 @@ Some Value Some Value Some Value + @@ -3036,6 +3106,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3061,6 +3132,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3086,6 +3158,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3111,6 +3184,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3136,6 +3210,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3161,6 +3236,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3186,6 +3262,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3211,6 +3288,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3236,6 +3314,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3261,6 +3340,7 @@ Some Value Some Value Some Value + @@ -3443,6 +3523,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3468,6 +3549,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3493,6 +3575,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3518,6 +3601,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3543,6 +3627,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3568,6 +3653,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3593,6 +3679,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3618,6 +3705,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3643,6 +3731,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3668,6 +3757,7 @@ Some Value Some Value Some Value + @@ -3850,6 +3940,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3875,6 +3966,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3900,6 +3992,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3925,6 +4018,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3950,6 +4044,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -3975,6 +4070,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -4000,6 +4096,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -4025,6 +4122,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -4050,6 +4148,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -4075,6 +4174,7 @@ Some Value Some Value Some Value + diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.MiscellanousAsync.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.MiscellanousAsync.xml index a98f6988cd..f65360906a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.MiscellanousAsync.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.MiscellanousAsync.xml @@ -139,6 +139,7 @@ Some Value Some Value Some Value + Microsoft.DocumentDB @@ -162,6 +163,7 @@ Some Value Some Value Some Value + @@ -293,6 +295,7 @@ Some Value Some Value Session + Microsoft.DocumentDB @@ -317,6 +320,7 @@ Some Value Some Value Session + From ef2f2a59091fed854bfeb3f7fbd290ec8c725c32 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 20 Nov 2024 16:17:21 +0530 Subject: [PATCH 38/49] code refatoring --- .../OpenTelemetry/CosmosOperationMeter.cs | 8 +++----- .../OpenTelemetry/TracesStabilityFactory.cs | 3 +-- .../Metrics/OpenTelemetryMetricsTest.cs | 20 ++++--------------- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index 94b7e9ef49..d601f6843a 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -21,6 +21,8 @@ internal static class CosmosOperationMeter /// private static readonly Meter OperationMeter = new Meter(CosmosDbClientMetrics.OperationMetrics.MeterName, CosmosDbClientMetrics.OperationMetrics.Version); + private static readonly IActivityAttributePopulator DimensionPopulator = TracesStabilityFactory.GetAttributePopulator(); + /// /// Histogram to record request latency (in seconds) for Cosmos DB operations. /// @@ -46,8 +48,6 @@ internal static class CosmosOperationMeter /// private static bool IsEnabled = false; - private static IActivityAttributePopulator activityAttributePopulator; - /// /// Initializes the histograms and counters for capturing Cosmos DB metrics. /// @@ -59,8 +59,6 @@ internal static void Initialize() return; } - activityAttributePopulator = TracesStabilityFactory.GetAttributePopulator(); - CosmosOperationMeter.RequestLatencyHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetrics.OperationMetrics.Name.Latency, unit: CosmosDbClientMetrics.OperationMetrics.Unit.Sec, description: CosmosDbClientMetrics.OperationMetrics.Description.Latency); @@ -103,7 +101,7 @@ internal static void RecordTelemetry(Func getOperationName, try { - Func[]> dimensionsFunc = () => activityAttributePopulator.PopulateOperationMeterDimensions(getOperationName(), containerName, databaseName, accountName, attributes, ex); + Func[]> dimensionsFunc = () => DimensionPopulator.PopulateOperationMeterDimensions(getOperationName(), containerName, databaseName, accountName, attributes, ex); CosmosOperationMeter.RecordActualItemCount(attributes?.ItemCount ?? ex?.Headers?.ItemCount, dimensionsFunc); CosmosOperationMeter.RecordRequestUnit(attributes?.RequestCharge ?? ex?.Headers?.RequestCharge, dimensionsFunc); diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/TracesStabilityFactory.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/TracesStabilityFactory.cs index 4339e336b3..70398310fd 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/TracesStabilityFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/TracesStabilityFactory.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry { using System; - using global::Azure.Core; /// /// Factory for handling telemetry trace stability modes, allowing attribute settings @@ -20,7 +19,7 @@ public static IActivityAttributePopulator GetAttributePopulator() { return otelStabilityMode switch { - OpenTelemetryStablityModes.Database => new OpenTelemetryAttributeKeys(), + OpenTelemetryStablityModes.Database or null => new OpenTelemetryAttributeKeys(), OpenTelemetryStablityModes.DatabaseDupe => new DatabaseDupAttributeKeys(), OpenTelemetryStablityModes.ClassicAppInsights => new AppInsightClassicAttributeKeys(), _ => new OpenTelemetryAttributeKeys() diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs index 860c1c1faf..b00bd30ed7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -18,6 +18,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics public class OpenTelemetryMetricsTest : BaseCosmosClientHelper { private const int AggregatingInterval = 500; + private readonly ManualResetEventSlim manualResetEventSlim = new ManualResetEventSlim(false); private static readonly Dictionary expectedMetrics = new Dictionary() { @@ -31,6 +32,8 @@ public class OpenTelemetryMetricsTest : BaseCosmosClientHelper [TestInitialize] public async Task Init() { + Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", null); + // Initialize OpenTelemetry MeterProvider this.meterProvider = Sdk .CreateMeterProviderBuilder() @@ -99,22 +102,7 @@ public async Task OperationLevelMetricsGenerationTest() { } - Assert.IsTrue(IsEqual(expectedMetrics, CustomMetricExporter.ActualMetrics), string.Join(", ", CustomMetricExporter.ActualMetrics.Select(kv => $"{kv.Key}: {kv.Value}"))); - } - - public static bool IsEqual(Dictionary expected, Dictionary actual) - { - if (expected.Count != actual.Count) - return false; - - // Compare both keys and values - foreach (KeyValuePair pair in expected) - { - if (!actual.TryGetValue(pair.Key, out TValue value) || !EqualityComparer.Default.Equals(pair.Value, value)) - return false; - } - - return true; + CollectionAssert.AreEquivalent(expectedMetrics, CustomMetricExporter.ActualMetrics, string.Join(", ", CustomMetricExporter.ActualMetrics.Select(kv => $"{kv.Key}: {kv.Value}"))); } } } From 731951a049b64ed8294087a698a74c65f27fe8df Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 20 Nov 2024 21:31:06 +0530 Subject: [PATCH 39/49] fix tetss --- .../src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs | 8 ++++---- .../Metrics/OpenTelemetryMetricsTest.cs | 2 -- .../Tracing/EndToEndTraceWriterBaselineTests.cs | 5 ++++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs index d601f6843a..c34d35001e 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs @@ -21,6 +21,9 @@ internal static class CosmosOperationMeter /// private static readonly Meter OperationMeter = new Meter(CosmosDbClientMetrics.OperationMetrics.MeterName, CosmosDbClientMetrics.OperationMetrics.Version); + /// + /// Populator Used for Dimension Attributes + /// private static readonly IActivityAttributePopulator DimensionPopulator = TracesStabilityFactory.GetAttributePopulator(); /// @@ -74,7 +77,7 @@ internal static void Initialize() CosmosOperationMeter.ActiveInstanceCounter ??= OperationMeter.CreateUpDownCounter(name: CosmosDbClientMetrics.OperationMetrics.Name.ActiveInstances, unit: CosmosDbClientMetrics.OperationMetrics.Unit.Count, description: CosmosDbClientMetrics.OperationMetrics.Description.ActiveInstances); - Console.WriteLine("initiized successfully"); + IsEnabled = true; } @@ -109,7 +112,6 @@ internal static void RecordTelemetry(Func getOperationName, } catch (Exception exception) { - Console.WriteLine(exception.ToString()); DefaultTrace.TraceWarning($"Failed to record telemetry data for Cosmos DB operation. {exception.StackTrace}"); } } @@ -187,7 +189,6 @@ internal static void AddInstanceCount(Uri accountEndpoint) } catch (Exception exception) { - Console.WriteLine(exception.ToString()); DefaultTrace.TraceWarning($"Failed to record InstanceCount telemetry data for Cosmos DB operation. {exception.StackTrace}"); } } @@ -217,7 +218,6 @@ internal static void RemoveInstanceCount(Uri accountEndpoint) } catch (Exception exception) { - Console.WriteLine(exception.ToString()); DefaultTrace.TraceWarning($"Failed to record InstanceCount telemetry data for Cosmos DB operation. {exception.StackTrace}"); } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs index b00bd30ed7..b1789176aa 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -32,8 +32,6 @@ public class OpenTelemetryMetricsTest : BaseCosmosClientHelper [TestInitialize] public async Task Init() { - Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", null); - // Initialize OpenTelemetry MeterProvider this.meterProvider = Sdk .CreateMeterProviderBuilder() diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs index 38a74a8edb..a9151ae136 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs @@ -22,6 +22,7 @@ namespace Microsoft.Azure.Cosmos.EmulatorTests.Tracing using Microsoft.Azure.Cosmos.SDK.EmulatorTests; using Microsoft.Azure.Cosmos.Services.Management.Tests.BaselineTest; using Microsoft.Azure.Cosmos.Telemetry; + using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; using Microsoft.Azure.Cosmos.Tests; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -52,6 +53,7 @@ public sealed class EndToEndTraceWriterBaselineTests : BaselineTests Date: Wed, 20 Nov 2024 22:05:27 +0530 Subject: [PATCH 40/49] refactor code --- Microsoft.Azure.Cosmos/src/CosmosClient.cs | 2 +- Microsoft.Azure.Cosmos/src/DocumentClient.cs | 4 +- .../src/Resource/ClientContextCore.cs | 4 +- .../OpenTelemetry/CosmosDbMeterUtil.cs | 38 +++ .../OpenTelemetry/CosmosDbOperationMeter.cs | 158 ++++++++++++ .../OpenTelemetry/CosmosOperationMeter.cs | 225 ------------------ 6 files changed, 201 insertions(+), 230 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbMeterUtil.cs create mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs delete mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs diff --git a/Microsoft.Azure.Cosmos/src/CosmosClient.cs b/Microsoft.Azure.Cosmos/src/CosmosClient.cs index 3f9770ac64..05cbb846a0 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClient.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClient.cs @@ -1438,7 +1438,7 @@ private int DecrementNumberOfActiveClients() // In case dispose is called multiple times. Check if at least 1 active client is there if (NumberOfActiveClients > 0) { - CosmosOperationMeter.RemoveInstanceCount(this.Endpoint); + CosmosDbOperationMeter.RemoveInstanceCount(this.Endpoint); return Interlocked.Decrement(ref NumberOfActiveClients); } diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index 5090dba3c6..984ad0371a 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -958,9 +958,9 @@ internal virtual void Initialize(Uri serviceEndpoint, if (this.cosmosClientTelemetryOptions.IsClientMetricsEnabled) { - CosmosOperationMeter.Initialize(); + CosmosDbOperationMeter.Initialize(); - CosmosOperationMeter.AddInstanceCount(this.ServiceEndpoint); + CosmosDbOperationMeter.AddInstanceCount(this.ServiceEndpoint); } // Starting ClientTelemetry Job diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index 79a47158e9..0c0768a8a9 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -539,7 +539,7 @@ private async Task RunWithDiagnosticsHelperAsync( recorder.Record(response); // Records metrics such as request units, latency, and item count for the operation. - CosmosOperationMeter.RecordTelemetry(getOperationName: getOperationName, + CosmosDbOperationMeter.RecordTelemetry(getOperationName: getOperationName, accountName: this.client.Endpoint, containerName: containerName, databaseName: databaseName, @@ -579,7 +579,7 @@ private async Task RunWithDiagnosticsHelperAsync( if (openTelemetry != null && ex is CosmosException cosmosException) { // Records telemetry data related to the exception. - CosmosOperationMeter.RecordTelemetry(getOperationName: getOperationName, + CosmosDbOperationMeter.RecordTelemetry(getOperationName: getOperationName, accountName: this.client.Endpoint, containerName: containerName, databaseName: databaseName, diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbMeterUtil.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbMeterUtil.cs new file mode 100644 index 0000000000..60b79ac4b1 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbMeterUtil.cs @@ -0,0 +1,38 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Telemetry +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Metrics; + using Microsoft.Azure.Cosmos.Core.Trace; + + internal static class CosmosDbMeterUtil + { + internal static void RecordHistogramMetric( + object value, + Func[]> dimensionsFunc, + Histogram histogram, + Func converter = null) + where T : struct + { + if (histogram == null || !histogram.Enabled || value == null) + { + return; + } + + try + { + T convertedValue = converter != null ? converter(value) : (T)value; + histogram.Record(convertedValue, dimensionsFunc()); + } + catch (Exception ex) + { + DefaultTrace.TraceWarning($"Failed to record metric. {ex.StackTrace}"); + } + } + + } +} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs new file mode 100644 index 0000000000..9dfecbd9e2 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs @@ -0,0 +1,158 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Telemetry +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Metrics; + using Microsoft.Azure.Cosmos.Core.Trace; + using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; + + /// + /// CosmosOperationMeter is a utility class responsible for collecting and recording telemetry metrics related to Cosmos DB operations. + /// It includes histograms and counters that capture metrics like request latency, request units, item count, and active instances. + /// + internal static class CosmosDbOperationMeter + { + /// + /// Meter instance for capturing various metrics related to Cosmos DB operations. + /// + private static readonly Meter OperationMeter = new Meter(CosmosDbClientMetrics.OperationMetrics.MeterName, CosmosDbClientMetrics.OperationMetrics.Version); + + /// + /// Populator Used for Dimension Attributes + /// + private static readonly IActivityAttributePopulator DimensionPopulator = TracesStabilityFactory.GetAttributePopulator(); + + /// + /// Histogram to record request latency (in seconds) for Cosmos DB operations. + /// + private static Histogram RequestLatencyHistogram = null; + + /// + /// Histogram to record request units consumed during Cosmos DB operations. + /// + private static Histogram RequestUnitsHistogram = null; + + /// + /// Histogram to record the actual number of items involved in the operation. + /// + private static Histogram ActualItemHistogram = null; + + /// + /// UpDownCounter to track the number of active instances interacting with Cosmos DB. + /// + private static UpDownCounter ActiveInstanceCounter = null; + + /// + /// Flag to check if metrics is enabled + /// + private static bool IsEnabled = false; + + /// + /// Initializes the histograms and counters for capturing Cosmos DB metrics. + /// + internal static void Initialize() + { + // If already initialized, do not initialize again + if (IsEnabled) + { + return; + } + + CosmosDbOperationMeter.RequestLatencyHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetrics.OperationMetrics.Name.Latency, + unit: CosmosDbClientMetrics.OperationMetrics.Unit.Sec, + description: CosmosDbClientMetrics.OperationMetrics.Description.Latency); + + CosmosDbOperationMeter.RequestUnitsHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetrics.OperationMetrics.Name.RequestCharge, + unit: CosmosDbClientMetrics.OperationMetrics.Unit.RequestUnit, + description: CosmosDbClientMetrics.OperationMetrics.Description.RequestCharge); + + CosmosDbOperationMeter.ActualItemHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetrics.OperationMetrics.Name.RowCount, + unit: CosmosDbClientMetrics.OperationMetrics.Unit.Count, + description: CosmosDbClientMetrics.OperationMetrics.Description.RowCount); + + CosmosDbOperationMeter.ActiveInstanceCounter ??= OperationMeter.CreateUpDownCounter(name: CosmosDbClientMetrics.OperationMetrics.Name.ActiveInstances, + unit: CosmosDbClientMetrics.OperationMetrics.Unit.Count, + description: CosmosDbClientMetrics.OperationMetrics.Description.ActiveInstances); + + IsEnabled = true; + } + + /// + /// Records telemetry data for Cosmos DB operations. + /// + internal static void RecordTelemetry( + Func getOperationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes = null, + CosmosException ex = null) + { + if (!IsEnabled) + { + return; + } + + try + { + Func[]> dimensionsFunc = () => + DimensionPopulator.PopulateOperationMeterDimensions( + getOperationName(), containerName, databaseName, accountName, attributes, ex); + + CosmosDbMeterUtil.RecordHistogramMetric(value: attributes?.ItemCount ?? ex?.Headers?.ItemCount, + dimensionsFunc, ActualItemHistogram, + Convert.ToInt32); + CosmosDbMeterUtil.RecordHistogramMetric(value: attributes?.RequestCharge ?? ex?.Headers?.RequestCharge, + dimensionsFunc, RequestUnitsHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(value: attributes?.Diagnostics?.GetClientElapsedTime() ?? ex?.Diagnostics?.GetClientElapsedTime(), + dimensionsFunc, RequestLatencyHistogram, + t => ((TimeSpan)t).TotalSeconds); + } + catch (Exception exception) + { + DefaultTrace.TraceWarning($"Failed to record telemetry data. {exception.StackTrace}"); + } + } + + /// + /// Adjusts the count of active Cosmos DB instances. + /// + internal static void AdjustInstanceCount(Uri accountEndpoint, int adjustment) + { + if (!IsEnabled || ActiveInstanceCounter == null || !ActiveInstanceCounter.Enabled) + { + return; + } + + try + { + KeyValuePair[] dimensions = new[] + { + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountEndpoint.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) + }; + + ActiveInstanceCounter.Add(adjustment, dimensions); + } + catch (Exception ex) + { + DefaultTrace.TraceWarning($"Failed to adjust instance count. {ex.StackTrace}"); + } + } + + internal static void AddInstanceCount(Uri accountEndpoint) + { + AdjustInstanceCount(accountEndpoint, 1); + } + + internal static void RemoveInstanceCount(Uri accountEndpoint) + { + AdjustInstanceCount(accountEndpoint, -1); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs deleted file mode 100644 index c34d35001e..0000000000 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosOperationMeter.cs +++ /dev/null @@ -1,225 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Telemetry -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.Metrics; - using Microsoft.Azure.Cosmos.Core.Trace; - using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; - - /// - /// CosmosOperationMeter is a utility class responsible for collecting and recording telemetry metrics related to Cosmos DB operations. - /// It includes histograms and counters that capture metrics like request latency, request units, item count, and active instances. - /// - internal static class CosmosOperationMeter - { - /// - /// Meter instance for capturing various metrics related to Cosmos DB operations. - /// - private static readonly Meter OperationMeter = new Meter(CosmosDbClientMetrics.OperationMetrics.MeterName, CosmosDbClientMetrics.OperationMetrics.Version); - - /// - /// Populator Used for Dimension Attributes - /// - private static readonly IActivityAttributePopulator DimensionPopulator = TracesStabilityFactory.GetAttributePopulator(); - - /// - /// Histogram to record request latency (in seconds) for Cosmos DB operations. - /// - private static Histogram RequestLatencyHistogram = null; - - /// - /// Histogram to record request units consumed during Cosmos DB operations. - /// - private static Histogram RequestUnitsHistogram = null; - - /// - /// Histogram to record the actual number of items involved in the operation. - /// - private static Histogram ActualItemHistogram = null; - - /// - /// UpDownCounter to track the number of active instances interacting with Cosmos DB. - /// - private static UpDownCounter ActiveInstanceCounter = null; - - /// - /// Flag to check if metrics is enabled - /// - private static bool IsEnabled = false; - - /// - /// Initializes the histograms and counters for capturing Cosmos DB metrics. - /// - internal static void Initialize() - { - // If already initialized, do not initialize again - if (IsEnabled) - { - return; - } - - CosmosOperationMeter.RequestLatencyHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetrics.OperationMetrics.Name.Latency, - unit: CosmosDbClientMetrics.OperationMetrics.Unit.Sec, - description: CosmosDbClientMetrics.OperationMetrics.Description.Latency); - - CosmosOperationMeter.RequestUnitsHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetrics.OperationMetrics.Name.RequestCharge, - unit: CosmosDbClientMetrics.OperationMetrics.Unit.RequestUnit, - description: CosmosDbClientMetrics.OperationMetrics.Description.RequestCharge); - - CosmosOperationMeter.ActualItemHistogram ??= OperationMeter.CreateHistogram(name: CosmosDbClientMetrics.OperationMetrics.Name.RowCount, - unit: CosmosDbClientMetrics.OperationMetrics.Unit.Count, - description: CosmosDbClientMetrics.OperationMetrics.Description.RowCount); - - CosmosOperationMeter.ActiveInstanceCounter ??= OperationMeter.CreateUpDownCounter(name: CosmosDbClientMetrics.OperationMetrics.Name.ActiveInstances, - unit: CosmosDbClientMetrics.OperationMetrics.Unit.Count, - description: CosmosDbClientMetrics.OperationMetrics.Description.ActiveInstances); - - IsEnabled = true; - } - - /// - /// Records telemetry data related to Cosmos DB operations. This includes request latency, request units, and item counts. - /// - /// Generate operation name - /// The URI of the Cosmos DB account. - /// The name of the container involved in the operation. - /// The name of the database involved in the operation. - /// Optional OpenTelemetry attributes related to the operation. - /// Optional exception object to capture error details. - internal static void RecordTelemetry(Func getOperationName, - Uri accountName, - string containerName, - string databaseName, - OpenTelemetryAttributes attributes = null, - CosmosException ex = null) - { - if (!IsEnabled) - { - return; - } - - try - { - Func[]> dimensionsFunc = () => DimensionPopulator.PopulateOperationMeterDimensions(getOperationName(), containerName, databaseName, accountName, attributes, ex); - - CosmosOperationMeter.RecordActualItemCount(attributes?.ItemCount ?? ex?.Headers?.ItemCount, dimensionsFunc); - CosmosOperationMeter.RecordRequestUnit(attributes?.RequestCharge ?? ex?.Headers?.RequestCharge, dimensionsFunc); - CosmosOperationMeter.RecordRequestLatency(attributes?.Diagnostics?.GetClientElapsedTime() ?? ex?.Diagnostics?.GetClientElapsedTime(), dimensionsFunc); - } - catch (Exception exception) - { - DefaultTrace.TraceWarning($"Failed to record telemetry data for Cosmos DB operation. {exception.StackTrace}"); - } - } - - /// - /// Records the actual item count metric for a Cosmos DB operation. - /// - /// The number of items returned or affected by the operation. - /// A function providing telemetry dimensions for the metric. - private static void RecordActualItemCount(string actualItemCount, Func[]> dimensionsFunc) - { - if (!IsEnabled || CosmosOperationMeter.ActualItemHistogram == null || - !CosmosOperationMeter.ActualItemHistogram.Enabled || string.IsNullOrEmpty(actualItemCount)) - { - return; - } - - CosmosOperationMeter.ActualItemHistogram.Record(Convert.ToInt32(actualItemCount), dimensionsFunc()); - } - - /// - /// Records the request units (RU/s) consumed by the operation. - /// - /// The RU/s value for the operation. - /// A function providing telemetry dimensions for the metric. - private static void RecordRequestUnit(double? requestCharge, Func[]> dimensionsFunc) - { - if (!IsEnabled || CosmosOperationMeter.RequestUnitsHistogram == null || - !CosmosOperationMeter.RequestUnitsHistogram.Enabled || !requestCharge.HasValue) - { - return; - } - - CosmosOperationMeter.RequestUnitsHistogram.Record(requestCharge.Value, dimensionsFunc()); - } - - /// - /// Records the latency (in seconds) for a Cosmos DB operation. - /// - /// The latency of the operation. - /// A function providing telemetry dimensions for the metric. - private static void RecordRequestLatency(TimeSpan? requestLatency, Func[]> dimensionsFunc) - { - if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || - !CosmosOperationMeter.ActiveInstanceCounter.Enabled || !requestLatency.HasValue) - { - return; - } - - CosmosOperationMeter.RequestLatencyHistogram.Record(requestLatency.Value.TotalMilliseconds / 1000, dimensionsFunc()); - } - - /// - /// Increases the count of active Cosmos DB instances. - /// - /// The URI of the account endpoint. - internal static void AddInstanceCount(Uri accountEndpoint) - { - if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || - !CosmosOperationMeter.ActiveInstanceCounter.Enabled) - { - return; - } - - try - { - KeyValuePair[] dimensions = new[] - { - new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountEndpoint.Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) - }; - - CosmosOperationMeter.ActiveInstanceCounter.Add(1, dimensions); - } - catch (Exception exception) - { - DefaultTrace.TraceWarning($"Failed to record InstanceCount telemetry data for Cosmos DB operation. {exception.StackTrace}"); - } - } - - /// - /// Decreases the count of active Cosmos DB instances. - /// - /// The URI of the account endpoint. - internal static void RemoveInstanceCount(Uri accountEndpoint) - { - if (!IsEnabled || CosmosOperationMeter.ActiveInstanceCounter == null || - !CosmosOperationMeter.ActiveInstanceCounter.Enabled) - { - return; - } - - try - { - KeyValuePair[] dimensions = new[] - { - new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountEndpoint.Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) - }; - - CosmosOperationMeter.ActiveInstanceCounter.Add(-1, dimensions); - } - catch (Exception exception) - { - DefaultTrace.TraceWarning($"Failed to record InstanceCount telemetry data for Cosmos DB operation. {exception.StackTrace}"); - } - } - } -} From 963824c17ffa62a58362386d04f94c42c9e83848 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 8 Nov 2024 08:35:32 +0530 Subject: [PATCH 41/49] first drfat uogeated dimensio name refactor code --- Microsoft.Azure.Cosmos.sln | 16 +- Microsoft.Azure.Cosmos/src/DocumentClient.cs | 1 + .../src/Resource/ClientContextCore.cs | 18 +- .../OpenTelemetry/CosmosDbClientMetrics.cs | 116 ++++++++- .../OpenTelemetry/CosmosNetworkMeter.cs | 238 ++++++++++++++++++ .../OpenTelemetryAttributeKeys.cs | 17 +- .../OpenTelemetry/OpenTelemetryAttributes.cs | 1 + .../ClientSideRequestStatisticsTraceDatum.cs | 30 ++- .../Metrics/OpenTelemetryMetricsTest.cs | 15 +- .../Telemetry/NetworkDataRecorderTest.cs | 6 +- .../Tracing/TraceTests.cs | 3 +- .../Tracing/TraceWriterBaselineTests.cs | 14 +- 12 files changed, 446 insertions(+), 29 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs diff --git a/Microsoft.Azure.Cosmos.sln b/Microsoft.Azure.Cosmos.sln index caafa0b41a..a9e73c529c 100644 --- a/Microsoft.Azure.Cosmos.sln +++ b/Microsoft.Azure.Cosmos.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29123.88 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35431.28 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos", "Microsoft.Azure.Cosmos\src\Microsoft.Azure.Cosmos.csproj", "{36F6F6A8-CEC8-4261-9948-903495BC3C25}" EndProject @@ -180,6 +180,18 @@ Global {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|Any CPU.Build.0 = Release|Any CPU {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|x64.ActiveCfg = Release|Any CPU {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|x64.Build.0 = Release|Any CPU + {D744906A-1091-403F-B0B6-794DE045169A}.Cover|Any CPU.ActiveCfg = Debug|Any CPU + {D744906A-1091-403F-B0B6-794DE045169A}.Cover|Any CPU.Build.0 = Debug|Any CPU + {D744906A-1091-403F-B0B6-794DE045169A}.Cover|x64.ActiveCfg = Debug|Any CPU + {D744906A-1091-403F-B0B6-794DE045169A}.Cover|x64.Build.0 = Debug|Any CPU + {D744906A-1091-403F-B0B6-794DE045169A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D744906A-1091-403F-B0B6-794DE045169A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D744906A-1091-403F-B0B6-794DE045169A}.Debug|x64.ActiveCfg = Debug|Any CPU + {D744906A-1091-403F-B0B6-794DE045169A}.Debug|x64.Build.0 = Debug|Any CPU + {D744906A-1091-403F-B0B6-794DE045169A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D744906A-1091-403F-B0B6-794DE045169A}.Release|Any CPU.Build.0 = Release|Any CPU + {D744906A-1091-403F-B0B6-794DE045169A}.Release|x64.ActiveCfg = Release|Any CPU + {D744906A-1091-403F-B0B6-794DE045169A}.Release|x64.Build.0 = Release|Any CPU {CE4D6DA8-148D-4A98-943B-D8C2D532E1DC}.Cover|Any CPU.ActiveCfg = Debug|Any CPU {CE4D6DA8-148D-4A98-943B-D8C2D532E1DC}.Cover|Any CPU.Build.0 = Debug|Any CPU {CE4D6DA8-148D-4A98-943B-D8C2D532E1DC}.Cover|x64.ActiveCfg = Debug|Any CPU diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index 984ad0371a..9816cbac80 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -959,6 +959,7 @@ internal virtual void Initialize(Uri serviceEndpoint, if (this.cosmosClientTelemetryOptions.IsClientMetricsEnabled) { CosmosDbOperationMeter.Initialize(); + CosmosNetworkMeter.Initialize(); CosmosDbOperationMeter.AddInstanceCount(this.ServiceEndpoint); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index 0c0768a8a9..a6ea71cf2f 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -533,17 +533,23 @@ private async Task RunWithDiagnosticsHelperAsync( && (!this.ClientOptions.CosmosClientTelemetryOptions.DisableDistributedTracing || this.ClientOptions.CosmosClientTelemetryOptions.IsClientMetricsEnabled)) { // Extracts and records telemetry data from the result of the operation. - OpenTelemetryAttributes response = openTelemetry?.Item2(result); + OpenTelemetryAttributes otelAttributes = openTelemetry?.Item2(result); // Records the telemetry attributes for Distributed Tracing (if enabled) - recorder.Record(response); + recorder.Record(otelAttributes); // Records metrics such as request units, latency, and item count for the operation. CosmosDbOperationMeter.RecordTelemetry(getOperationName: getOperationName, accountName: this.client.Endpoint, containerName: containerName, databaseName: databaseName, - attributes: response); + attributes: otelAttributes); + + CosmosNetworkMeter.RecordTelemetry(getOperationName: getOperationName, + accountName: this.client.Endpoint, + containerName: containerName, + databaseName: databaseName, + attributes: otelAttributes); } return result; } @@ -584,6 +590,12 @@ private async Task RunWithDiagnosticsHelperAsync( containerName: containerName, databaseName: databaseName, ex: cosmosException); + + CosmosNetworkMeter.RecordTelemetry(getOperationName: getOperationName, + accountName: this.client.Endpoint, + containerName: containerName, + databaseName: databaseName, + ex: cosmosException); } throw; diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs index f295022504..bcaf0818fe 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.Cosmos public sealed class CosmosDbClientMetrics { /// - /// OperationMetrics + /// Operation Metrics /// public static class OperationMetrics { @@ -101,6 +101,120 @@ public static class Description } } + /// + /// Network Metrics + /// + public static class NetworkMetrics + { + /// + /// the name of the operation meter + /// + public const string MeterName = "Azure.Cosmos.Client.Request"; + + /// + /// Version of the operation meter + /// + public const string Version = "1.0.0"; + + /// + /// Metric Names + /// + public static class Name + { + /// + /// Network Call Latency + /// + public const string Latency = "db.client.cosmosdb.request.duration"; + + /// + /// Request Payload Size + /// + public const string RequestBodySize = "db.client.cosmosdb.request.body.size"; + + /// + /// Request Payload Size + /// + public const string ResponseBodySize = "db.client.cosmosdb.response.body.size"; + + /// + /// Channel Aquisition Latency + /// + public const string ChannelAquisitionLatency = "db.client.cosmosdb.request.channel_aquisition.duration"; + + /// + /// Backend Server Latency + /// + public const string BackendLatency = "db.server.cosmosdb.request.duration"; + + /// + /// Transit Time Latency + /// + public const string TransitTimeLatency = "db.client.cosmosdb.request.transit.duration"; + + /// + /// Received Time Latency + /// + public const string ReceivedTimeLatency = "db.client.cosmosdb.request.received.duration"; + } + + /// + /// Unit for metrics + /// + public static class Unit + { + /// + /// Unit representing bytes + /// + public const string Bytes = "bytes"; + + /// + /// Unit representing time in seconds + /// + public const string Sec = "s"; + } + + /// + /// Provides descriptions for metrics. + /// + public static class Description + { + /// + /// Network Call Latency + /// + public const string Latency = "Duration of client requests."; + + /// + /// Request Payload Size + /// + public const string RequestBodySize = "Size of client request body."; + + /// + /// Request Payload Size + /// + public const string ResponseBodySize = "Size of client response body."; + + /// + /// Channel Aquisition Latency + /// + public const string ChannelAquisitionLatency = "The duration of the successfully established outbound TCP connections. i.e. Channel Aquisition Time (for direct mode)."; + + /// + /// Backend Server Latency + /// + public const string BackendLatency = "Backend Latency (for direct mode)."; + + /// + /// Transit Time Latency + /// + public const string TransitTimeLatency = "Time spent on the wire (for direct mode)."; + + /// + /// Received Time Latency + /// + public const string ReceivedTimeLatency = "Time spent on 'Received' stage (for direct mode)."; + } + } + /// /// Buckets /// diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs new file mode 100644 index 0000000000..e9a18b5b64 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs @@ -0,0 +1,238 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Telemetry +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Metrics; + using System.Linq; + using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Cosmos.Tracing.TraceData; + + internal static class CosmosNetworkMeter + { + /// + /// Meter instance for capturing various metrics related to Cosmos DB operations. + /// + internal static Meter NetworkMeter = new Meter(CosmosDbClientMetrics.NetworkMetrics.MeterName, CosmosDbClientMetrics.NetworkMetrics.Version); + + internal static Histogram RequestLatencyHistogram = null; + + internal static Histogram RequestBodySizeHistogram = null; + + internal static Histogram ResponseBodySizeHistogram = null; + + internal static Histogram ChannelAquisitionLatencyHistogram = null; + + internal static Histogram BackendLatencyHistogram = null; + + internal static Histogram TransitLatencyHistogram = null; + + internal static Histogram ReceivedLatencyHistogram = null; + + private static bool IsEnabled = false; + + /// + /// Initializes the histograms and counters for capturing Cosmos DB metrics. + /// + internal static void Initialize() + { + // If already initialized, do not initialize again + if (IsEnabled) + { + return; + } + + CosmosNetworkMeter.RequestLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.Latency, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, + description: CosmosDbClientMetrics.NetworkMetrics.Description.Latency); + + CosmosNetworkMeter.RequestBodySizeHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.RequestBodySize, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Bytes, + description: CosmosDbClientMetrics.NetworkMetrics.Description.RequestBodySize); + + CosmosNetworkMeter.ResponseBodySizeHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.ResponseBodySize, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Bytes, + description: CosmosDbClientMetrics.NetworkMetrics.Description.ResponseBodySize); + + CosmosNetworkMeter.ChannelAquisitionLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.ChannelAquisitionLatency, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, + description: CosmosDbClientMetrics.NetworkMetrics.Description.ChannelAquisitionLatency); + + CosmosNetworkMeter.BackendLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.BackendLatency, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, + description: CosmosDbClientMetrics.NetworkMetrics.Description.BackendLatency); + + CosmosNetworkMeter.TransitLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.TransitTimeLatency, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, + description: CosmosDbClientMetrics.NetworkMetrics.Description.TransitTimeLatency); + + CosmosNetworkMeter.ReceivedLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.ReceivedTimeLatency, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, + description: CosmosDbClientMetrics.NetworkMetrics.Description.ReceivedTimeLatency); + + IsEnabled = true; + } + + internal static void RecordTelemetry(Func getOperationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes = null, + CosmosException ex = null) + { + if (!IsEnabled) + { + return; + } + + SummaryDiagnostics summaryDiagnostics + = new SummaryDiagnostics(((CosmosTraceDiagnostics)(ex?.Diagnostics ?? attributes?.Diagnostics)).Value); + summaryDiagnostics.StoreResponseStatistics.Value.ForEach(stat => + { + if (stat?.StoreResult == null) + { + return; + } + + KeyValuePair[] dimension = GetDimensions( + getOperationName, accountName, containerName, databaseName, attributes, ex, tcpStats: stat); + + CosmosNetworkMeter.RecordRequestLatency(stat.RequestLatency.TotalSeconds, dimension); + CosmosNetworkMeter.RecordRequestBodySize(stat?.StoreResult?.TransportRequestStats?.RequestBodySizeInBytes, dimension); + CosmosNetworkMeter.RecordResponseBodySize(stat?.StoreResult?.TransportRequestStats?.ResponseBodySizeInBytes, dimension); + CosmosNetworkMeter.RecordBackendLatency(stat?.StoreResult?.BackendRequestDurationInMs, dimension); + }); + + summaryDiagnostics.HttpResponseStatistics.Value.ForEach(stat => + { + KeyValuePair[] dimension = GetDimensions( + getOperationName, accountName, containerName, databaseName, attributes, ex, httpStats: stat); + + CosmosNetworkMeter.RecordRequestLatency(stat.Duration.TotalSeconds, dimension); + CosmosNetworkMeter.RecordRequestBodySize(stat.HttpResponseMessage?.RequestMessage?.Content?.Headers?.ContentLength, dimension); + CosmosNetworkMeter.RecordResponseBodySize(stat.ResponseContentLength, dimension); + }); + } + + private static KeyValuePair[] GetDimensions(Func getOperationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes, + CosmosException ex, + ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats = null, + ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats = null) + { + return new KeyValuePair[] + { + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), + new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName), + new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName?.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName?.Port), + new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, getOperationName()), + new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), + new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode), + new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes?.ConsistencyLevel ?? ex?.Headers?.ConsistencyLevel), + new KeyValuePair(OpenTelemetryAttributeKeys.NetworkProtocolName, GetEndpoint(tcpStats, httpStats).Scheme), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointHost, GetEndpoint(tcpStats, httpStats).Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndPointPort, GetEndpoint(tcpStats, httpStats).Port), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointResourceId, GetEndpoint(tcpStats, httpStats).PathAndQuery), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointStatusCode, GetStatusCode(tcpStats, httpStats)), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointSubStatusCode, GetSubStatusCode(tcpStats, httpStats)), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointRegion, GetRegion(tcpStats, httpStats)), + new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, GetException(tcpStats, httpStats)) + }; + } + + private static string GetRegion(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) + { + return httpStats?.Region ?? tcpStats.Region; + } + + private static string GetException(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) + { + return httpStats?.Exception?.Message ?? tcpStats?.StoreResult?.Exception?.Message; + } + + private static int GetSubStatusCode(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) + { + int? subStatuscode = null; + if (httpStats.HasValue && + httpStats.Value.HttpResponseMessage?.Headers != null && + httpStats.Value + .HttpResponseMessage + .Headers + .TryGetValues(Documents.WFConstants.BackendHeaders.SubStatus, out IEnumerable statuscodes)) + { + subStatuscode = Convert.ToInt32(statuscodes.FirstOrDefault()); + } + + return subStatuscode ?? Convert.ToInt32(tcpStats?.StoreResult?.SubStatusCode); + } + + private static int? GetStatusCode(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) + { + return (int?)httpStats?.HttpResponseMessage?.StatusCode ?? (int?)tcpStats?.StoreResult?.StatusCode; + } + + private static Uri GetEndpoint(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) + { + return httpStats?.RequestUri ?? tcpStats?.LocationEndpoint; + } + + internal static void RecordRequestLatency(double latency, KeyValuePair[] dimension) + { + if (!IsEnabled || CosmosNetworkMeter.RequestLatencyHistogram == null || + !CosmosNetworkMeter.RequestLatencyHistogram.Enabled) + { + return; + } + + CosmosNetworkMeter.RequestLatencyHistogram.Record(latency, dimension); + } + + internal static void RecordRequestBodySize(long? bodySize, KeyValuePair[] dimension) + { + if (!IsEnabled || + CosmosNetworkMeter.RequestBodySizeHistogram == null || + !bodySize.HasValue || + !CosmosNetworkMeter.ResponseBodySizeHistogram.Enabled) + { + return; + } + + CosmosNetworkMeter.RequestBodySizeHistogram.Record(bodySize.Value, dimension); + } + + internal static void RecordResponseBodySize(long? bodySize, KeyValuePair[] dimension) + { + if (!IsEnabled || + CosmosNetworkMeter.ResponseBodySizeHistogram == null || + !bodySize.HasValue || + !CosmosNetworkMeter.ResponseBodySizeHistogram.Enabled) + { + return; + } + + CosmosNetworkMeter.ResponseBodySizeHistogram.Record(bodySize.Value, dimension); + } + + internal static void RecordBackendLatency(string latencyInMs, KeyValuePair[] dimension) + { + if (!IsEnabled || + CosmosNetworkMeter.BackendLatencyHistogram == null || + string.IsNullOrEmpty(latencyInMs) || + !CosmosNetworkMeter.BackendLatencyHistogram.Enabled) + { + return; + } + + CosmosNetworkMeter.BackendLatencyHistogram.Record(Convert.ToDouble(latencyInMs) / 1000, dimension); + } + + } +} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs index e6b822ca90..f1aa07b6d7 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs @@ -170,11 +170,22 @@ internal sealed class OpenTelemetryAttributeKeys : IActivityAttributePopulator /// public const string ExceptionStacktrace = "exception.stacktrace"; - /// - /// Represents the type of error. - /// public const string ErrorType = "error.type"; + public const string NetworkProtocolName = "network.protocol.name"; + + public const string ServiceEndpointHost = "network.protocol.host"; + + public const string ServiceEndPointPort = "network.protocol.port"; + + public const string ServiceEndpointStatusCode = "db.cosmosdb.network.response.status_code"; + + public const string ServiceEndpointSubStatusCode = "db.cosmosdb.network.response.sub_status_code"; + + public const string ServiceEndpointRegion = "cloud.region"; + + public const string ServiceEndpointResourceId = "db.cosmosdb.network.routing_id "; + public void PopulateAttributes(DiagnosticScope scope, string operationName, string databaseName, diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributes.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributes.cs index 3206266291..f5b491c65f 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributes.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributes.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry { using System.Net; + using Microsoft.Azure.Cosmos.Diagnostics; using Microsoft.Azure.Cosmos.Query.Core; internal class OpenTelemetryAttributes diff --git a/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientSideRequestStatisticsTraceDatum.cs b/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientSideRequestStatisticsTraceDatum.cs index 829899c4b1..5c45957081 100644 --- a/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientSideRequestStatisticsTraceDatum.cs +++ b/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientSideRequestStatisticsTraceDatum.cs @@ -12,6 +12,7 @@ namespace Microsoft.Azure.Cosmos.Tracing.TraceData using System.Text; using Microsoft.Azure.Cosmos.Handler; using Microsoft.Azure.Cosmos.Json; + using Microsoft.Azure.Cosmos.Pagination; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Rntbd; @@ -260,7 +261,8 @@ public void RecordResponse( request.ResourceType, request.OperationType, request.Headers[HttpConstants.HttpHeaders.SessionToken], - locationEndpoint); + locationEndpoint, + regionName); lock (this.storeResponseStatistics) { @@ -352,8 +354,9 @@ public void RecordHttpResponse(HttpRequestMessage request, lock (this.httpResponseStatistics) { Uri locationEndpoint = request.RequestUri; + object regionName = null; if (request.Properties != null && - request.Properties.TryGetValue(HttpRequestRegionNameProperty, out object regionName)) + request.Properties.TryGetValue(HttpRequestRegionNameProperty, out regionName)) { this.TraceSummary.AddRegionContacted(Convert.ToString(regionName), locationEndpoint); } @@ -365,7 +368,8 @@ public void RecordHttpResponse(HttpRequestMessage request, request.Method, resourceType, response, - exception: null)); + exception: null, + region: Convert.ToString(regionName))); } } @@ -380,8 +384,10 @@ public void RecordHttpException(HttpRequestMessage request, lock (this.httpResponseStatistics) { Uri locationEndpoint = request.RequestUri; + + object regionName = null; if (request.Properties != null && - request.Properties.TryGetValue(HttpRequestRegionNameProperty, out object regionName)) + request.Properties.TryGetValue(HttpRequestRegionNameProperty, out regionName)) { this.TraceSummary.AddRegionContacted(Convert.ToString(regionName), locationEndpoint); } @@ -393,7 +399,8 @@ public void RecordHttpException(HttpRequestMessage request, request.Method, resourceType, responseMessage: null, - exception: exception)); + exception: exception, + region: Convert.ToString(regionName))); } } @@ -460,7 +467,8 @@ public StoreResponseStatistics( ResourceType resourceType, OperationType operationType, string requestSessionToken, - Uri locationEndpoint) + Uri locationEndpoint, + string region) { this.RequestStartTime = requestStartTime; this.RequestResponseTime = requestResponseTime; @@ -470,8 +478,10 @@ public StoreResponseStatistics( this.RequestSessionToken = requestSessionToken; this.LocationEndpoint = locationEndpoint; this.IsSupplementalResponse = operationType == OperationType.Head || operationType == OperationType.HeadFeed; + this.Region = region; } + public string Region { get; } public DateTime? RequestStartTime { get; } public DateTime RequestResponseTime { get; } public StoreResult StoreResult { get; } @@ -492,7 +502,8 @@ public HttpResponseStatistics( HttpMethod httpMethod, ResourceType resourceType, HttpResponseMessage responseMessage, - Exception exception) + Exception exception, + string region) { this.RequestStartTime = requestStartTime; this.Duration = requestEndTime - requestStartTime; @@ -501,7 +512,8 @@ public HttpResponseStatistics( this.ResourceType = resourceType; this.HttpMethod = httpMethod; this.RequestUri = requestUri; - + this.Region = region; + this.ResponseContentLength = responseMessage?.Content?.Headers?.ContentLength; if (responseMessage != null) { Headers headers = new Headers(GatewayStoreClient.ExtractResponseHeaders(responseMessage)); @@ -513,6 +525,7 @@ public HttpResponseStatistics( } } + public string Region { get; } public DateTime RequestStartTime { get; } public TimeSpan Duration { get; } public HttpResponseMessage HttpResponseMessage { get; } @@ -521,6 +534,7 @@ public HttpResponseStatistics( public HttpMethod HttpMethod { get; } public Uri RequestUri { get; } public string ActivityId { get; } + public long? ResponseContentLength { get; } } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs index b1789176aa..9cbf959897 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -25,7 +25,14 @@ public class OpenTelemetryMetricsTest : BaseCosmosClientHelper { "db.client.operation.duration", MetricType.Histogram }, { "db.client.response.row_count", MetricType.Histogram}, { "db.client.cosmosdb.operation.request_charge", MetricType.Histogram }, - { "db.client.cosmosdb.active_instance.count", MetricType.LongSumNonMonotonic } + { "db.client.cosmosdb.active_instance.count", MetricType.LongSumNonMonotonic }, + { "db.client.cosmosdb.request.duration", MetricType.Histogram}, + { "db.client.cosmosdb.request.body.size", MetricType.Histogram}, + { "db.client.cosmosdb.response.body.size", MetricType.Histogram}, + { "db.server.cosmosdb.request.duration", MetricType.Histogram}, + /* { "db.client.cosmosdb.request.channel_aquisition.duration", MetricType.Histogram}, + { "db.client.cosmosdb.request.transit.duration", MetricType.Histogram}, + { "db.client.cosmosdb.request.received.duration", MetricType.Histogram}*/ }; private MeterProvider meterProvider; @@ -35,8 +42,8 @@ public async Task Init() // Initialize OpenTelemetry MeterProvider this.meterProvider = Sdk .CreateMeterProviderBuilder() - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Azure Cosmos DB Operation Level Metrics")) - .AddMeter(CosmosDbClientMetrics.OperationMetrics.MeterName) + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Azure Cosmos DB Metrics")) + .AddMeter(CosmosDbClientMetrics.OperationMetrics.MeterName, CosmosDbClientMetrics.NetworkMetrics.MeterName) .AddView( instrumentName: CosmosDbClientMetrics.OperationMetrics.Name.RequestCharge, metricStreamConfiguration: new ExplicitBucketHistogramConfiguration @@ -75,7 +82,7 @@ public async Task Cleanup() } [TestMethod] - public async Task OperationLevelMetricsGenerationTest() + public async Task MetricsGenerationTest() { Container container = await this.database.CreateContainerIfNotExistsAsync(Guid.NewGuid().ToString(), "/pk", throughput: 10000); for (int count = 0; count < 10; count++) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Telemetry/NetworkDataRecorderTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Telemetry/NetworkDataRecorderTest.cs index fc6c8a25c3..be2600c31d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Telemetry/NetworkDataRecorderTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Telemetry/NetworkDataRecorderTest.cs @@ -36,7 +36,8 @@ public void TestRecordWithErroredAndHighLatencyRequests() resourceType: Documents.ResourceType.Document, operationType: OperationType.Create, requestSessionToken: default, - locationEndpoint: new Uri("https://dummy.url")), + locationEndpoint: new Uri("https://dummy.url"), + "region1"), new StoreResponseStatistics( requestStartTime: DateTime.Now, @@ -48,7 +49,8 @@ public void TestRecordWithErroredAndHighLatencyRequests() resourceType: Documents.ResourceType.Document, operationType: OperationType.Create, requestSessionToken: default, - locationEndpoint: new Uri("https://dummy.url")) + locationEndpoint: new Uri("https://dummy.url"), + "region1") }; recorder.Record(stats, "databaseId", "containerId"); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceTests.cs index 6e8b42b552..07472a3a44 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceTests.cs @@ -109,7 +109,8 @@ public void ValidateStoreResultSerialization() ResourceType.Document, OperationType.Query, "42", - new Uri("http://someUri1.com")); + new Uri("http://someUri1.com"), + "region1"); ((List)datum.GetType().GetField("storeResponseStatistics", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(datum)).Add(storeResponseStatistics); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceWriterBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceWriterBaselineTests.cs index cefb82338d..9488d2373a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceWriterBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceWriterBaselineTests.cs @@ -367,7 +367,8 @@ public void TraceData() ResourceType.Document, OperationType.Query, "42", - uri1.Uri); + uri1.Uri, + "region1"); TraceWriterBaselineTests.GetPrivateField>(datum, "storeResponseStatistics").Add(storeResponseStatistics); rootTrace.AddDatum("Client Side Request Stats", datum); @@ -401,7 +402,8 @@ public void TraceData() resourceType: default, operationType: default, requestSessionToken: default, - locationEndpoint: default); + locationEndpoint: default, + "region1"); TraceWriterBaselineTests.GetPrivateField>(datum, "storeResponseStatistics").Add(storeResponseStatistics); rootTrace.AddDatum("Client Side Request Stats Default", datum); @@ -426,7 +428,8 @@ public void TraceData() HttpMethod.Get, ResourceType.Document, new HttpResponseMessage(System.Net.HttpStatusCode.OK) { ReasonPhrase = "Success" }, - exception: null); + exception: null, + "region1"); TraceWriterBaselineTests.GetPrivateField>(datum, "httpResponseStatistics").Add(httpResponseStatistics); @@ -437,7 +440,8 @@ public void TraceData() HttpMethod.Get, ResourceType.Document, responseMessage: null, - exception: new OperationCanceledException()); + exception: new OperationCanceledException(), + "region1"); TraceWriterBaselineTests.GetPrivateField>(datum, "httpResponseStatistics").Add(httpResponseStatisticsException); rootTrace.AddDatum("Client Side Request Stats", datum); @@ -939,7 +943,7 @@ public static TraceForBaselineTesting GetRootTrace() return new TraceForBaselineTesting("Trace For Baseline Testing", TraceLevel.Info, TraceComponent.Unknown, parent: null); } - public void UpdateRegionContacted(TraceDatum traceDatum) + public void UpdateRegionContacted(TraceDatum _) { //NoImplementation } From 35c31415a7557b567a27661c17126dce06ab1d50 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Mon, 18 Nov 2024 14:39:34 +0530 Subject: [PATCH 42/49] updated --- .../AppInsightClassicAttributeKeys.cs | 19 ++++- .../OpenTelemetry/CosmosNetworkMeter.cs | 81 +++---------------- .../OpenTelemetry/DatabaseDupAttributeKeys.cs | 25 ++++++ .../IActivityAttributePopulator.cs | 10 +++ .../OpenTelemetryAttributeKeys.cs | 71 +++++++++++++++- 5 files changed, 129 insertions(+), 77 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs index daf1b31cbe..450daa0d58 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System; using System.Collections.Generic; using global::Azure.Core; + using Microsoft.Azure.Cosmos.Tracing.TraceData; internal sealed class AppInsightClassicAttributeKeys : IActivityAttributePopulator { @@ -162,17 +163,29 @@ public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMo } } + public KeyValuePair[] PopulateNetworkMeterDimensions(string operationName, Uri accountName, string containerName, string databaseName, OpenTelemetryAttributes attributes, CosmosException ex, ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats = null, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats = null) + { + return new KeyValuePair[] + { + new KeyValuePair(AppInsightClassicAttributeKeys.ContainerName, containerName), + new KeyValuePair(AppInsightClassicAttributeKeys.DbName, databaseName), + new KeyValuePair(AppInsightClassicAttributeKeys.ServerAddress, accountName?.Host), + new KeyValuePair(AppInsightClassicAttributeKeys.DbOperation, operationName), + new KeyValuePair(AppInsightClassicAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), + new KeyValuePair(AppInsightClassicAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode) + }; + } + public KeyValuePair[] PopulateOperationMeterDimensions(string operationName, string containerName, string databaseName, Uri accountName, OpenTelemetryAttributes attributes, CosmosException ex) { return new KeyValuePair[] { new KeyValuePair(AppInsightClassicAttributeKeys.ContainerName, containerName), new KeyValuePair(AppInsightClassicAttributeKeys.DbName, databaseName), - new KeyValuePair(AppInsightClassicAttributeKeys.ServerAddress, accountName.Host), + new KeyValuePair(AppInsightClassicAttributeKeys.ServerAddress, accountName?.Host), new KeyValuePair(AppInsightClassicAttributeKeys.DbOperation, operationName), new KeyValuePair(AppInsightClassicAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), - new KeyValuePair(AppInsightClassicAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode), - new KeyValuePair(AppInsightClassicAttributeKeys.Region, string.Join(",", attributes.Diagnostics.GetContactedRegions())) + new KeyValuePair(AppInsightClassicAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode) }; } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs index e9a18b5b64..da356c6737 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs @@ -7,8 +7,8 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System; using System.Collections.Generic; using System.Diagnostics.Metrics; - using System.Linq; using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; using Microsoft.Azure.Cosmos.Tracing.TraceData; internal static class CosmosNetworkMeter @@ -34,6 +34,8 @@ internal static class CosmosNetworkMeter private static bool IsEnabled = false; + private static IActivityAttributePopulator activityAttributePopulator; + /// /// Initializes the histograms and counters for capturing Cosmos DB metrics. /// @@ -45,6 +47,8 @@ internal static void Initialize() return; } + activityAttributePopulator = TracesStabilityFactory.GetAttributePopulator(); + CosmosNetworkMeter.RequestLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.Latency, unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, description: CosmosDbClientMetrics.NetworkMetrics.Description.Latency); @@ -97,8 +101,8 @@ SummaryDiagnostics summaryDiagnostics return; } - KeyValuePair[] dimension = GetDimensions( - getOperationName, accountName, containerName, databaseName, attributes, ex, tcpStats: stat); + KeyValuePair[] dimension = activityAttributePopulator.PopulateNetworkMeterDimensions( + getOperationName(), accountName, containerName, databaseName, attributes, ex, tcpStats: stat); CosmosNetworkMeter.RecordRequestLatency(stat.RequestLatency.TotalSeconds, dimension); CosmosNetworkMeter.RecordRequestBodySize(stat?.StoreResult?.TransportRequestStats?.RequestBodySizeInBytes, dimension); @@ -108,8 +112,8 @@ SummaryDiagnostics summaryDiagnostics summaryDiagnostics.HttpResponseStatistics.Value.ForEach(stat => { - KeyValuePair[] dimension = GetDimensions( - getOperationName, accountName, containerName, databaseName, attributes, ex, httpStats: stat); + KeyValuePair[] dimension = activityAttributePopulator.PopulateNetworkMeterDimensions( + getOperationName(), accountName, containerName, databaseName, attributes, ex, httpStats: stat); CosmosNetworkMeter.RecordRequestLatency(stat.Duration.TotalSeconds, dimension); CosmosNetworkMeter.RecordRequestBodySize(stat.HttpResponseMessage?.RequestMessage?.Content?.Headers?.ContentLength, dimension); @@ -117,73 +121,6 @@ SummaryDiagnostics summaryDiagnostics }); } - private static KeyValuePair[] GetDimensions(Func getOperationName, - Uri accountName, - string containerName, - string databaseName, - OpenTelemetryAttributes attributes, - CosmosException ex, - ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats = null, - ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats = null) - { - return new KeyValuePair[] - { - new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), - new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName), - new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName?.Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName?.Port), - new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, getOperationName()), - new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), - new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode), - new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes?.ConsistencyLevel ?? ex?.Headers?.ConsistencyLevel), - new KeyValuePair(OpenTelemetryAttributeKeys.NetworkProtocolName, GetEndpoint(tcpStats, httpStats).Scheme), - new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointHost, GetEndpoint(tcpStats, httpStats).Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndPointPort, GetEndpoint(tcpStats, httpStats).Port), - new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointResourceId, GetEndpoint(tcpStats, httpStats).PathAndQuery), - new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointStatusCode, GetStatusCode(tcpStats, httpStats)), - new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointSubStatusCode, GetSubStatusCode(tcpStats, httpStats)), - new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointRegion, GetRegion(tcpStats, httpStats)), - new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, GetException(tcpStats, httpStats)) - }; - } - - private static string GetRegion(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) - { - return httpStats?.Region ?? tcpStats.Region; - } - - private static string GetException(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) - { - return httpStats?.Exception?.Message ?? tcpStats?.StoreResult?.Exception?.Message; - } - - private static int GetSubStatusCode(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) - { - int? subStatuscode = null; - if (httpStats.HasValue && - httpStats.Value.HttpResponseMessage?.Headers != null && - httpStats.Value - .HttpResponseMessage - .Headers - .TryGetValues(Documents.WFConstants.BackendHeaders.SubStatus, out IEnumerable statuscodes)) - { - subStatuscode = Convert.ToInt32(statuscodes.FirstOrDefault()); - } - - return subStatuscode ?? Convert.ToInt32(tcpStats?.StoreResult?.SubStatusCode); - } - - private static int? GetStatusCode(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) - { - return (int?)httpStats?.HttpResponseMessage?.StatusCode ?? (int?)tcpStats?.StoreResult?.StatusCode; - } - - private static Uri GetEndpoint(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) - { - return httpStats?.RequestUri ?? tcpStats?.LocationEndpoint; - } - internal static void RecordRequestLatency(double latency, KeyValuePair[] dimension) { if (!IsEnabled || CosmosNetworkMeter.RequestLatencyHistogram == null || diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/DatabaseDupAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/DatabaseDupAttributeKeys.cs index 9cbb173ad8..a133d94718 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/DatabaseDupAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/DatabaseDupAttributeKeys.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System.Collections.Generic; using System.Linq; using global::Azure.Core; + using Microsoft.Azure.Cosmos.Tracing.TraceData; internal class DatabaseDupAttributeKeys : IActivityAttributePopulator { @@ -38,6 +39,30 @@ public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMo this.otelPopulator.PopulateAttributes(scope, queryTextMode, operationType, response); } + public KeyValuePair[] PopulateNetworkMeterDimensions(string operationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes, + CosmosException ex, + ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats = null, + ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats = null) + { + KeyValuePair[] appInsightDimensions = this.appInsightPopulator + .PopulateNetworkMeterDimensions(operationName, accountName, containerName, databaseName, attributes, ex, tcpStats, httpStats) + .ToArray(); + KeyValuePair[] otelDimensions = this.otelPopulator + .PopulateNetworkMeterDimensions(operationName, accountName, containerName, databaseName, attributes, ex, tcpStats, httpStats) + .ToArray(); + + KeyValuePair[] dimensions + = new KeyValuePair[appInsightDimensions.Length + otelDimensions.Length]; + dimensions + .Concat(appInsightDimensions) + .Concat(otelDimensions); + return dimensions; + } + public KeyValuePair[] PopulateOperationMeterDimensions(string operationName, string containerName, string databaseName, diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs index f7ad05f36a..ff34ffccc6 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System.Collections.Generic; using System.Xml.Linq; using global::Azure.Core; + using Microsoft.Azure.Cosmos.Tracing.TraceData; internal interface IActivityAttributePopulator { @@ -33,5 +34,14 @@ public KeyValuePair[] PopulateOperationMeterDimensions(string op string databaseName, Uri accountName, OpenTelemetryAttributes attributes, CosmosException ex); + + public KeyValuePair[] PopulateNetworkMeterDimensions(string operationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes, + CosmosException ex, + ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats = null, + ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats = null); } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs index f1aa07b6d7..14ee5a532f 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs @@ -4,11 +4,11 @@ namespace Microsoft.Azure.Cosmos.Telemetry { - using System; + using System; using System.Collections.Generic; using System.Linq; using global::Azure.Core; - using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Cosmos.Tracing.TraceData; /// /// Contains constant string values representing OpenTelemetry attribute keys for monitoring and tracing Cosmos DB operations. @@ -261,6 +261,37 @@ public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMo } + public KeyValuePair[] PopulateNetworkMeterDimensions(string operationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes, + CosmosException ex, + ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats = null, + ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats = null) + { + return new KeyValuePair[] + { + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), + new KeyValuePair(OpenTelemetryAttributeKeys.ContainerName, containerName), + new KeyValuePair(OpenTelemetryAttributeKeys.DbName, databaseName), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountName?.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountName?.Port), + new KeyValuePair(OpenTelemetryAttributeKeys.DbOperation, operationName), + new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), + new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode), + new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, attributes?.ConsistencyLevel ?? ex?.Headers?.ConsistencyLevel), + new KeyValuePair(OpenTelemetryAttributeKeys.NetworkProtocolName, GetEndpoint(tcpStats, httpStats).Scheme), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointHost, GetEndpoint(tcpStats, httpStats).Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndPointPort, GetEndpoint(tcpStats, httpStats).Port), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointResourceId, GetEndpoint(tcpStats, httpStats).PathAndQuery), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointStatusCode, GetStatusCode(tcpStats, httpStats)), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointSubStatusCode, GetSubStatusCode(tcpStats, httpStats)), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointRegion, GetRegion(tcpStats, httpStats)), + new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, GetException(tcpStats, httpStats)) + }; + } + public KeyValuePair[] PopulateOperationMeterDimensions(string operationName, string containerName, string databaseName, Uri accountName, OpenTelemetryAttributes attributes, CosmosException ex) { @@ -292,5 +323,41 @@ private static string[] GetRegions(CosmosDiagnostics diagnostics) .Distinct() .ToArray(); } + + private static int? GetStatusCode(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) + { + return (int?)httpStats?.HttpResponseMessage?.StatusCode ?? (int?)tcpStats?.StoreResult?.StatusCode; + } + + private static Uri GetEndpoint(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) + { + return httpStats?.RequestUri ?? tcpStats?.LocationEndpoint; + } + + private static string GetRegion(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) + { + return httpStats?.Region ?? tcpStats.Region; + } + + private static string GetException(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) + { + return httpStats?.Exception?.Message ?? tcpStats?.StoreResult?.Exception?.Message; + } + + private static int GetSubStatusCode(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) + { + int? subStatuscode = null; + if (httpStats.HasValue && + httpStats.Value.HttpResponseMessage?.Headers != null && + httpStats.Value + .HttpResponseMessage + .Headers + .TryGetValues(Documents.WFConstants.BackendHeaders.SubStatus, out IEnumerable statuscodes)) + { + subStatuscode = Convert.ToInt32(statuscodes.FirstOrDefault()); + } + + return subStatuscode ?? Convert.ToInt32(tcpStats?.StoreResult?.SubStatusCode); + } } } From eb289e54b03767ad33121722aba576bc52ac005a Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Wed, 20 Nov 2024 06:36:35 +0530 Subject: [PATCH 43/49] fix test --- .../OpenTelemetry/CosmosNetworkMeter.cs | 57 +++++++++++++++++++ .../Metrics/OpenTelemetryMetricsTest.cs | 4 +- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs index da356c6737..e58ae934ce 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry using Microsoft.Azure.Cosmos.Diagnostics; using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; using Microsoft.Azure.Cosmos.Tracing.TraceData; + using Newtonsoft.Json; internal static class CosmosNetworkMeter { @@ -108,6 +109,29 @@ SummaryDiagnostics summaryDiagnostics CosmosNetworkMeter.RecordRequestBodySize(stat?.StoreResult?.TransportRequestStats?.RequestBodySizeInBytes, dimension); CosmosNetworkMeter.RecordResponseBodySize(stat?.StoreResult?.TransportRequestStats?.ResponseBodySizeInBytes, dimension); CosmosNetworkMeter.RecordBackendLatency(stat?.StoreResult?.BackendRequestDurationInMs, dimension); + + dynamic parsedData = JsonConvert.DeserializeObject(stat?.StoreResult?.TransportRequestStats.ToString()); + + var requestTimeline = parsedData.requestTimeline; + // Extract specific events + foreach (var timelineEvent in requestTimeline) + { + string eventName = timelineEvent["event"]; + if (eventName == "ChannelAcquisitionStarted") + { + CosmosNetworkMeter.RecordChannelAquisitionLatency(Convert.ToDouble(timelineEvent.durationInMs), dimension); + } + + if (eventName == "Transit Time") + { + CosmosNetworkMeter.RecordTransitTimeLatency(Convert.ToDouble(timelineEvent.durationInMs), dimension); + } + + if (eventName == "Received") + { + CosmosNetworkMeter.RecordReceivedLatency(Convert.ToDouble(timelineEvent.durationInMs), dimension); + } + } }); summaryDiagnostics.HttpResponseStatistics.Value.ForEach(stat => @@ -121,6 +145,39 @@ SummaryDiagnostics summaryDiagnostics }); } + internal static void RecordChannelAquisitionLatency(double latency, KeyValuePair[] dimension) + { + if (!IsEnabled || CosmosNetworkMeter.ChannelAquisitionLatencyHistogram == null || + !CosmosNetworkMeter.ChannelAquisitionLatencyHistogram.Enabled) + { + return; + } + + CosmosNetworkMeter.ChannelAquisitionLatencyHistogram.Record(latency, dimension); + } + + internal static void RecordTransitTimeLatency(double latency, KeyValuePair[] dimension) + { + if (!IsEnabled || CosmosNetworkMeter.TransitLatencyHistogram == null || + !CosmosNetworkMeter.TransitLatencyHistogram.Enabled) + { + return; + } + + CosmosNetworkMeter.TransitLatencyHistogram.Record(latency, dimension); + } + + internal static void RecordReceivedLatency(double latency, KeyValuePair[] dimension) + { + if (!IsEnabled || CosmosNetworkMeter.ReceivedLatencyHistogram == null || + !CosmosNetworkMeter.ReceivedLatencyHistogram.Enabled) + { + return; + } + + CosmosNetworkMeter.ReceivedLatencyHistogram.Record(latency, dimension); + } + internal static void RecordRequestLatency(double latency, KeyValuePair[] dimension) { if (!IsEnabled || CosmosNetworkMeter.RequestLatencyHistogram == null || diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs index 9cbf959897..d2ab060927 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -30,9 +30,9 @@ public class OpenTelemetryMetricsTest : BaseCosmosClientHelper { "db.client.cosmosdb.request.body.size", MetricType.Histogram}, { "db.client.cosmosdb.response.body.size", MetricType.Histogram}, { "db.server.cosmosdb.request.duration", MetricType.Histogram}, - /* { "db.client.cosmosdb.request.channel_aquisition.duration", MetricType.Histogram}, + { "db.client.cosmosdb.request.channel_aquisition.duration", MetricType.Histogram}, { "db.client.cosmosdb.request.transit.duration", MetricType.Histogram}, - { "db.client.cosmosdb.request.received.duration", MetricType.Histogram}*/ + { "db.client.cosmosdb.request.received.duration", MetricType.Histogram} }; private MeterProvider meterProvider; From 1280e1393d6d1398f5d7be3ffc147eb1274d48c0 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Thu, 21 Nov 2024 07:18:17 +0530 Subject: [PATCH 44/49] updated sln --- Microsoft.Azure.Cosmos.sln | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/Microsoft.Azure.Cosmos.sln b/Microsoft.Azure.Cosmos.sln index a9e73c529c..b1d77052bf 100644 --- a/Microsoft.Azure.Cosmos.sln +++ b/Microsoft.Azure.Cosmos.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.11.35431.28 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29123.88 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos", "Microsoft.Azure.Cosmos\src\Microsoft.Azure.Cosmos.csproj", "{36F6F6A8-CEC8-4261-9948-903495BC3C25}" EndProject @@ -34,8 +34,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Encr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FaultInjection", "Microsoft.Azure.Cosmos\FaultInjection\src\FaultInjection.csproj", "{021DDC27-02EF-42C4-9A9E-AA600833C2EE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FaultInjectionTests", "Microsoft.Azure.Cosmos\FaultInjection\tests\FaultInjectionTests.csproj", "{D744906A-1091-403F-B0B6-794DE045169A}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests", "Microsoft.Azure.Cosmos.Encryption.Custom\tests\Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests\Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests.csproj", "{CE4D6DA8-148D-4A98-943B-D8C2D532E1DC}" EndProject Global @@ -180,18 +178,6 @@ Global {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|Any CPU.Build.0 = Release|Any CPU {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|x64.ActiveCfg = Release|Any CPU {021DDC27-02EF-42C4-9A9E-AA600833C2EE}.Release|x64.Build.0 = Release|Any CPU - {D744906A-1091-403F-B0B6-794DE045169A}.Cover|Any CPU.ActiveCfg = Debug|Any CPU - {D744906A-1091-403F-B0B6-794DE045169A}.Cover|Any CPU.Build.0 = Debug|Any CPU - {D744906A-1091-403F-B0B6-794DE045169A}.Cover|x64.ActiveCfg = Debug|Any CPU - {D744906A-1091-403F-B0B6-794DE045169A}.Cover|x64.Build.0 = Debug|Any CPU - {D744906A-1091-403F-B0B6-794DE045169A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D744906A-1091-403F-B0B6-794DE045169A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D744906A-1091-403F-B0B6-794DE045169A}.Debug|x64.ActiveCfg = Debug|Any CPU - {D744906A-1091-403F-B0B6-794DE045169A}.Debug|x64.Build.0 = Debug|Any CPU - {D744906A-1091-403F-B0B6-794DE045169A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D744906A-1091-403F-B0B6-794DE045169A}.Release|Any CPU.Build.0 = Release|Any CPU - {D744906A-1091-403F-B0B6-794DE045169A}.Release|x64.ActiveCfg = Release|Any CPU - {D744906A-1091-403F-B0B6-794DE045169A}.Release|x64.Build.0 = Release|Any CPU {CE4D6DA8-148D-4A98-943B-D8C2D532E1DC}.Cover|Any CPU.ActiveCfg = Debug|Any CPU {CE4D6DA8-148D-4A98-943B-D8C2D532E1DC}.Cover|Any CPU.Build.0 = Debug|Any CPU {CE4D6DA8-148D-4A98-943B-D8C2D532E1DC}.Cover|x64.ActiveCfg = Debug|Any CPU From 675c49fb539fa9ff6a1e8e2cf1716ba7983d65e4 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Thu, 21 Nov 2024 08:27:18 +0530 Subject: [PATCH 45/49] refactor code --- Directory.Build.props | 2 +- Microsoft.Azure.Cosmos/src/DocumentClient.cs | 2 +- .../src/Resource/ClientContextCore.cs | 4 +- .../OpenTelemetry/CosmosDbNetworkMeter.cs | 156 ++++++++++++ .../OpenTelemetry/CosmosDbOperationMeter.cs | 1 - .../OpenTelemetry/CosmosNetworkMeter.cs | 232 ------------------ .../OpenTelemetry/TracesStabilityFactory.cs | 2 +- 7 files changed, 161 insertions(+), 238 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbNetworkMeter.cs delete mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs diff --git a/Directory.Build.props b/Directory.Build.props index 50e90feb06..6fd763199e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ 3.46.0 3.47.0 preview.0 - 3.37.1 + 3.37.2 2.0.4 2.1.0 preview4 diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index 9816cbac80..23dda28e43 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -959,7 +959,7 @@ internal virtual void Initialize(Uri serviceEndpoint, if (this.cosmosClientTelemetryOptions.IsClientMetricsEnabled) { CosmosDbOperationMeter.Initialize(); - CosmosNetworkMeter.Initialize(); + CosmosDbNetworkMeter.Initialize(); CosmosDbOperationMeter.AddInstanceCount(this.ServiceEndpoint); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index a6ea71cf2f..4127e070db 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -545,7 +545,7 @@ private async Task RunWithDiagnosticsHelperAsync( databaseName: databaseName, attributes: otelAttributes); - CosmosNetworkMeter.RecordTelemetry(getOperationName: getOperationName, + CosmosDbNetworkMeter.RecordTelemetry(getOperationName: getOperationName, accountName: this.client.Endpoint, containerName: containerName, databaseName: databaseName, @@ -591,7 +591,7 @@ private async Task RunWithDiagnosticsHelperAsync( databaseName: databaseName, ex: cosmosException); - CosmosNetworkMeter.RecordTelemetry(getOperationName: getOperationName, + CosmosDbNetworkMeter.RecordTelemetry(getOperationName: getOperationName, accountName: this.client.Endpoint, containerName: containerName, databaseName: databaseName, diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbNetworkMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbNetworkMeter.cs new file mode 100644 index 0000000000..c84040c344 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbNetworkMeter.cs @@ -0,0 +1,156 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Telemetry +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Metrics; + using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Cosmos.Tracing.TraceData; + + internal static class CosmosDbNetworkMeter + { + /// + /// Meter instance for capturing various metrics related to Cosmos DB operations. + /// + private static readonly Meter NetworkMeter = new Meter(CosmosDbClientMetrics.NetworkMetrics.MeterName, CosmosDbClientMetrics.NetworkMetrics.Version); + + /// + /// Populator Used for Dimension Attributes + /// + private static readonly IActivityAttributePopulator DimensionPopulator = TracesStabilityFactory.GetAttributePopulator(); + + private static Histogram RequestLatencyHistogram = null; + + private static Histogram RequestBodySizeHistogram = null; + + private static Histogram ResponseBodySizeHistogram = null; + + private static Histogram ChannelAquisitionLatencyHistogram = null; + + private static Histogram BackendLatencyHistogram = null; + + private static Histogram TransitLatencyHistogram = null; + + private static Histogram ReceivedLatencyHistogram = null; + + private static bool IsEnabled = false; + + /// + /// Initializes the histograms and counters for capturing Cosmos DB metrics. + /// + internal static void Initialize() + { + // If already initialized, do not initialize again + if (IsEnabled) + { + return; + } + + CosmosDbNetworkMeter.RequestLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.Latency, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, + description: CosmosDbClientMetrics.NetworkMetrics.Description.Latency); + + CosmosDbNetworkMeter.RequestBodySizeHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.RequestBodySize, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Bytes, + description: CosmosDbClientMetrics.NetworkMetrics.Description.RequestBodySize); + + CosmosDbNetworkMeter.ResponseBodySizeHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.ResponseBodySize, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Bytes, + description: CosmosDbClientMetrics.NetworkMetrics.Description.ResponseBodySize); + + CosmosDbNetworkMeter.ChannelAquisitionLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.ChannelAquisitionLatency, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, + description: CosmosDbClientMetrics.NetworkMetrics.Description.ChannelAquisitionLatency); + + CosmosDbNetworkMeter.BackendLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.BackendLatency, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, + description: CosmosDbClientMetrics.NetworkMetrics.Description.BackendLatency); + + CosmosDbNetworkMeter.TransitLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.TransitTimeLatency, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, + description: CosmosDbClientMetrics.NetworkMetrics.Description.TransitTimeLatency); + + CosmosDbNetworkMeter.ReceivedLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.ReceivedTimeLatency, + unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, + description: CosmosDbClientMetrics.NetworkMetrics.Description.ReceivedTimeLatency); + + IsEnabled = true; + } + + internal static void RecordTelemetry(Func getOperationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes = null, + CosmosException ex = null) + { + if (!IsEnabled) + { + return; + } + + SummaryDiagnostics summaryDiagnostics + = new SummaryDiagnostics(((CosmosTraceDiagnostics)(ex?.Diagnostics ?? attributes?.Diagnostics)).Value); + summaryDiagnostics.StoreResponseStatistics.Value.ForEach(stat => + { + if (stat?.StoreResult == null) + { + return; + } + + Func[]> dimensionsFunc = () => DimensionPopulator.PopulateNetworkMeterDimensions( + getOperationName(), accountName, containerName, databaseName, attributes, ex, tcpStats: stat); + + CosmosDbMeterUtil.RecordHistogramMetric(stat.RequestLatency.TotalSeconds, dimensionsFunc, RequestLatencyHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(stat?.StoreResult?.TransportRequestStats?.RequestBodySizeInBytes, dimensionsFunc, RequestBodySizeHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(stat?.StoreResult?.TransportRequestStats?.ResponseBodySizeInBytes, dimensionsFunc, ResponseBodySizeHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(stat?.StoreResult?.BackendRequestDurationInMs, dimensionsFunc, BackendLatencyHistogram, (value) => Convert.ToDouble(value) / 1000); + + DateTime? requestCreatedTime = stat?.StoreResult?.TransportRequestStats?.requestCreatedTime; + TimeSpan? channelAcquisitionStartedTime = stat?.StoreResult?.TransportRequestStats?.channelAcquisitionStartedTime; + TimeSpan? requestPipelinedTime = stat?.StoreResult?.TransportRequestStats?.requestPipelinedTime; + TimeSpan? transitTime = stat?.StoreResult?.TransportRequestStats?.requestSentTime; + TimeSpan? requestReceivedTime = stat?.StoreResult?.TransportRequestStats?.requestReceivedTime; + TimeSpan? requestFailedTime = stat?.StoreResult?.TransportRequestStats?.requestFailedTime; + TimeSpan? requestCompletedTime = stat?.StoreResult?.TransportRequestStats?.requestCompletedTime; + + TimeSpan? channelAquisitionLatency = null; + TimeSpan? caDiffTimeSpan = requestPipelinedTime ?? requestFailedTime; + if (caDiffTimeSpan.HasValue) + { + channelAquisitionLatency = caDiffTimeSpan.Value.Subtract(channelAcquisitionStartedTime.Value); + } + CosmosDbMeterUtil.RecordHistogramMetric(channelAquisitionLatency, dimensionsFunc, ChannelAquisitionLatencyHistogram); + + TimeSpan? transitLatency = null; + TimeSpan? rrDiffTimeSpan = requestReceivedTime ?? requestFailedTime; + if (rrDiffTimeSpan.HasValue) + { + transitLatency = rrDiffTimeSpan.Value.Subtract(transitTime.Value); + } + CosmosDbMeterUtil.RecordHistogramMetric(transitLatency, dimensionsFunc, TransitLatencyHistogram); + + TimeSpan? receivedLatency = null; + TimeSpan? rcDiffTimeSpan = requestCompletedTime ?? requestFailedTime; + if (rrDiffTimeSpan.HasValue) + { + receivedLatency = rcDiffTimeSpan.Value.Subtract(requestReceivedTime.Value); + } + CosmosDbMeterUtil.RecordHistogramMetric(receivedLatency, dimensionsFunc, ReceivedLatencyHistogram); + }); + + summaryDiagnostics.HttpResponseStatistics.Value.ForEach(stat => + { + Func[]> dimensionsFunc = () => DimensionPopulator.PopulateNetworkMeterDimensions( + getOperationName(), accountName, containerName, databaseName, attributes, ex, httpStats: stat); + + CosmosDbMeterUtil.RecordHistogramMetric(stat.Duration.TotalSeconds, dimensionsFunc, RequestLatencyHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(stat.HttpResponseMessage?.RequestMessage?.Content?.Headers?.ContentLength, dimensionsFunc, RequestBodySizeHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(stat.ResponseContentLength, dimensionsFunc, ResponseBodySizeHistogram); + }); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs index 9dfecbd9e2..1af71042e7 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs @@ -8,7 +8,6 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System.Collections.Generic; using System.Diagnostics.Metrics; using Microsoft.Azure.Cosmos.Core.Trace; - using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; /// /// CosmosOperationMeter is a utility class responsible for collecting and recording telemetry metrics related to Cosmos DB operations. diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs deleted file mode 100644 index e58ae934ce..0000000000 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosNetworkMeter.cs +++ /dev/null @@ -1,232 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Telemetry -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.Metrics; - using Microsoft.Azure.Cosmos.Diagnostics; - using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; - using Microsoft.Azure.Cosmos.Tracing.TraceData; - using Newtonsoft.Json; - - internal static class CosmosNetworkMeter - { - /// - /// Meter instance for capturing various metrics related to Cosmos DB operations. - /// - internal static Meter NetworkMeter = new Meter(CosmosDbClientMetrics.NetworkMetrics.MeterName, CosmosDbClientMetrics.NetworkMetrics.Version); - - internal static Histogram RequestLatencyHistogram = null; - - internal static Histogram RequestBodySizeHistogram = null; - - internal static Histogram ResponseBodySizeHistogram = null; - - internal static Histogram ChannelAquisitionLatencyHistogram = null; - - internal static Histogram BackendLatencyHistogram = null; - - internal static Histogram TransitLatencyHistogram = null; - - internal static Histogram ReceivedLatencyHistogram = null; - - private static bool IsEnabled = false; - - private static IActivityAttributePopulator activityAttributePopulator; - - /// - /// Initializes the histograms and counters for capturing Cosmos DB metrics. - /// - internal static void Initialize() - { - // If already initialized, do not initialize again - if (IsEnabled) - { - return; - } - - activityAttributePopulator = TracesStabilityFactory.GetAttributePopulator(); - - CosmosNetworkMeter.RequestLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.Latency, - unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, - description: CosmosDbClientMetrics.NetworkMetrics.Description.Latency); - - CosmosNetworkMeter.RequestBodySizeHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.RequestBodySize, - unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Bytes, - description: CosmosDbClientMetrics.NetworkMetrics.Description.RequestBodySize); - - CosmosNetworkMeter.ResponseBodySizeHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.ResponseBodySize, - unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Bytes, - description: CosmosDbClientMetrics.NetworkMetrics.Description.ResponseBodySize); - - CosmosNetworkMeter.ChannelAquisitionLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.ChannelAquisitionLatency, - unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, - description: CosmosDbClientMetrics.NetworkMetrics.Description.ChannelAquisitionLatency); - - CosmosNetworkMeter.BackendLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.BackendLatency, - unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, - description: CosmosDbClientMetrics.NetworkMetrics.Description.BackendLatency); - - CosmosNetworkMeter.TransitLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.TransitTimeLatency, - unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, - description: CosmosDbClientMetrics.NetworkMetrics.Description.TransitTimeLatency); - - CosmosNetworkMeter.ReceivedLatencyHistogram ??= NetworkMeter.CreateHistogram(name: CosmosDbClientMetrics.NetworkMetrics.Name.ReceivedTimeLatency, - unit: CosmosDbClientMetrics.NetworkMetrics.Unit.Sec, - description: CosmosDbClientMetrics.NetworkMetrics.Description.ReceivedTimeLatency); - - IsEnabled = true; - } - - internal static void RecordTelemetry(Func getOperationName, - Uri accountName, - string containerName, - string databaseName, - OpenTelemetryAttributes attributes = null, - CosmosException ex = null) - { - if (!IsEnabled) - { - return; - } - - SummaryDiagnostics summaryDiagnostics - = new SummaryDiagnostics(((CosmosTraceDiagnostics)(ex?.Diagnostics ?? attributes?.Diagnostics)).Value); - summaryDiagnostics.StoreResponseStatistics.Value.ForEach(stat => - { - if (stat?.StoreResult == null) - { - return; - } - - KeyValuePair[] dimension = activityAttributePopulator.PopulateNetworkMeterDimensions( - getOperationName(), accountName, containerName, databaseName, attributes, ex, tcpStats: stat); - - CosmosNetworkMeter.RecordRequestLatency(stat.RequestLatency.TotalSeconds, dimension); - CosmosNetworkMeter.RecordRequestBodySize(stat?.StoreResult?.TransportRequestStats?.RequestBodySizeInBytes, dimension); - CosmosNetworkMeter.RecordResponseBodySize(stat?.StoreResult?.TransportRequestStats?.ResponseBodySizeInBytes, dimension); - CosmosNetworkMeter.RecordBackendLatency(stat?.StoreResult?.BackendRequestDurationInMs, dimension); - - dynamic parsedData = JsonConvert.DeserializeObject(stat?.StoreResult?.TransportRequestStats.ToString()); - - var requestTimeline = parsedData.requestTimeline; - // Extract specific events - foreach (var timelineEvent in requestTimeline) - { - string eventName = timelineEvent["event"]; - if (eventName == "ChannelAcquisitionStarted") - { - CosmosNetworkMeter.RecordChannelAquisitionLatency(Convert.ToDouble(timelineEvent.durationInMs), dimension); - } - - if (eventName == "Transit Time") - { - CosmosNetworkMeter.RecordTransitTimeLatency(Convert.ToDouble(timelineEvent.durationInMs), dimension); - } - - if (eventName == "Received") - { - CosmosNetworkMeter.RecordReceivedLatency(Convert.ToDouble(timelineEvent.durationInMs), dimension); - } - } - }); - - summaryDiagnostics.HttpResponseStatistics.Value.ForEach(stat => - { - KeyValuePair[] dimension = activityAttributePopulator.PopulateNetworkMeterDimensions( - getOperationName(), accountName, containerName, databaseName, attributes, ex, httpStats: stat); - - CosmosNetworkMeter.RecordRequestLatency(stat.Duration.TotalSeconds, dimension); - CosmosNetworkMeter.RecordRequestBodySize(stat.HttpResponseMessage?.RequestMessage?.Content?.Headers?.ContentLength, dimension); - CosmosNetworkMeter.RecordResponseBodySize(stat.ResponseContentLength, dimension); - }); - } - - internal static void RecordChannelAquisitionLatency(double latency, KeyValuePair[] dimension) - { - if (!IsEnabled || CosmosNetworkMeter.ChannelAquisitionLatencyHistogram == null || - !CosmosNetworkMeter.ChannelAquisitionLatencyHistogram.Enabled) - { - return; - } - - CosmosNetworkMeter.ChannelAquisitionLatencyHistogram.Record(latency, dimension); - } - - internal static void RecordTransitTimeLatency(double latency, KeyValuePair[] dimension) - { - if (!IsEnabled || CosmosNetworkMeter.TransitLatencyHistogram == null || - !CosmosNetworkMeter.TransitLatencyHistogram.Enabled) - { - return; - } - - CosmosNetworkMeter.TransitLatencyHistogram.Record(latency, dimension); - } - - internal static void RecordReceivedLatency(double latency, KeyValuePair[] dimension) - { - if (!IsEnabled || CosmosNetworkMeter.ReceivedLatencyHistogram == null || - !CosmosNetworkMeter.ReceivedLatencyHistogram.Enabled) - { - return; - } - - CosmosNetworkMeter.ReceivedLatencyHistogram.Record(latency, dimension); - } - - internal static void RecordRequestLatency(double latency, KeyValuePair[] dimension) - { - if (!IsEnabled || CosmosNetworkMeter.RequestLatencyHistogram == null || - !CosmosNetworkMeter.RequestLatencyHistogram.Enabled) - { - return; - } - - CosmosNetworkMeter.RequestLatencyHistogram.Record(latency, dimension); - } - - internal static void RecordRequestBodySize(long? bodySize, KeyValuePair[] dimension) - { - if (!IsEnabled || - CosmosNetworkMeter.RequestBodySizeHistogram == null || - !bodySize.HasValue || - !CosmosNetworkMeter.ResponseBodySizeHistogram.Enabled) - { - return; - } - - CosmosNetworkMeter.RequestBodySizeHistogram.Record(bodySize.Value, dimension); - } - - internal static void RecordResponseBodySize(long? bodySize, KeyValuePair[] dimension) - { - if (!IsEnabled || - CosmosNetworkMeter.ResponseBodySizeHistogram == null || - !bodySize.HasValue || - !CosmosNetworkMeter.ResponseBodySizeHistogram.Enabled) - { - return; - } - - CosmosNetworkMeter.ResponseBodySizeHistogram.Record(bodySize.Value, dimension); - } - - internal static void RecordBackendLatency(string latencyInMs, KeyValuePair[] dimension) - { - if (!IsEnabled || - CosmosNetworkMeter.BackendLatencyHistogram == null || - string.IsNullOrEmpty(latencyInMs) || - !CosmosNetworkMeter.BackendLatencyHistogram.Enabled) - { - return; - } - - CosmosNetworkMeter.BackendLatencyHistogram.Record(Convert.ToDouble(latencyInMs) / 1000, dimension); - } - - } -} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/TracesStabilityFactory.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/TracesStabilityFactory.cs index 70398310fd..82b1ee905f 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/TracesStabilityFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/TracesStabilityFactory.cs @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry +namespace Microsoft.Azure.Cosmos.Telemetry { using System; From b4e2045adb44d99d7d3a55834096cc0725b84666 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Thu, 21 Nov 2024 11:17:31 +0530 Subject: [PATCH 46/49] test fix --- .../OpenTelemetry/CosmosDbNetworkMeter.cs | 56 ++++++++----------- .../Metrics/CustomMetricExporter.cs | 50 ++++++++++++++++- .../Metrics/OpenTelemetryMetricsTest.cs | 17 +----- 3 files changed, 74 insertions(+), 49 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbNetworkMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbNetworkMeter.cs index c84040c344..5e1b3d417f 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbNetworkMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbNetworkMeter.cs @@ -108,38 +108,21 @@ SummaryDiagnostics summaryDiagnostics CosmosDbMeterUtil.RecordHistogramMetric(stat?.StoreResult?.TransportRequestStats?.RequestBodySizeInBytes, dimensionsFunc, RequestBodySizeHistogram); CosmosDbMeterUtil.RecordHistogramMetric(stat?.StoreResult?.TransportRequestStats?.ResponseBodySizeInBytes, dimensionsFunc, ResponseBodySizeHistogram); CosmosDbMeterUtil.RecordHistogramMetric(stat?.StoreResult?.BackendRequestDurationInMs, dimensionsFunc, BackendLatencyHistogram, (value) => Convert.ToDouble(value) / 1000); - - DateTime? requestCreatedTime = stat?.StoreResult?.TransportRequestStats?.requestCreatedTime; - TimeSpan? channelAcquisitionStartedTime = stat?.StoreResult?.TransportRequestStats?.channelAcquisitionStartedTime; - TimeSpan? requestPipelinedTime = stat?.StoreResult?.TransportRequestStats?.requestPipelinedTime; - TimeSpan? transitTime = stat?.StoreResult?.TransportRequestStats?.requestSentTime; - TimeSpan? requestReceivedTime = stat?.StoreResult?.TransportRequestStats?.requestReceivedTime; - TimeSpan? requestFailedTime = stat?.StoreResult?.TransportRequestStats?.requestFailedTime; - TimeSpan? requestCompletedTime = stat?.StoreResult?.TransportRequestStats?.requestCompletedTime; - - TimeSpan? channelAquisitionLatency = null; - TimeSpan? caDiffTimeSpan = requestPipelinedTime ?? requestFailedTime; - if (caDiffTimeSpan.HasValue) - { - channelAquisitionLatency = caDiffTimeSpan.Value.Subtract(channelAcquisitionStartedTime.Value); - } - CosmosDbMeterUtil.RecordHistogramMetric(channelAquisitionLatency, dimensionsFunc, ChannelAquisitionLatencyHistogram); - - TimeSpan? transitLatency = null; - TimeSpan? rrDiffTimeSpan = requestReceivedTime ?? requestFailedTime; - if (rrDiffTimeSpan.HasValue) - { - transitLatency = rrDiffTimeSpan.Value.Subtract(transitTime.Value); - } - CosmosDbMeterUtil.RecordHistogramMetric(transitLatency, dimensionsFunc, TransitLatencyHistogram); - - TimeSpan? receivedLatency = null; - TimeSpan? rcDiffTimeSpan = requestCompletedTime ?? requestFailedTime; - if (rrDiffTimeSpan.HasValue) - { - receivedLatency = rcDiffTimeSpan.Value.Subtract(requestReceivedTime.Value); - } - CosmosDbMeterUtil.RecordHistogramMetric(receivedLatency, dimensionsFunc, ReceivedLatencyHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(CalculateLatency( + stat?.StoreResult?.TransportRequestStats?.channelAcquisitionStartedTime, + stat?.StoreResult?.TransportRequestStats?.requestPipelinedTime, + stat?.StoreResult?.TransportRequestStats?.requestFailedTime), + dimensionsFunc, ChannelAquisitionLatencyHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(CalculateLatency( + stat?.StoreResult?.TransportRequestStats?.requestSentTime, + stat?.StoreResult?.TransportRequestStats?.requestReceivedTime, + stat?.StoreResult?.TransportRequestStats?.requestFailedTime), + dimensionsFunc, TransitLatencyHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(CalculateLatency( + stat?.StoreResult?.TransportRequestStats?.requestReceivedTime, + stat?.StoreResult?.TransportRequestStats?.requestCompletedTime, + stat?.StoreResult?.TransportRequestStats?.requestFailedTime), + dimensionsFunc, ReceivedLatencyHistogram); }); summaryDiagnostics.HttpResponseStatistics.Value.ForEach(stat => @@ -152,5 +135,14 @@ SummaryDiagnostics summaryDiagnostics CosmosDbMeterUtil.RecordHistogramMetric(stat.ResponseContentLength, dimensionsFunc, ResponseBodySizeHistogram); }); } + + private static double? CalculateLatency( + TimeSpan? start, + TimeSpan? end, + TimeSpan? failed) + { + TimeSpan? requestend = end ?? failed; + return start.HasValue && requestend.HasValue ? (requestend.Value - start.Value).TotalSeconds : (double?)null; + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs index 19024ae825..ecd6228f68 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs @@ -16,7 +16,26 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics public class CustomMetricExporter : BaseExporter { private readonly ManualResetEventSlim manualResetEventSlim = null; - private readonly static List expectedDimensions = new() + + internal static readonly Dictionary expectedOperationMetrics = new Dictionary() + { + { "db.client.operation.duration", MetricType.Histogram }, + { "db.client.response.row_count", MetricType.Histogram}, + { "db.client.cosmosdb.operation.request_charge", MetricType.Histogram }, + { "db.client.cosmosdb.active_instance.count", MetricType.LongSumNonMonotonic } + }; + + internal static readonly Dictionary expectedNetworkMetrics = new Dictionary() + { + { "db.client.cosmosdb.request.duration", MetricType.Histogram}, + { "db.client.cosmosdb.request.body.size", MetricType.Histogram}, + { "db.client.cosmosdb.response.body.size", MetricType.Histogram}, + { "db.server.cosmosdb.request.duration", MetricType.Histogram}, + { "db.client.cosmosdb.request.channel_aquisition.duration", MetricType.Histogram}, + { "db.client.cosmosdb.request.transit.duration", MetricType.Histogram}, + { "db.client.cosmosdb.request.received.duration", MetricType.Histogram} + }; + private readonly static List expectedOperationDimensions = new() { OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryAttributeKeys.ContainerName, @@ -31,6 +50,27 @@ public class CustomMetricExporter : BaseExporter OpenTelemetryAttributeKeys.ErrorType }; + private readonly static List expectedNetworkDimensions = new() + { + OpenTelemetryAttributeKeys.DbSystemName, + OpenTelemetryAttributeKeys.ContainerName, + OpenTelemetryAttributeKeys.DbName, + OpenTelemetryAttributeKeys.ServerAddress, + OpenTelemetryAttributeKeys.ServerPort, + OpenTelemetryAttributeKeys.DbOperation, + OpenTelemetryAttributeKeys.StatusCode, + OpenTelemetryAttributeKeys.SubStatusCode, + OpenTelemetryAttributeKeys.ConsistencyLevel, + OpenTelemetryAttributeKeys.NetworkProtocolName, + OpenTelemetryAttributeKeys.ServiceEndpointHost, + OpenTelemetryAttributeKeys.ServiceEndPointPort, + OpenTelemetryAttributeKeys.ServiceEndpointResourceId, + OpenTelemetryAttributeKeys.ServiceEndpointStatusCode, + OpenTelemetryAttributeKeys.ServiceEndpointSubStatusCode, + OpenTelemetryAttributeKeys.ServiceEndpointRegion, + OpenTelemetryAttributeKeys.ErrorType + }; + private readonly static List expectedDimensionsForInstanceCountMetrics = new() { OpenTelemetryAttributeKeys.DbSystemName, @@ -66,9 +106,13 @@ public override ExportResult Export(in Batch batch) { CollectionAssert.AreEquivalent(expectedDimensionsForInstanceCountMetrics, actualDimensions.ToList(), $"Dimensions are not matching for {metric.Name}"); } - else + else if (expectedOperationMetrics.ContainsKey(metric.Name)) + { + CollectionAssert.AreEquivalent(expectedOperationDimensions, actualDimensions.ToList(), $"Dimensions are not matching for {metric.Name}"); + } + else if (expectedNetworkMetrics.ContainsKey(metric.Name)) { - CollectionAssert.AreEquivalent(expectedDimensions, actualDimensions.ToList(), $"Dimensions are not matching for {metric.Name}"); + CollectionAssert.AreEquivalent(expectedNetworkDimensions, actualDimensions.ToList(), $"Dimensions are not matching for {metric.Name}"); } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs index d2ab060927..14cf1cd900 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/OpenTelemetryMetricsTest.cs @@ -20,20 +20,9 @@ public class OpenTelemetryMetricsTest : BaseCosmosClientHelper private const int AggregatingInterval = 500; private readonly ManualResetEventSlim manualResetEventSlim = new ManualResetEventSlim(false); - private static readonly Dictionary expectedMetrics = new Dictionary() - { - { "db.client.operation.duration", MetricType.Histogram }, - { "db.client.response.row_count", MetricType.Histogram}, - { "db.client.cosmosdb.operation.request_charge", MetricType.Histogram }, - { "db.client.cosmosdb.active_instance.count", MetricType.LongSumNonMonotonic }, - { "db.client.cosmosdb.request.duration", MetricType.Histogram}, - { "db.client.cosmosdb.request.body.size", MetricType.Histogram}, - { "db.client.cosmosdb.response.body.size", MetricType.Histogram}, - { "db.server.cosmosdb.request.duration", MetricType.Histogram}, - { "db.client.cosmosdb.request.channel_aquisition.duration", MetricType.Histogram}, - { "db.client.cosmosdb.request.transit.duration", MetricType.Histogram}, - { "db.client.cosmosdb.request.received.duration", MetricType.Histogram} - }; + private static readonly Dictionary expectedMetrics = CustomMetricExporter.expectedOperationMetrics + .Concat(CustomMetricExporter.expectedNetworkMetrics) + .ToDictionary(kv => kv.Key, kv => kv.Value); private MeterProvider meterProvider; [TestInitialize] From e4a7459e573e2a5929a328f69c0ae3859040837f Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Sun, 24 Nov 2024 06:52:02 +0530 Subject: [PATCH 47/49] cleanup --- Microsoft.Azure.Cosmos.sln | 2 ++ .../src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs | 1 - .../src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos.sln b/Microsoft.Azure.Cosmos.sln index b1d77052bf..caafa0b41a 100644 --- a/Microsoft.Azure.Cosmos.sln +++ b/Microsoft.Azure.Cosmos.sln @@ -34,6 +34,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Cosmos.Encr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FaultInjection", "Microsoft.Azure.Cosmos\FaultInjection\src\FaultInjection.csproj", "{021DDC27-02EF-42C4-9A9E-AA600833C2EE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FaultInjectionTests", "Microsoft.Azure.Cosmos\FaultInjection\tests\FaultInjectionTests.csproj", "{D744906A-1091-403F-B0B6-794DE045169A}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests", "Microsoft.Azure.Cosmos.Encryption.Custom\tests\Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests\Microsoft.Azure.Cosmos.Encryption.Custom.Performance.Tests.csproj", "{CE4D6DA8-148D-4A98-943B-D8C2D532E1DC}" EndProject Global diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs index 9dfecbd9e2..1af71042e7 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs @@ -8,7 +8,6 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System.Collections.Generic; using System.Diagnostics.Metrics; using Microsoft.Azure.Cosmos.Core.Trace; - using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; /// /// CosmosOperationMeter is a utility class responsible for collecting and recording telemetry metrics related to Cosmos DB operations. diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs index ff34ffccc6..edd7b3c350 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Cosmos.Telemetry { using System; using System.Collections.Generic; - using System.Xml.Linq; using global::Azure.Core; using Microsoft.Azure.Cosmos.Tracing.TraceData; From 164d61594703afad30fb790f48fc54dd29b3d5da Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Mon, 25 Nov 2024 15:25:59 +0530 Subject: [PATCH 48/49] compile fix --- Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs | 1 - .../Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index 0391b44942..6da7d745c2 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -550,7 +550,6 @@ private async Task RunWithDiagnosticsHelperAsync( containerName: containerName, databaseName: databaseName, attributes: otelAttributes); - attributes: response); } return result; } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs index 3cbc46de9e..32759464af 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs @@ -185,7 +185,7 @@ public KeyValuePair[] PopulateOperationMeterDimensions(string op new KeyValuePair(AppInsightClassicAttributeKeys.ServerAddress, accountName?.Host), new KeyValuePair(AppInsightClassicAttributeKeys.DbOperation, operationName), new KeyValuePair(AppInsightClassicAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), - new KeyValuePair(AppInsightClassicAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode) + new KeyValuePair(AppInsightClassicAttributeKeys.SubStatusCode, attributes?.SubStatusCode ?? ex?.SubStatusCode), new KeyValuePair(AppInsightClassicAttributeKeys.ServerAddress, accountName.Host), new KeyValuePair(AppInsightClassicAttributeKeys.DbOperation, operationName), new KeyValuePair(AppInsightClassicAttributeKeys.StatusCode, (int)(attributes?.StatusCode ?? ex?.StatusCode)), From e618478bdadc2fddf494263c3b604f456a387265 Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Mon, 25 Nov 2024 16:21:13 +0530 Subject: [PATCH 49/49] fix direct contract changes --- .../ClientRetryPolicyTests.cs | 3 ++- .../Microsoft.Azure.Cosmos.Tests/StoreReaderTest.cs | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ClientRetryPolicyTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ClientRetryPolicyTests.cs index 8df56bdc95..46e51348be 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ClientRetryPolicyTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ClientRetryPolicyTests.cs @@ -355,7 +355,8 @@ private async Task ValidateConnectTimeoutTriggersClientRetryPolicyAsync( useMultipleWriteLocations: useMultipleWriteLocations, detectClientConnectivityIssues: true, disableRetryWithRetryPolicy: false, - enableReplicaValidation: false); + enableReplicaValidation: false, + accountConfigurationProperties: null); // Reducing retry timeout to avoid long-running tests replicatedResourceClient.GoneAndRetryWithRetryTimeoutInSecondsOverride = 1; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StoreReaderTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StoreReaderTest.cs index 10847fae57..2701cd90d7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StoreReaderTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/StoreReaderTest.cs @@ -599,13 +599,13 @@ public void GlobalStrongConsistentWriteMockTest() { TransportClient mockTransportClient = this.GetMockTransportClientForGlobalStrongWrites(addressInformation, i, false, false, false); StoreReader storeReader = new StoreReader(mockTransportClient, addressSelector, new AddressEnumerator(), sessionContainer, false); - ConsistencyWriter consistencyWriter = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, false, false); + ConsistencyWriter consistencyWriter = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, false, false, null); StoreResponse response = consistencyWriter.WriteAsync(entity, new TimeoutHelper(TimeSpan.FromSeconds(30)), false).Result; Assert.AreEqual(100, response.LSN); //globalCommittedLsn never catches up in this case mockTransportClient = this.GetMockTransportClientForGlobalStrongWrites(addressInformation, i, true, false, false); - consistencyWriter = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, false, false); + consistencyWriter = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, false, false, null); try { response = consistencyWriter.WriteAsync(entity, new TimeoutHelper(TimeSpan.FromSeconds(30)), false).Result; @@ -616,17 +616,17 @@ public void GlobalStrongConsistentWriteMockTest() } mockTransportClient = this.GetMockTransportClientForGlobalStrongWrites(addressInformation, i, false, true, false); - consistencyWriter = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, false, false); + consistencyWriter = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, false, false, null); response = consistencyWriter.WriteAsync(entity, new TimeoutHelper(TimeSpan.FromSeconds(30)), false).Result; Assert.AreEqual(100, response.LSN); mockTransportClient = this.GetMockTransportClientForGlobalStrongWrites(addressInformation, i, false, true, true); - consistencyWriter = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, false, false); + consistencyWriter = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, false, false, null); response = consistencyWriter.WriteAsync(entity, new TimeoutHelper(TimeSpan.FromSeconds(30)), false).Result; Assert.AreEqual(100, response.LSN); mockTransportClient = this.GetMockTransportClientForGlobalStrongWrites(addressInformation, i, false, false, true); - consistencyWriter = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, false, false); + consistencyWriter = new ConsistencyWriter(addressSelector, sessionContainer, mockTransportClient, mockServiceConfigReader.Object, mockAuthorizationTokenProvider.Object, false, false, null); response = consistencyWriter.WriteAsync(entity, new TimeoutHelper(TimeSpan.FromSeconds(30)), false).Result; Assert.AreEqual(100, response.LSN); }