diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index 6d8129c834..7b4f134a56 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -955,7 +955,8 @@ internal virtual void Initialize(Uri serviceEndpoint, if (this.cosmosClientTelemetryOptions.IsClientMetricsEnabled) { CosmosDbOperationMeter.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 3653b45403..2523a1adee 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -13,7 +13,6 @@ 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; @@ -211,7 +210,7 @@ internal override Task OperationType operationType, RequestOptions requestOptions, Func> task, - Tuple> openTelemetry, + (string OperationName, Func GetAttributes)? openTelemetry, ResourceType? resourceType = null, TraceComponent traceComponent = TraceComponent.Transport, Tracing.TraceLevel traceLevel = Tracing.TraceLevel.Info) @@ -247,7 +246,7 @@ private async Task OperationHelperWithRootTraceAsync( OperationType operationType, RequestOptions requestOptions, Func> task, - Tuple> openTelemetry, + (string OperationName, Func GetAttributes)? openTelemetry, TraceComponent traceComponent, Tracing.TraceLevel traceLevel, ResourceType? resourceType) @@ -277,7 +276,7 @@ private Task OperationHelperWithRootTraceWithSynchronizationContextAsyn OperationType operationType, RequestOptions requestOptions, Func> task, - Tuple> openTelemetry, + (string OperationName, Func GetAttributes)? openTelemetry, TraceComponent traceComponent, Tracing.TraceLevel traceLevel, ResourceType? resourceType) @@ -494,26 +493,29 @@ private async Task RunWithDiagnosticsHelperAsync( OperationType operationType, ITrace trace, Func> task, - Tuple> openTelemetry, + (string OperationName, Func GetAttributes)? openTelemetry, RequestOptions requestOptions, ResourceType? resourceType = null) { + bool isOtelCompatibleOperation = openTelemetry != null && this.ShouldRecordTelemetry(); + Uri gatewayEndpoint = this.client.Endpoint; + Func getOperationName = () => { // If opentelemetry is not enabled then return null operation name, so that no activity is created. - if (openTelemetry == null) + if (!isOtelCompatibleOperation) { return null; } if (resourceType is not null && this.IsBulkOperationSupported(resourceType.Value, operationType)) { - return OpenTelemetryConstants.Operations.ExecuteBulkPrefix + openTelemetry.Item1; + return OpenTelemetryConstants.Operations.ExecuteBulkPrefix + openTelemetry?.OperationName; } - return openTelemetry.Item1; + return openTelemetry?.OperationName; }; - using (OpenTelemetryCoreRecorder recorder = + using (OpenTelemetryCoreRecorder recorder = isOtelCompatibleOperation ? OpenTelemetryRecorderFactory.CreateRecorder( getOperationName: getOperationName, containerName: containerName, @@ -521,73 +523,109 @@ private async Task RunWithDiagnosticsHelperAsync( operationType: operationType, requestOptions: requestOptions, trace: trace, - clientContext: this.isDisposed ? null : this)) + clientContext: this.isDisposed ? null : this) : default) using (new ActivityScope(Guid.NewGuid())) { try { 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 (isOtelCompatibleOperation) { // 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. - CosmosDbOperationMeter.RecordTelemetry(getOperationName: getOperationName, - accountName: this.client.Endpoint, - containerName: containerName, - databaseName: databaseName, - attributes: response); + OpenTelemetryAttributes otelAttributes = openTelemetry?.GetAttributes(result); + + // Records the telemetry attributes for Distributed Tracing (if enabled) and Metrics + recorder.Record(otelAttributes); + RecordMetrics(getOperationName, + this.client.Endpoint, + containerName, + databaseName, + attributes: otelAttributes); } - return result; - } - catch (OperationCanceledException oe) when (!(oe is CosmosOperationCanceledException)) - { - CosmosOperationCanceledException operationCancelledException = new CosmosOperationCanceledException(oe, trace); - recorder.MarkFailed(operationCancelledException); - - throw operationCancelledException; - } - catch (ObjectDisposedException objectDisposed) when (!(objectDisposed is CosmosObjectDisposedException)) - { - CosmosObjectDisposedException objectDisposedException = new CosmosObjectDisposedException( - objectDisposed, - this.client, - trace); - recorder.MarkFailed(objectDisposedException); - throw objectDisposedException; + return result; } - catch (NullReferenceException nullRefException) when (!(nullRefException is CosmosNullReferenceException)) + catch (Exception ex) when (TryTransformException(ex, trace, this.client, out Exception cosmosException)) { - CosmosNullReferenceException nullException = new CosmosNullReferenceException( - nullRefException, - trace); - recorder.MarkFailed(nullException); + if (isOtelCompatibleOperation) + { + recorder.MarkFailed(cosmosException); + RecordMetrics(getOperationName, + gatewayEndpoint, + containerName, + databaseName, + cosmosException: cosmosException); + } - throw nullException; + throw cosmosException; // Rethrow after recording telemetry } catch (Exception ex) { - recorder.MarkFailed(ex); - if (openTelemetry != null && ex is CosmosException cosmosException) - { - // Records telemetry data related to the exception. - CosmosDbOperationMeter.RecordTelemetry(getOperationName: getOperationName, - accountName: this.client.Endpoint, - containerName: containerName, - databaseName: databaseName, - ex: cosmosException); - } - - throw; + // Fallback handling for exceptions not covered by the 'when' filter + recorder.MarkFailed(ex); // Record the exception using the telemetry recorder + + // Optionally rethrow or handle the exception gracefully + throw; // Re-throwing to ensure the caller is aware of the unhandled exception } + + } + } + + // Checks if telemetry is enabled + private bool ShouldRecordTelemetry() + { + CosmosClientTelemetryOptions telemetryOptions = this.clientOptions.CosmosClientTelemetryOptions; + return !telemetryOptions.DisableDistributedTracing || telemetryOptions.IsClientMetricsEnabled; + } + + // Handles exceptions and records telemetry + private static bool TryTransformException( + Exception ex, + ITrace trace, + CosmosClient cosmosClient, + out Exception cosmosException) + { + cosmosException = ex switch + { + OperationCanceledException oe when oe is not CosmosOperationCanceledException => + new CosmosOperationCanceledException(oe, trace), + ObjectDisposedException od when od is not CosmosObjectDisposedException => + new CosmosObjectDisposedException(od, cosmosClient, trace), + NullReferenceException nr when nr is not CosmosNullReferenceException => + new CosmosNullReferenceException(nr, trace), + Exception ce when ce is CosmosException => ex, + _ => null + }; + + if (cosmosException is null) + { + return false; } + return true; + } + + private static void RecordMetrics(Func getOperationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes = null, + Exception cosmosException = null) + { + // Records telemetry data + CosmosDbOperationMeter.RecordTelemetry(getOperationName: getOperationName, + accountName: accountName, + containerName: containerName, + databaseName: databaseName, + attributes: attributes, + ex: cosmosException); + + CosmosDbNetworkMeter.RecordTelemetry(getOperationName: getOperationName, + accountName: accountName, + containerName: containerName, + databaseName: databaseName, + attributes: attributes, + ex: cosmosException); } private async Task ProcessResourceOperationAsBulkStreamAsync( diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosClientContext.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosClientContext.cs index 5db29ee8a2..e4c2592cc4 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosClientContext.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosClientContext.cs @@ -65,7 +65,7 @@ internal abstract Task OperationHelperAsync( OperationType operationType, RequestOptions requestOptions, Func> task, - Tuple> openTelemetry = null, + (string OperationName, Func GetAttributes)? openTelemetry = null, ResourceType? resourceType = null, TraceComponent traceComponent = TraceComponent.Transport, TraceLevel traceLevel = TraceLevel.Info); diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/Models/NetworkMetricData.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Models/NetworkMetricData.cs new file mode 100644 index 0000000000..7e449655bc --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Models/NetworkMetricData.cs @@ -0,0 +1,47 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Telemetry.Models +{ + internal class NetworkMetricData + { + // Constructor + public NetworkMetricData( + double latency, + long? requestBodySize, + long? responseBodySize, + double backendLatency, + double? channelAcquisitionLatency, + double? transitTimeLatency, + double? receivedLatency) + { + this.Latency = latency; + this.RequestBodySize = requestBodySize; + this.ResponseBodySize = responseBodySize; + this.BackendLatency = backendLatency; + this.ChannelAcquisitionLatency = channelAcquisitionLatency; + this.TransitTimeLatency = transitTimeLatency; + this.ReceivedLatency = receivedLatency; + } + + // Constructor + public NetworkMetricData( + double latency, + long? requestBodySize, + long? responseBodySize) + { + this.Latency = latency; + this.RequestBodySize = requestBodySize; + this.ResponseBodySize = responseBodySize; + } + + public double Latency { get; } + public long? RequestBodySize { get; } + public long? ResponseBodySize { get; } + public double BackendLatency { get; } + public double? ChannelAcquisitionLatency { get; } + public double? TransitTimeLatency { get; } + public double? ReceivedLatency { get; } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/Models/OperationMetricData.cs b/Microsoft.Azure.Cosmos/src/Telemetry/Models/OperationMetricData.cs new file mode 100644 index 0000000000..6163cb8f8d --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/Models/OperationMetricData.cs @@ -0,0 +1,19 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Telemetry.Models +{ + internal class OperationMetricData + { + public OperationMetricData(string itemCount, double? requestCharge) + { + this.ItemCount = itemCount; + this.RequestCharge = requestCharge; + } + + public string ItemCount { get; } + + public double? RequestCharge { get; } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs index daf1b31cbe..04873d48a6 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 { @@ -142,7 +143,10 @@ public void PopulateAttributes(DiagnosticScope scope, Exception exception) } } - public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMode, string operationType, OpenTelemetryAttributes response) + public void PopulateAttributes(DiagnosticScope scope, + QueryTextMode? queryTextMode, + string operationType, + OpenTelemetryAttributes response) { scope.AddAttribute(AppInsightClassicAttributeKeys.OperationType, operationType); if (response != null) @@ -162,17 +166,50 @@ public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMo } } - public KeyValuePair[] PopulateOperationMeterDimensions(string operationName, string containerName, string databaseName, Uri accountName, OpenTelemetryAttributes attributes, CosmosException ex) + public KeyValuePair[] PopulateNetworkMeterDimensions(string operationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes, + Exception 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.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.StatusCode, CosmosDbMeterUtil.GetStatusCode(attributes, ex)), + new KeyValuePair(AppInsightClassicAttributeKeys.SubStatusCode, CosmosDbMeterUtil.GetSubStatusCode(attributes, ex)) + }; + } + + public KeyValuePair[] PopulateOperationMeterDimensions(string operationName, + string containerName, + string databaseName, + Uri accountName, + OpenTelemetryAttributes attributes, + Exception 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, CosmosDbMeterUtil.GetStatusCode(attributes, ex)), + new KeyValuePair(AppInsightClassicAttributeKeys.SubStatusCode, CosmosDbMeterUtil.GetSubStatusCode(attributes, ex)), + new KeyValuePair(AppInsightClassicAttributeKeys.Region, CosmosDbMeterUtil.GetRegions(attributes?.Diagnostics)) + }; + } + + public KeyValuePair[] PopulateInstanceCountDimensions(Uri accountEndpoint) + { + return new[] + { + new KeyValuePair(AppInsightClassicAttributeKeys.ServerAddress, accountEndpoint.Host) }; } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbClientMetrics.cs index f295022504..272eb52417 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.client.cosmosdb.request.service_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/CosmosDbMeterUtil.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbMeterUtil.cs index 60b79ac4b1..920f7f3d90 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbMeterUtil.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbMeterUtil.cs @@ -7,7 +7,12 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System; using System.Collections.Generic; using System.Diagnostics.Metrics; + using System.Linq; + using System.Net.Http; using Microsoft.Azure.Cosmos.Core.Trace; + using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Cosmos.Telemetry.Models; + using static Microsoft.Azure.Cosmos.Tracing.TraceData.ClientSideRequestStatisticsTraceDatum; internal static class CosmosDbMeterUtil { @@ -30,9 +35,164 @@ internal static void RecordHistogramMetric( } catch (Exception ex) { - DefaultTrace.TraceWarning($"Failed to record metric. {ex.StackTrace}"); + DefaultTrace.TraceWarning($"Failed to record metric. {ex}"); } } + internal 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; + } + + internal static bool TryGetDiagnostics(OpenTelemetryAttributes attributes, + Exception ex, + out CosmosTraceDiagnostics traces) + { + traces = null; + + // Retrieve diagnostics from the exception if applicable + CosmosDiagnostics diagnostics = ex switch + { + CosmosOperationCanceledException cancelEx => cancelEx.Diagnostics, + CosmosObjectDisposedException disposedEx => disposedEx.Diagnostics, + CosmosNullReferenceException nullRefEx => nullRefEx.Diagnostics, + CosmosException cosmosException => cosmosException.Diagnostics, + _ when attributes != null => attributes.Diagnostics, + _ => null + }; + + // Ensure diagnostics is not null and cast is valid + if (diagnostics != null && diagnostics is CosmosTraceDiagnostics traceDiagnostics) + { + traces = traceDiagnostics; + return true; + } + + return false; + } + + internal static bool TryOperationMetricsValues( + OpenTelemetryAttributes attributes, + Exception ex, + out OperationMetricData values) + { + // Attempt to cast the exception to CosmosException + CosmosException cosmosException = ex as CosmosException; + + // Retrieve item count and request charge, prioritizing attributes if available + string itemCount = attributes?.ItemCount ?? cosmosException?.Headers?.ItemCount; + double? requestCharge = attributes?.RequestCharge ?? cosmosException?.Headers?.RequestCharge; + + // If neither value is available, return false + if (itemCount == null && requestCharge == null) + { + values = null; + return false; + } + + // Create the OperationMetricValue instance + values = new OperationMetricData(itemCount, requestCharge); + + return true; + } + + internal static bool TryNetworkMetricsValues( + StoreResponseStatistics stats, + out NetworkMetricData values) + { + double latency = stats.RequestLatency.TotalSeconds; + long? requestBodySize = stats?.StoreResult?.TransportRequestStats?.RequestBodySizeInBytes; + long? responseBodySize = stats?.StoreResult?.TransportRequestStats?.ResponseBodySizeInBytes; + double backendLatency = Convert.ToDouble(stats?.StoreResult?.BackendRequestDurationInMs); + double? channelAcquisitionLatency = CosmosDbMeterUtil.CalculateLatency( + stats?.StoreResult?.TransportRequestStats?.channelAcquisitionStartedTime, + stats?.StoreResult?.TransportRequestStats?.requestPipelinedTime, + stats?.StoreResult?.TransportRequestStats?.requestFailedTime); + double? transitTimeLatency = CosmosDbMeterUtil.CalculateLatency( + stats?.StoreResult?.TransportRequestStats?.requestSentTime, + stats?.StoreResult?.TransportRequestStats?.requestReceivedTime, + stats?.StoreResult?.TransportRequestStats?.requestFailedTime); + double? receivedLatency = CosmosDbMeterUtil.CalculateLatency( + stats?.StoreResult?.TransportRequestStats?.requestReceivedTime, + stats?.StoreResult?.TransportRequestStats?.requestCompletedTime, + stats?.StoreResult?.TransportRequestStats?.requestFailedTime); + + values = new NetworkMetricData(latency, requestBodySize, responseBodySize, backendLatency, channelAcquisitionLatency, transitTimeLatency, receivedLatency); + + return true; + } + + internal static NetworkMetricData GetNetworkMetricsValues( + HttpResponseStatistics stats) + { + double latency = stats.Duration.TotalSeconds; + long? requestBodySize = GetPayloadSize(stats.HttpResponseMessage?.RequestMessage?.Content); + long? responseBodySize = stats.ResponseContentLength ?? GetPayloadSize(stats.HttpResponseMessage?.Content); + + return new NetworkMetricData(latency, requestBodySize, responseBodySize); + } + + private static long GetPayloadSize(HttpContent content) + { + if (content == null) + { + return 0; + } + + long contentLength = 0; + try + { + if (content.Headers != null && content.Headers.ContentLength != null) + { + contentLength = content.Headers.ContentLength.Value; + } + } + catch (ObjectDisposedException) + { + // ignore and return content length as 0 + } + + return contentLength; + } + + internal 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(); + } + + internal static int? GetStatusCode(OpenTelemetryAttributes attributes, + Exception ex) + { + return ex switch + { + CosmosException cosmosException => (int)cosmosException.StatusCode, + _ when attributes != null => (int)attributes.StatusCode, + _ => null + }; + } + + internal static int? GetSubStatusCode(OpenTelemetryAttributes attributes, + Exception ex) + { + return ex switch + { + CosmosException cosmosException => (int)cosmosException.SubStatusCode, + _ when attributes != null => (int)attributes.SubStatusCode, + _ => null + }; + } + } } 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..71f6aabab8 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbNetworkMeter.cs @@ -0,0 +1,148 @@ +// ------------------------------------------------------------ +// 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.Diagnostics; + using Microsoft.Azure.Cosmos.Telemetry.Models; + 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 + /// + internal static 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, + Exception ex = null) + { + if (!IsEnabled || !CosmosDbMeterUtil.TryGetDiagnostics(attributes, ex, out CosmosTraceDiagnostics diagnostics)) + { + DefaultTrace.TraceWarning("Network Meter is not enabled or Diagnostics is not available."); + return; + } + + SummaryDiagnostics summaryDiagnostics = new SummaryDiagnostics(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.TryNetworkMetricsValues(stat, out NetworkMetricData metricData); + + CosmosDbMeterUtil.RecordHistogramMetric(metricData.Latency, dimensionsFunc, RequestLatencyHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(metricData.RequestBodySize, dimensionsFunc, RequestBodySizeHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(metricData.ResponseBodySize, dimensionsFunc, ResponseBodySizeHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(metricData.BackendLatency, dimensionsFunc, BackendLatencyHistogram, (value) => Convert.ToDouble(value) / 1000); + CosmosDbMeterUtil.RecordHistogramMetric(metricData.ChannelAcquisitionLatency, dimensionsFunc, ChannelAquisitionLatencyHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(metricData.TransitTimeLatency, dimensionsFunc, TransitLatencyHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(metricData.ReceivedLatency, dimensionsFunc, ReceivedLatencyHistogram); + }); + + summaryDiagnostics.HttpResponseStatistics.Value.ForEach(stat => + { + KeyValuePair[] dimensionsFunc() + { + return DimensionPopulator.PopulateNetworkMeterDimensions(getOperationName(), + accountName, + containerName, + databaseName, + attributes, + ex, + httpStats: stat); + } + + NetworkMetricData metricData = CosmosDbMeterUtil.GetNetworkMetricsValues(stat); + + CosmosDbMeterUtil.RecordHistogramMetric(metricData.Latency, dimensionsFunc, RequestLatencyHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(metricData.RequestBodySize, dimensionsFunc, RequestBodySizeHistogram); + CosmosDbMeterUtil.RecordHistogramMetric(metricData.ResponseBodySize, dimensionsFunc, ResponseBodySizeHistogram); + }); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs index 9dfecbd9e2..f595d0c37a 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbOperationMeter.cs @@ -8,7 +8,8 @@ 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; + using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Cosmos.Telemetry.Models; /// /// CosmosOperationMeter is a utility class responsible for collecting and recording telemetry metrics related to Cosmos DB operations. @@ -24,7 +25,7 @@ internal static class CosmosDbOperationMeter /// /// Populator Used for Dimension Attributes /// - private static readonly IActivityAttributePopulator DimensionPopulator = TracesStabilityFactory.GetAttributePopulator(); + internal static IActivityAttributePopulator DimensionPopulator = TracesStabilityFactory.GetAttributePopulator(); /// /// Histogram to record request latency (in seconds) for Cosmos DB operations. @@ -90,7 +91,7 @@ internal static void RecordTelemetry( string containerName, string databaseName, OpenTelemetryAttributes attributes = null, - CosmosException ex = null) + Exception ex = null) { if (!IsEnabled) { @@ -103,14 +104,18 @@ internal static void RecordTelemetry( 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); + if (CosmosDbMeterUtil.TryOperationMetricsValues(attributes, ex, out OperationMetricData value)) + { + CosmosDbMeterUtil.RecordHistogramMetric(value.ItemCount, dimensionsFunc, ActualItemHistogram, Convert.ToInt32); + CosmosDbMeterUtil.RecordHistogramMetric(value.RequestCharge, dimensionsFunc, RequestUnitsHistogram); + } + + if (CosmosDbMeterUtil.TryGetDiagnostics(attributes, ex, out CosmosTraceDiagnostics diagnostics)) + { + CosmosDbMeterUtil.RecordHistogramMetric(value: diagnostics.GetClientElapsedTime(), + dimensionsFunc, RequestLatencyHistogram, + t => ((TimeSpan)t).TotalSeconds); + } } catch (Exception exception) { @@ -130,12 +135,7 @@ internal static void AdjustInstanceCount(Uri accountEndpoint, int adjustment) try { - KeyValuePair[] dimensions = new[] - { - new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountEndpoint.Host), - new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) - }; + KeyValuePair[] dimensions = DimensionPopulator.PopulateInstanceCountDimensions(accountEndpoint); ActiveInstanceCounter.Add(adjustment, dimensions); } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/DatabaseDupAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/DatabaseDupAttributeKeys.cs index 9cbb173ad8..6f6e5416e6 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 { @@ -15,12 +16,20 @@ internal class DatabaseDupAttributeKeys : IActivityAttributePopulator private readonly IActivityAttributePopulator otelPopulator; public DatabaseDupAttributeKeys() - { + { this.otelPopulator = new OpenTelemetryAttributeKeys(); this.appInsightPopulator = new AppInsightClassicAttributeKeys(); } - public void PopulateAttributes(DiagnosticScope scope, string operationName, string databaseName, string containerName, Uri accountName, string userAgent, string machineId, string clientId, string connectionMode) + public void PopulateAttributes(DiagnosticScope scope, + string operationName, + string databaseName, + string containerName, + Uri accountName, + string userAgent, + string machineId, + string clientId, + string connectionMode) { this.appInsightPopulator.PopulateAttributes(scope, operationName, databaseName, containerName, accountName, userAgent, machineId, clientId, connectionMode); this.otelPopulator.PopulateAttributes(scope, operationName, databaseName, containerName, accountName, userAgent, machineId, clientId, connectionMode); @@ -32,31 +41,60 @@ public void PopulateAttributes(DiagnosticScope scope, Exception exception) this.otelPopulator.PopulateAttributes(scope, exception); } - public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMode, string operationType, OpenTelemetryAttributes response) + public void PopulateAttributes(DiagnosticScope scope, + QueryTextMode? queryTextMode, + string operationType, + OpenTelemetryAttributes response) { this.appInsightPopulator.PopulateAttributes(scope, queryTextMode, operationType, response); this.otelPopulator.PopulateAttributes(scope, queryTextMode, operationType, response); } + public KeyValuePair[] PopulateInstanceCountDimensions(Uri accountName) + { + return this.MergeDimensions( + () => this.appInsightPopulator.PopulateInstanceCountDimensions(accountName), + () => this.otelPopulator.PopulateInstanceCountDimensions(accountName)); + } + + public KeyValuePair[] PopulateNetworkMeterDimensions(string operationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes, + Exception ex, + ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats = null, + ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats = null) + { + return this.MergeDimensions( + () => this.appInsightPopulator.PopulateNetworkMeterDimensions(operationName, accountName, containerName, databaseName, attributes, ex, tcpStats, httpStats), + () => this.otelPopulator.PopulateNetworkMeterDimensions(operationName, accountName, containerName, databaseName, attributes, ex, tcpStats, httpStats)); + } + 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); + Exception ex) + { + return this.MergeDimensions( + () => this.appInsightPopulator.PopulateOperationMeterDimensions(operationName, containerName, databaseName, accountName, attributes, ex), + () => this.otelPopulator.PopulateOperationMeterDimensions(operationName, containerName, databaseName, accountName, attributes, ex)); + } + + private KeyValuePair[] MergeDimensions( + Func>> appInsightDimensionsProvider, + Func>> otelDimensionsProvider) + { + KeyValuePair[] appInsightDimensions = appInsightDimensionsProvider().ToArray(); + KeyValuePair[] otelDimensions = otelDimensionsProvider().ToArray(); + + KeyValuePair[] dimensions = new KeyValuePair[appInsightDimensions.Length + otelDimensions.Length]; + + Array.Copy(appInsightDimensions, dimensions, appInsightDimensions.Length); + Array.Copy(otelDimensions, 0, dimensions, appInsightDimensions.Length, otelDimensions.Length); + return dimensions; } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs index f7ad05f36a..dff188fc53 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/IActivityAttributePopulator.cs @@ -6,8 +6,8 @@ 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; internal interface IActivityAttributePopulator { @@ -32,6 +32,18 @@ public KeyValuePair[] PopulateOperationMeterDimensions(string op string containerName, string databaseName, Uri accountName, - OpenTelemetryAttributes attributes, CosmosException ex); + OpenTelemetryAttributes attributes, + Exception ex); + + public KeyValuePair[] PopulateNetworkMeterDimensions(string operationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes, + Exception ex, + ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats = null, + ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats = null); + + public KeyValuePair[] PopulateInstanceCountDimensions(Uri accountName); } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs index e6b822ca90..b9f0778488 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. @@ -170,6 +170,20 @@ internal sealed class OpenTelemetryAttributeKeys : IActivityAttributePopulator /// public const string ExceptionStacktrace = "exception.stacktrace"; + 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 ServiceEndpointRoutingId = "db.cosmosdb.network.routing_id "; + /// /// Represents the type of error. /// @@ -210,7 +224,10 @@ public void PopulateAttributes(DiagnosticScope scope, Exception exception) } } - public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMode, string operationType, OpenTelemetryAttributes response) + public void PopulateAttributes(DiagnosticScope scope, + QueryTextMode? queryTextMode, + string operationType, + OpenTelemetryAttributes response) { if (response == null) { @@ -245,13 +262,53 @@ public void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMo { scope.AddAttribute( OpenTelemetryAttributeKeys.Region, - GetRegions(response.Diagnostics), (input) => string.Join(",", input)); + CosmosDbMeterUtil.GetRegions(response.Diagnostics), (input) => string.Join(",", input)); } - + + } + + public KeyValuePair[] PopulateNetworkMeterDimensions(string operationName, + Uri accountName, + string containerName, + string databaseName, + OpenTelemetryAttributes attributes, + Exception ex, + ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats = null, + ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats = null) + { + Uri replicaEndpoint = GetEndpoint(tcpStats, httpStats); + + int? operationLevelStatusCode = CosmosDbMeterUtil.GetStatusCode(attributes, ex); + int? operationLevelSubStatusCode = CosmosDbMeterUtil.GetSubStatusCode(attributes, 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, operationLevelStatusCode), + new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, operationLevelSubStatusCode), + new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, GetConsistencyLevel(attributes, ex)), + new KeyValuePair(OpenTelemetryAttributeKeys.NetworkProtocolName, replicaEndpoint.Scheme), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointHost, replicaEndpoint.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndPointPort, replicaEndpoint.Port), + new KeyValuePair(OpenTelemetryAttributeKeys.ServiceEndpointRoutingId, GetRoutingId(tcpStats, httpStats)), + 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) + string containerName, + string databaseName, + Uri accountName, + OpenTelemetryAttributes attributes, + Exception ex) { return new KeyValuePair[] { @@ -261,25 +318,104 @@ public KeyValuePair[] PopulateOperationMeterDimensions(string op 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, GetRegions(attributes?.Diagnostics)), + new KeyValuePair(OpenTelemetryAttributeKeys.StatusCode, CosmosDbMeterUtil.GetStatusCode(attributes, ex)), + new KeyValuePair(OpenTelemetryAttributeKeys.SubStatusCode, CosmosDbMeterUtil.GetSubStatusCode(attributes, ex)), + new KeyValuePair(OpenTelemetryAttributeKeys.ConsistencyLevel, GetConsistencyLevel(attributes, ex)), + new KeyValuePair(OpenTelemetryAttributeKeys.Region, CosmosDbMeterUtil.GetRegions(attributes?.Diagnostics)), new KeyValuePair(OpenTelemetryAttributeKeys.ErrorType, ex?.Message) }; } - private static string[] GetRegions(CosmosDiagnostics diagnostics) + private static int? GetStatusCode(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) { - if (diagnostics?.GetContactedRegions() is not IReadOnlyList<(string regionName, Uri uri)> contactedRegions) + return (int?)httpStats?.HttpResponseMessage?.StatusCode ?? (int?)tcpStats?.StoreResult?.StatusCode; + } + + private static string GetConsistencyLevel(OpenTelemetryAttributes attributes, + Exception ex) + { + return ex switch { - return null; + CosmosException cosmosException => cosmosException.Headers.ConsistencyLevel, + _ when attributes != null => attributes.ConsistencyLevel, + _ => null + }; + } + + private static Uri GetEndpoint(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) + { + return httpStats?.RequestUri ?? tcpStats?.StoreResult?.StorePhysicalAddress; + } + + /// + /// Direct Mode: partitions/{partitionId}/replicas/{replicaId} + /// Gateway Mode: + /// a) If Partition Key Range Id is available, then it is the value of x-ms-documentdb-partitionkeyrangeid header + /// b) otherwise, it is the path of the request URI + /// + private static string GetRoutingId(ClientSideRequestStatisticsTraceDatum.StoreResponseStatistics tcpStats, ClientSideRequestStatisticsTraceDatum.HttpResponseStatistics? httpStats) + { + if (tcpStats != null) + { + string path = tcpStats?.StoreResult?.StorePhysicalAddress?.AbsolutePath; + if (path == null) + { + return string.Empty; + } + + int startIndex = path.IndexOf("/partitions/"); + return startIndex >= 0 ? path.Substring(startIndex) : string.Empty; + } + + if (httpStats.HasValue && httpStats.Value.HttpResponseMessage != null && httpStats.Value.HttpResponseMessage.Headers != null) + { + if (httpStats.Value.HttpResponseMessage.Headers.TryGetValues("x-ms-documentdb-partitionkeyrangeid", out IEnumerable pkrangeid)) + { + return string.Join(",", pkrangeid); + } + else + { + return httpStats.Value.RequestUri?.AbsolutePath; + } + } + + return string.Empty; + } + + 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 contactedRegions - .Select(region => region.regionName) - .Distinct() - .ToArray(); + return subStatuscode ?? Convert.ToInt32(tcpStats?.StoreResult?.SubStatusCode); + } + + public KeyValuePair[] PopulateInstanceCountDimensions(Uri accountEndpoint) + { + return new[] + { + new KeyValuePair(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerAddress, accountEndpoint.Host), + new KeyValuePair(OpenTelemetryAttributeKeys.ServerPort, accountEndpoint.Port) + }; } } } 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; diff --git a/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientSideRequestStatisticsTraceDatum.cs b/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientSideRequestStatisticsTraceDatum.cs index 829899c4b1..9bd89c977a 100644 --- a/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientSideRequestStatisticsTraceDatum.cs +++ b/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientSideRequestStatisticsTraceDatum.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Cosmos.Tracing.TraceData { using System; using System.Collections.Generic; - using System.Diagnostics; using System.Net; using System.Net.Http; using System.Text; @@ -260,7 +259,8 @@ public void RecordResponse( request.ResourceType, request.OperationType, request.Headers[HttpConstants.HttpHeaders.SessionToken], - locationEndpoint); + locationEndpoint, + regionName); lock (this.storeResponseStatistics) { @@ -352,8 +352,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 +366,8 @@ public void RecordHttpResponse(HttpRequestMessage request, request.Method, resourceType, response, - exception: null)); + exception: null, + region: Convert.ToString(regionName))); } } @@ -380,8 +382,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 +397,8 @@ public void RecordHttpException(HttpRequestMessage request, request.Method, resourceType, responseMessage: null, - exception: exception)); + exception: exception, + region: Convert.ToString(regionName))); } } @@ -460,7 +465,8 @@ public StoreResponseStatistics( ResourceType resourceType, OperationType operationType, string requestSessionToken, - Uri locationEndpoint) + Uri locationEndpoint, + string region) { this.RequestStartTime = requestStartTime; this.RequestResponseTime = requestResponseTime; @@ -470,8 +476,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 +500,8 @@ public HttpResponseStatistics( HttpMethod httpMethod, ResourceType resourceType, HttpResponseMessage responseMessage, - Exception exception) + Exception exception, + string region) { this.RequestStartTime = requestStartTime; this.Duration = requestEndTime - requestStartTime; @@ -501,7 +510,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 +523,7 @@ public HttpResponseStatistics( } } + public string Region { get; } public DateTime RequestStartTime { get; } public TimeSpan Duration { get; } public HttpResponseMessage HttpResponseMessage { get; } @@ -521,6 +532,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/CustomMetricExporter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Metrics/CustomMetricExporter.cs index 19024ae825..a6a47ce48e 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 @@ -13,35 +13,18 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics using System; using System.Linq; - public class CustomMetricExporter : BaseExporter + internal 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(); + internal static Dictionary ActualMetrics = new(); + internal static Dictionary> Dimensions = new(); public CustomMetricExporter(ManualResetEventSlim manualResetEventSlim) { + CustomMetricExporter.ActualMetrics = new(); + CustomMetricExporter.Dimensions = new(); + this.manualResetEventSlim = manualResetEventSlim; } @@ -62,14 +45,7 @@ public override ExportResult Export(in Batch batch) } } - 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}"); - } + Dimensions.TryAdd(metric.Name, actualDimensions.ToList()); } if (ActualMetrics.Count > 0) @@ -77,10 +53,8 @@ public override ExportResult Export(in Batch batch) this.manualResetEventSlim.Set(); } } - catch (Exception ex) + catch { - Console.WriteLine(ex); - this.manualResetEventSlim.Set(); return ExportResult.Failure; } 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..da4b4a6ec0 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 @@ -13,14 +13,18 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.Metrics using System.Threading; using System.Collections.Generic; using System.Linq; + using System.Diagnostics; + using Microsoft.Azure.Cosmos.Telemetry; [TestClass] public class OpenTelemetryMetricsTest : BaseCosmosClientHelper { + private const string StabilityEnvVariableName = "OTEL_SEMCONV_STABILITY_OPT_IN"; private const int AggregatingInterval = 500; private readonly ManualResetEventSlim manualResetEventSlim = new ManualResetEventSlim(false); - private static readonly Dictionary expectedMetrics = new Dictionary() + + private static readonly Dictionary expectedOperationMetrics = new() { { "db.client.operation.duration", MetricType.Histogram }, { "db.client.response.row_count", MetricType.Histogram}, @@ -28,42 +32,39 @@ public class OpenTelemetryMetricsTest : BaseCosmosClientHelper { "db.client.cosmosdb.active_instance.count", MetricType.LongSumNonMonotonic } }; + private static readonly Dictionary expectedNetworkMetrics = new() + { + { "db.client.cosmosdb.request.duration", MetricType.Histogram}, + { "db.client.cosmosdb.request.body.size", MetricType.Histogram}, + { "db.client.cosmosdb.response.body.size", MetricType.Histogram}, + { "db.client.cosmosdb.request.service_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 expectedGatewayModeNetworkMetrics = new() + { + { "db.client.cosmosdb.request.duration", MetricType.Histogram}, + { "db.client.cosmosdb.request.body.size", MetricType.Histogram}, + { "db.client.cosmosdb.response.body.size", MetricType.Histogram}, + }; + + private static readonly Dictionary expectedMetrics = expectedOperationMetrics + .Concat(expectedNetworkMetrics) + .ToDictionary(kv => kv.Key, kv => kv.Value); + private static readonly Dictionary expectedGatewayModeMetrics = expectedOperationMetrics + .Concat(expectedGatewayModeNetworkMetrics) + .ToDictionary(kv => kv.Key, kv => kv.Value); + private MeterProvider meterProvider; + + private Stopwatch timeoutTimer; + [TestInitialize] - public async Task Init() + public void TestInitialize() { - // Initialize OpenTelemetry MeterProvider - this.meterProvider = Sdk - .CreateMeterProviderBuilder() - .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Azure Cosmos DB Operation Level Metrics")) - .AddMeter(CosmosDbClientMetrics.OperationMetrics.MeterName) - .AddView( - instrumentName: CosmosDbClientMetrics.OperationMetrics.Name.RequestCharge, - metricStreamConfiguration: new ExplicitBucketHistogramConfiguration - { - Boundaries = CosmosDbClientMetrics.HistogramBuckets.RequestUnitBuckets - }) - .AddView( - instrumentName: CosmosDbClientMetrics.OperationMetrics.Name.Latency, - metricStreamConfiguration: new ExplicitBucketHistogramConfiguration - { - Boundaries = CosmosDbClientMetrics.HistogramBuckets.RequestLatencyBuckets - }) - .AddView( - instrumentName: CosmosDbClientMetrics.OperationMetrics.Name.RowCount, - metricStreamConfiguration: new ExplicitBucketHistogramConfiguration - { - Boundaries = CosmosDbClientMetrics.HistogramBuckets.RowCountBuckets - }) - .AddReader(new PeriodicExportingMetricReader( - exporter: new CustomMetricExporter(this.manualResetEventSlim), - exportIntervalMilliseconds: AggregatingInterval)) - .Build(); - - await base.TestInit((builder) => builder.WithClientTelemetryOptions(new CosmosClientTelemetryOptions() - { - IsClientMetricsEnabled = true - })); + Environment.SetEnvironmentVariable(StabilityEnvVariableName, null); } [TestCleanup] @@ -72,11 +73,58 @@ public async Task Cleanup() await base.TestCleanup(); this.meterProvider.Dispose(); + + Environment.SetEnvironmentVariable(StabilityEnvVariableName, null); } [TestMethod] - public async Task OperationLevelMetricsGenerationTest() + [DataRow(OpenTelemetryStablityModes.DatabaseDupe, ConnectionMode.Direct, DisplayName = "Direct Mode: Metrics and Dimensions when OTEL_SEMCONV_STABILITY_OPT_IN is set to 'database/dup'")] + [DataRow(OpenTelemetryStablityModes.Database, ConnectionMode.Direct, DisplayName = "Direct Mode: Metrics and Dimensions when OTEL_SEMCONV_STABILITY_OPT_IN is set to 'database'")] + [DataRow(OpenTelemetryStablityModes.ClassicAppInsights, ConnectionMode.Direct, DisplayName = "Direct Mode: Metrics and Dimensions when OTEL_SEMCONV_STABILITY_OPT_IN is set to 'appinsightssdk'")] + [DataRow(null, ConnectionMode.Direct, DisplayName = "Direct Mode: Metrics and Dimensions when OTEL_SEMCONV_STABILITY_OPT_IN is not set")] + [DataRow(OpenTelemetryStablityModes.DatabaseDupe, ConnectionMode.Gateway, DisplayName = "Gateway Mode: Metrics and Dimensions when OTEL_SEMCONV_STABILITY_OPT_IN is set to 'database/dup'")] + [DataRow(OpenTelemetryStablityModes.Database, ConnectionMode.Gateway, DisplayName = "Gateway Mode: Metrics and Dimensions when OTEL_SEMCONV_STABILITY_OPT_IN is set to 'database'")] + [DataRow(OpenTelemetryStablityModes.ClassicAppInsights, ConnectionMode.Gateway, DisplayName = "Gateway Mode: Metrics and Dimensions when OTEL_SEMCONV_STABILITY_OPT_IN is set to 'appinsightssdk'")] + [DataRow(null, ConnectionMode.Gateway, DisplayName = "Gateway Mode: Metrics and Dimensions when OTEL_SEMCONV_STABILITY_OPT_IN is not set")] + public async Task MetricsGenerationTest(string stabilityMode, ConnectionMode connectionMode) { + Environment.SetEnvironmentVariable(StabilityEnvVariableName, stabilityMode); + + // Refreshing Static variables to reflect the new stability mode + TracesStabilityFactory.RefreshStabilityMode(); + CosmosDbOperationMeter.DimensionPopulator = TracesStabilityFactory.GetAttributePopulator(); + CosmosDbNetworkMeter.DimensionPopulator = TracesStabilityFactory.GetAttributePopulator(); + + // Initialize OpenTelemetry MeterProvider + this.meterProvider = Sdk + .CreateMeterProviderBuilder() + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Azure Cosmos DB Metrics")) + .AddMeter(CosmosDbClientMetrics.OperationMetrics.MeterName, CosmosDbClientMetrics.NetworkMetrics.MeterName) + .AddReader(new PeriodicExportingMetricReader( + exporter: new CustomMetricExporter(this.manualResetEventSlim), + exportIntervalMilliseconds: AggregatingInterval)) + .Build(); + + if (connectionMode == ConnectionMode.Direct) + { + await base.TestInit((builder) => builder.WithClientTelemetryOptions(new CosmosClientTelemetryOptions() + { + IsClientMetricsEnabled = true + }) + .WithConnectionModeDirect()); + + } else if (connectionMode == ConnectionMode.Gateway) + { + await base.TestInit((builder) => builder.WithClientTelemetryOptions(new CosmosClientTelemetryOptions() + { + IsClientMetricsEnabled = true + }) + .WithConnectionModeGateway()); + } + + this.timeoutTimer = Stopwatch.StartNew(); + + // Cosmos Db operations Container container = await this.database.CreateContainerIfNotExistsAsync(Guid.NewGuid().ToString(), "/pk", throughput: 10000); for (int count = 0; count < 10; count++) { @@ -96,11 +144,130 @@ public async Task OperationLevelMetricsGenerationTest() } } + // Waiting for a notification from the exporter, which means metrics are emitted while (!this.manualResetEventSlim.IsSet) { + // Timing out, if exporter didn't send any signal is 3 seconds + if (this.timeoutTimer.Elapsed == TimeSpan.FromSeconds(3)) + { + this.manualResetEventSlim.Set(); + Assert.Fail("Timed Out: Metrics were not generated in 3 seconds."); + } } - CollectionAssert.AreEquivalent(expectedMetrics, CustomMetricExporter.ActualMetrics, string.Join(", ", CustomMetricExporter.ActualMetrics.Select(kv => $"{kv.Key}: {kv.Value}"))); + // Asserting Metrics + CollectionAssert.AreEquivalent(connectionMode == ConnectionMode.Direct? expectedMetrics : expectedGatewayModeMetrics, CustomMetricExporter.ActualMetrics, string.Join(", ", CustomMetricExporter.ActualMetrics.Select(kv => $"{kv.Key}: {kv.Value}"))); + + // Asserting Dimensions + foreach (KeyValuePair> dimension in CustomMetricExporter.Dimensions) + { + if (dimension.Key == CosmosDbClientMetrics.OperationMetrics.Name.ActiveInstances) + { + CollectionAssert.AreEquivalent(GetExpectedInstanceCountDimensions(), dimension.Value, $"Actual dimensions for {dimension.Key} are {string.Join(", ", dimension.Value.Select(kv => $"{kv}"))}"); + } + else if (expectedOperationMetrics.ContainsKey(dimension.Key)) + { + CollectionAssert.AreEquivalent(GetExpectedOperationDimensions(), dimension.Value, $"Actual dimensions for {dimension.Key} are {string.Join(", ", dimension.Value.Select(kv => $"{kv}"))}"); + } + else if (expectedNetworkMetrics.ContainsKey(dimension.Key)) + { + CollectionAssert.AreEquivalent(GetExpectedNetworkDimensions(), dimension.Value, $"Actual dimensions for {dimension.Key} are {string.Join(", ", dimension.Value.Select(kv => $"{kv}"))}"); + } + } + } + + private static List GetExpectedInstanceCountDimensions() + { + List otelBased = new() + { + OpenTelemetryAttributeKeys.DbSystemName, + OpenTelemetryAttributeKeys.ServerAddress, + OpenTelemetryAttributeKeys.ServerPort + }; + List appInsightBased = new() + { + AppInsightClassicAttributeKeys.ServerAddress + }; + + return GetBasedOnStabilityMode(otelBased, appInsightBased); + } + + private static List GetExpectedOperationDimensions() + { + List otelBased = new() + { + OpenTelemetryAttributeKeys.DbSystemName, + OpenTelemetryAttributeKeys.ContainerName, + OpenTelemetryAttributeKeys.DbName, + OpenTelemetryAttributeKeys.ServerAddress, + OpenTelemetryAttributeKeys.ServerPort, + OpenTelemetryAttributeKeys.DbOperation, + OpenTelemetryAttributeKeys.StatusCode, + OpenTelemetryAttributeKeys.SubStatusCode, + OpenTelemetryAttributeKeys.ConsistencyLevel, + OpenTelemetryAttributeKeys.Region, + OpenTelemetryAttributeKeys.ErrorType + }; + List appInsightBased = new() + { + AppInsightClassicAttributeKeys.ContainerName, + AppInsightClassicAttributeKeys.DbName, + AppInsightClassicAttributeKeys.ServerAddress, + AppInsightClassicAttributeKeys.DbOperation, + AppInsightClassicAttributeKeys.StatusCode, + AppInsightClassicAttributeKeys.SubStatusCode, + AppInsightClassicAttributeKeys.Region + }; + + return GetBasedOnStabilityMode(otelBased, appInsightBased); + } + + private static List GetExpectedNetworkDimensions() + { + List otelBased = 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.ServiceEndpointRoutingId, + OpenTelemetryAttributeKeys.ServiceEndpointStatusCode, + OpenTelemetryAttributeKeys.ServiceEndpointSubStatusCode, + OpenTelemetryAttributeKeys.ServiceEndpointRegion, + OpenTelemetryAttributeKeys.ErrorType + }; + List appInsightBased = new() + { + AppInsightClassicAttributeKeys.ContainerName, + AppInsightClassicAttributeKeys.DbName, + AppInsightClassicAttributeKeys.ServerAddress, + AppInsightClassicAttributeKeys.DbOperation, + AppInsightClassicAttributeKeys.StatusCode, + AppInsightClassicAttributeKeys.SubStatusCode + }; + + return GetBasedOnStabilityMode(otelBased, appInsightBased); + } + + private static List GetBasedOnStabilityMode(List otelBased, List appInsightBased) + { + string stabilityMode = Environment.GetEnvironmentVariable(StabilityEnvVariableName); + + return stabilityMode switch + { + OpenTelemetryStablityModes.Database or null => otelBased, + OpenTelemetryStablityModes.DatabaseDupe => otelBased.Union(appInsightBased).ToList(), + OpenTelemetryStablityModes.ClassicAppInsights => appInsightBased, + _ => otelBased + }; } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/TraceWriterBaselineTests.TraceData.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/TraceWriterBaselineTests.TraceData.xml index d2c33b99b2..13be13cdef 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/TraceWriterBaselineTests.TraceData.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/TraceWriterBaselineTests.TraceData.xml @@ -218,7 +218,8 @@ ResourceType.Document, OperationType.Query, "42", - uri1.Uri); + uri1.Uri, + "region1"); TraceWriterBaselineTests.GetPrivateField>(datum, "storeResponseStatistics").Add(storeResponseStatistics); rootTrace.AddDatum("Client Side Request Stats", datum); @@ -431,7 +432,8 @@ resourceType: default, operationType: default, requestSessionToken: default, - locationEndpoint: default); + locationEndpoint: default, + region: "region1"); TraceWriterBaselineTests.GetPrivateField>(datum, "storeResponseStatistics").Add(storeResponseStatistics); rootTrace.AddDatum("Client Side Request Stats Default", datum); @@ -565,7 +567,8 @@ HttpMethod.Get, ResourceType.Document, new HttpResponseMessage(System.Net.HttpStatusCode.OK) { ReasonPhrase = "Success" }, - exception: null); + exception: null, + region: "region1"); TraceWriterBaselineTests.GetPrivateField>(datum, "httpResponseStatistics").Add(httpResponseStatistics); @@ -576,7 +579,8 @@ HttpMethod.Get, ResourceType.Document, responseMessage: null, - exception: new OperationCanceledException()); + exception: new OperationCanceledException(), + region: "region1"); TraceWriterBaselineTests.GetPrivateField>(datum, "httpResponseStatistics").Add(httpResponseStatisticsException); rootTrace.AddDatum("Client Side Request Stats", datum); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncBatcherTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncBatcherTests.cs index 001a8b57d4..02819588ab 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncBatcherTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncBatcherTests.cs @@ -762,11 +762,11 @@ private static CosmosClientContext MockClientContext() It.IsAny(), It.IsAny(), It.IsAny>>(), - It.IsAny>>(), + It.IsAny<(string OperationName, Func GetAttributes)?>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns>, Tuple>, ResourceType?, TraceComponent, TraceLevel>( + .Returns>, (string OperationName, Func GetAttributes)?, ResourceType?, TraceComponent, TraceLevel>( (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, resourceType, comp, level) => func(NoOpTrace.Singleton)); return mockContext.Object; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncContainerExecutorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncContainerExecutorTests.cs index b509882701..93aa72519a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncContainerExecutorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncContainerExecutorTests.cs @@ -350,11 +350,11 @@ private Mock MockClientContext() It.IsAny(), It.IsAny(), It.IsAny>>(), - It.IsAny>>(), + It.IsAny<(string OperationName, Func GetAttributes)?>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns>, Tuple>, ResourceType?, TraceComponent, TraceLevel>( + .Returns>, (string OperationName, Func GetAttributes)?, ResourceType?, TraceComponent, TraceLevel>( (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, resourceType, comp, level) => func(NoOpTrace.Singleton)); mockContext.Setup(x => x.Client).Returns(MockCosmosUtil.CreateMockCosmosClient()); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncStreamerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncStreamerTests.cs index 7214e6a33d..606eb3d16f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncStreamerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncStreamerTests.cs @@ -206,11 +206,11 @@ private CosmosClientContext GetMockClientContext() It.IsAny(), It.IsAny(), It.IsAny>>(), - It.IsAny>>(), + It.IsAny<(string OperationName, Func GetAttributes)?>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns>, Tuple>, ResourceType?, TraceComponent, TraceLevel>( + .Returns>, (string OperationName, Func GetAttributes)?, ResourceType?, TraceComponent, TraceLevel>( (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, resourceType, comp, level) => func(NoOpTrace.Singleton)); return mockContext.Object; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/ChangeFeedEstimatorIteratorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/ChangeFeedEstimatorIteratorTests.cs index 71f1b604a8..cca7863af2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/ChangeFeedEstimatorIteratorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/ChangeFeedEstimatorIteratorTests.cs @@ -391,11 +391,11 @@ static FeedIteratorInternal feedCreator(DocumentServiceLease lease, string conti It.IsAny(), It.IsAny(), It.IsAny>>>(), - It.IsAny, OpenTelemetryAttributes>>>(), + It.IsAny<(string OperationName, Func, OpenTelemetryAttributes> GetAttributes)?>(), It.IsAny(), It.Is(tc => tc == TraceComponent.ChangeFeed), It.IsAny())) - .Returns>>, Tuple, OpenTelemetryAttributes>>, ResourceType?, TraceComponent, TraceLevel>( + .Returns>>, (string OperationName, Func, OpenTelemetryAttributes> GetAttributes)?, ResourceType?, TraceComponent, TraceLevel>( (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, resourceType, comp, level) => { using (ITrace trace = Trace.GetRootTrace(operationName, comp, level)) @@ -503,11 +503,11 @@ private static ContainerInternal GetMockedContainer() It.IsAny(), It.IsAny(), It.IsAny>>>(), - It.IsAny, OpenTelemetryAttributes>>>(), + It.IsAny<(string OperationName, Func, OpenTelemetryAttributes> GetAttributes)?>(), It.IsAny(), It.Is(tc => tc == TraceComponent.ChangeFeed), It.IsAny())) - .Returns>>, Tuple, OpenTelemetryAttributes>>, ResourceType?, TraceComponent, TraceLevel>( + .Returns>>, (string OperationName, Func, OpenTelemetryAttributes> GetAttributes)?, ResourceType?, TraceComponent, TraceLevel>( (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, resourceType, comp, level) => { using (ITrace trace = Trace.GetRootTrace(operationName, comp, level)) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCoreTests.cs index 6d1349f412..bf2aaea1e5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/ChangeFeedPartitionKeyResultSetIteratorCoreTests.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Cosmos.ChangeFeed.Tests using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed.LeaseManagement; + using Microsoft.Azure.Cosmos.Serialization.HybridRow; using Microsoft.Azure.Cosmos.Telemetry; using Microsoft.Azure.Cosmos.Tests; using Microsoft.Azure.Cosmos.Tracing; @@ -48,11 +49,11 @@ public async Task EtagPassesContinuation() It.IsAny(), It.IsAny(), It.IsAny>>(), - It.IsAny>>(), + It.IsAny<(string OperationName, Func GetAttributes)?>(), It.IsAny(), It.Is(tc => tc == TraceComponent.ChangeFeed), It.IsAny())) - .Returns>, Tuple>, ResourceType?, TraceComponent, TraceLevel>( + .Returns>, (string OperationName, Func GetAttributes)?, ResourceType?, TraceComponent, TraceLevel>( (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, resourceType, comp, level) => { using (ITrace trace = Trace.GetRootTrace(operationName, comp, level)) @@ -131,11 +132,11 @@ public async Task NextReadHasUpdatedContinuation() It.IsAny(), It.IsAny(), It.IsAny>>(), - It.IsAny>>(), + It.IsAny<(string OperationName, Func GetAttributes)?>(), It.IsAny(), It.Is(tc => tc == TraceComponent.ChangeFeed), It.IsAny())) - .Returns>, Tuple>, ResourceType?, TraceComponent, TraceLevel>( + .Returns>, (string OperationName, Func GetAttributes)?, ResourceType?, TraceComponent, TraceLevel>( (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, resourceType, comp, level) => { using (ITrace trace = Trace.GetRootTrace(operationName, comp, level)) @@ -204,11 +205,11 @@ public async Task ShouldSetFeedRangePartitionKeyRange() It.IsAny(), It.IsAny(), It.IsAny>>(), - It.IsAny>>(), + It.IsAny<(string OperationName, Func GetAttributes)?>(), It.IsAny(), It.Is(tc => tc == TraceComponent.ChangeFeed), It.IsAny())) - .Returns>, Tuple>, ResourceType?, TraceComponent, TraceLevel>( + .Returns>, (string OperationName, Func GetAttributes)?, ResourceType?, TraceComponent, TraceLevel>( (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, resourceType, comp, level) => { using (ITrace trace = Trace.GetRootTrace(operationName, comp, level)) @@ -288,11 +289,11 @@ public async Task ShouldUseFeedRangeEpk() It.IsAny(), It.IsAny(), It.IsAny>>(), - It.IsAny>>(), + It.IsAny<(string OperationName, Func GetAttributes)?>(), It.IsAny(), It.Is(tc => tc == TraceComponent.ChangeFeed), It.IsAny())) - .Returns>, Tuple>, Documents.ResourceType?, TraceComponent, TraceLevel>( + .Returns>, (string OperationName, Func GetAttributes)?, Documents.ResourceType?, TraceComponent, TraceLevel>( (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, resourceType, comp, level) => { using (ITrace trace = Trace.GetRootTrace(operationName, comp, level)) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/ContractEnforcement.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/ContractEnforcement.cs index 640dcbeca2..2fb714bee5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/ContractEnforcement.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/ContractEnforcement.cs @@ -258,7 +258,9 @@ public static string GetCurrentTelemetryContract(string dllName) List nonTelemetryModels = new() { "AzureVMMetadata", - "Compute" + "Compute", + "NetworkMetricData", + "OperationMetricData" }; TypeTree locally = new(typeof(object)); 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 b1876d0b5b..1132104047 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 @@ -3349,6 +3349,11 @@ "Attributes": [], "MethodInfo": null }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+OperationMetrics": { "Type": "NestedType", "Attributes": [], @@ -3382,6 +3387,136 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics;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.CosmosDbClientMetrics+NetworkMetrics+Description": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics+Name": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics+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.CosmosDbClientMetrics+NetworkMetrics+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 BackendLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String BackendLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String ChannelAquisitionLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ChannelAquisitionLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String ReceivedTimeLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ReceivedTimeLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestBodySize": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestBodySize;IsInitOnly:False;IsStatic:True;" + }, + "System.String ResponseBodySize": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ResponseBodySize;IsInitOnly:False;IsStatic:True;" + }, + "System.String TransitTimeLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String TransitTimeLatency;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics+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 BackendLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String BackendLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String ChannelAquisitionLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ChannelAquisitionLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String ReceivedTimeLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ReceivedTimeLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestBodySize": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestBodySize;IsInitOnly:False;IsStatic:True;" + }, + "System.String ResponseBodySize": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ResponseBodySize;IsInitOnly:False;IsStatic:True;" + }, + "System.String TransitTimeLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String TransitTimeLatency;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics+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 Bytes": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Bytes;IsInitOnly:False;IsStatic:True;" + }, + "System.String Sec": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Sec;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + } + } + }, "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": { @@ -3510,6 +3645,234 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics;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.CosmosDbClientMetrics+NetworkMetrics+Description": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics+Name": { + "Type": "NestedType", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics+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.CosmosDbClientMetrics+NetworkMetrics+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 BackendLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String BackendLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String ChannelAquisitionLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ChannelAquisitionLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String ReceivedTimeLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ReceivedTimeLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestBodySize": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestBodySize;IsInitOnly:False;IsStatic:True;" + }, + "System.String ResponseBodySize": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ResponseBodySize;IsInitOnly:False;IsStatic:True;" + }, + "System.String TransitTimeLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String TransitTimeLatency;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics+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 BackendLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String BackendLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String ChannelAquisitionLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ChannelAquisitionLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String ReceivedTimeLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ReceivedTimeLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestBodySize": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestBodySize;IsInitOnly:False;IsStatic:True;" + }, + "System.String ResponseBodySize": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ResponseBodySize;IsInitOnly:False;IsStatic:True;" + }, + "System.String TransitTimeLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String TransitTimeLatency;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics+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 Bytes": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Bytes;IsInitOnly:False;IsStatic:True;" + }, + "System.String Sec": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Sec;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + } + } + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics+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 BackendLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String BackendLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String ChannelAquisitionLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ChannelAquisitionLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String ReceivedTimeLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ReceivedTimeLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestBodySize": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestBodySize;IsInitOnly:False;IsStatic:True;" + }, + "System.String ResponseBodySize": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ResponseBodySize;IsInitOnly:False;IsStatic:True;" + }, + "System.String TransitTimeLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String TransitTimeLatency;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics+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 BackendLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String BackendLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String ChannelAquisitionLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ChannelAquisitionLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String Latency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Latency;IsInitOnly:False;IsStatic:True;" + }, + "System.String ReceivedTimeLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ReceivedTimeLatency;IsInitOnly:False;IsStatic:True;" + }, + "System.String RequestBodySize": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String RequestBodySize;IsInitOnly:False;IsStatic:True;" + }, + "System.String ResponseBodySize": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String ResponseBodySize;IsInitOnly:False;IsStatic:True;" + }, + "System.String TransitTimeLatency": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String TransitTimeLatency;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosDbClientMetrics+NetworkMetrics+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 Bytes": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Bytes;IsInitOnly:False;IsStatic:True;" + }, + "System.String Sec": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "System.String Sec;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, "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": { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs index a250b0924a..0f5cc0e13e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs @@ -835,11 +835,11 @@ public async Task TestMultipleNestedPartitionKeyValueFromStreamAsync() It.IsAny(), It.IsAny(), It.IsAny>>(), - It.IsAny>>(), + It.IsAny<(string OperationName, Func GetAttributes)?>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns>, Tuple>, ResourceType?, TraceComponent, TraceLevel>( + .Returns>, (string OperationName, Func GetAttributes)?, ResourceType?, TraceComponent, TraceLevel>( (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, resourceType, comp, level) => func(NoOpTrace.Singleton)); mockContext.Setup(x => x.OperationHelperAsync>( @@ -849,11 +849,11 @@ public async Task TestMultipleNestedPartitionKeyValueFromStreamAsync() It.IsAny(), It.IsAny(), It.IsAny>>>(), - It.IsAny, OpenTelemetryAttributes>>>(), + It.IsAny<(string OperationName, Func, OpenTelemetryAttributes> GetAttributes)?>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns>>, Tuple, OpenTelemetryAttributes>>, ResourceType?, TraceComponent, TraceLevel>( + .Returns>>, (string OperationName, Func, OpenTelemetryAttributes> GetAttributes)?, ResourceType?, TraceComponent, TraceLevel>( (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, resourceType, comp, level) => func(NoOpTrace.Singleton)); mockContext.Setup(x => x.ProcessResourceOperationStreamAsync( diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs index 32a34cba3e..251b199468 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs @@ -482,11 +482,11 @@ private CosmosClientContext MockClientContext() It.IsAny(), It.IsAny(), It.IsAny>>(), - It.IsAny>>(), + It.IsAny<(string OperationName, Func GetAttributes)?>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns>, Tuple>, ResourceType?, TraceComponent, TraceLevel>( + .Returns>, (string OperationName, Func GetAttributes)?, ResourceType?, TraceComponent, TraceLevel>( (operationName, containerName, databaseName, operationType, requestOptions, func, oTelFunc, resourceType, comp, level) => func(NoOpTrace.Singleton)); return mockContext.Object; 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 4ff2a7f77d..ecb747b8d7 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 @@ -23,7 +23,6 @@ namespace Microsoft.Azure.Cosmos.Tests.Tracing using Microsoft.Azure.Cosmos.Query.Core.Metrics; using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.Pipeline; - using Microsoft.Azure.Cosmos.Query.Core.Pipeline.Pagination; using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; using Microsoft.Azure.Cosmos.ReadFeed; using Microsoft.Azure.Cosmos.ReadFeed.Pagination; @@ -367,7 +366,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 +401,8 @@ public void TraceData() resourceType: default, operationType: default, requestSessionToken: default, - locationEndpoint: default); + locationEndpoint: default, + region: "region1"); TraceWriterBaselineTests.GetPrivateField>(datum, "storeResponseStatistics").Add(storeResponseStatistics); rootTrace.AddDatum("Client Side Request Stats Default", datum); @@ -426,7 +427,8 @@ public void TraceData() HttpMethod.Get, ResourceType.Document, new HttpResponseMessage(System.Net.HttpStatusCode.OK) { ReasonPhrase = "Success" }, - exception: null); + exception: null, + region: "region1"); TraceWriterBaselineTests.GetPrivateField>(datum, "httpResponseStatistics").Add(httpResponseStatistics); @@ -437,7 +439,8 @@ public void TraceData() HttpMethod.Get, ResourceType.Document, responseMessage: null, - exception: new OperationCanceledException()); + exception: new OperationCanceledException(), + region: "region1"); TraceWriterBaselineTests.GetPrivateField>(datum, "httpResponseStatistics").Add(httpResponseStatisticsException); rootTrace.AddDatum("Client Side Request Stats", datum); @@ -826,7 +829,7 @@ public override void SerializeAsXml(XmlWriter xmlWriter) .Join( Environment.NewLine, codeSnippet - .Select(x => x != string.Empty ? x.Substring(" ".Length) : string.Empty)) + .Select(x => x != string.Empty ? x[" ".Length..] : string.Empty)) + Environment.NewLine; } catch(Exception) @@ -939,7 +942,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 }