diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs index 127c6a0554..12732395c0 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs @@ -612,6 +612,27 @@ public static FeedIterator ToStreamIterator(this IQueryable query) return linqQuery.ToStreamIterator(); } + /// + /// This extension method returns the Index Metrics for a given Response object. The index utilization metrics is to be used for debugging purposes only. + /// This result is only available if QueryRequestOptions.PopulateIndexMetrics is set to true.Returns null if the index metrics is not available in the response. + /// + /// The query Response. + /// A string represents the Index Metrics. + /// + /// This example shows a common usage of this function. + /// + /// response = await this.Container.GetLinqQueryable(requestOptions: queryRequestOption).Select(item => item.Id).CountAsync(cancellationToken); + /// string indexMetrics = response.GetIndexMetrics(); + /// ]]> + /// + /// + public static string GetIndexMetrics(this Response response) + { + return ResponseMessage.DecodeIndexMetrics(response.Headers, isBase64Encoded: false)?.Value; + } + /// /// Returns the maximum value in a generic . /// diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs index 6676d096c9..136e0d3ba0 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs @@ -215,6 +215,18 @@ internal async Task> AggregateResultAsync(CancellationToken cancella { FeedResponse response = await localFeedIterator.ReadNextAsync(rootTrace, cancellationToken); headers.RequestCharge += response.RequestCharge; + + // IndexMetrics only show up on first round trip + if (response.Headers.IndexUtilizationText != null) + { + headers.IndexUtilizationText = response.Headers.IndexUtilizationText; + } + + if (response.Headers.ActivityId != null && headers.ActivityId == null) + { + headers.ActivityId = response.Headers.ActivityId; + } + result.AddRange(response); } } diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/Aggregate/AggregateQueryPipelineStage.cs b/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/Aggregate/AggregateQueryPipelineStage.cs index 13c245c4b3..1fe3fc4fa6 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/Aggregate/AggregateQueryPipelineStage.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/Aggregate/AggregateQueryPipelineStage.cs @@ -71,7 +71,8 @@ public override async ValueTask MoveNextAsync(ITrace trace, CancellationTo double requestCharge = 0; IReadOnlyDictionary cumulativeAdditionalHeaders = default; - + + string activityId = default; while (await this.inputStage.MoveNextAsync(trace, cancellationToken)) { TryCatch tryGetPageFromSource = this.inputStage.Current; @@ -82,6 +83,12 @@ public override async ValueTask MoveNextAsync(ITrace trace, CancellationTo } QueryPage sourcePage = tryGetPageFromSource.Result; + + if (activityId == default && sourcePage.ActivityId != default) + { + // We only take the first activityId. + activityId = sourcePage.ActivityId; + } requestCharge += sourcePage.RequestCharge; @@ -110,7 +117,7 @@ public override async ValueTask MoveNextAsync(ITrace trace, CancellationTo QueryPage queryPage = new QueryPage( documents: finalResult, requestCharge: requestCharge, - activityId: default, + activityId: activityId, cosmosQueryExecutionInfo: default, distributionPlanSpec: default, disallowContinuationTokenMessage: default, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemLinqTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/CosmosItemLinqTests.cs similarity index 96% rename from Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemLinqTests.cs rename to Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/CosmosItemLinqTests.cs index 9f1b8c2177..846739cf98 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemLinqTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/CosmosItemLinqTests.cs @@ -313,7 +313,7 @@ public async Task QueryableExtentionFunctionsTest() //Creating items for query. IList itemList = await ToDoActivity.CreateRandomItems(container: this.Container, pkCount: 10, perPKItemCount: 1, randomPartitionKey: true); - QueryRequestOptions queryRequestOptions = new QueryRequestOptions(); + QueryRequestOptions queryRequestOptions = new QueryRequestOptions() { PopulateIndexMetrics = true }; IOrderedQueryable linqQueryable = this.Container.GetItemLinqQueryable( requestOptions: queryRequestOptions); @@ -396,6 +396,34 @@ public async Task QueryableExtentionFunctionsTest() Assert.AreEqual(100, maxTaskNum); } + + [TestMethod] + public async Task GetIndexMetricsTest() + { + //Creating items for query. + IList itemList = await ToDoActivity.CreateRandomItems(container: this.Container, pkCount: 10, perPKItemCount: 1, randomPartitionKey: true); + + QueryRequestOptions queryRequestOptions = new QueryRequestOptions() { PopulateIndexMetrics = true }; + IOrderedQueryable linqQueryable = this.Container.GetItemLinqQueryable( + requestOptions: queryRequestOptions); + + // Response object with valid index metrics field + Response response = await linqQueryable.Select(item => item.taskNum).SumAsync(); + this.VerifyResponse(response, 420, queryRequestOptions); + + string indexMetrics = response.GetIndexMetrics(); + Assert.AreEqual( + @"{""UtilizedIndexes"":{""SingleIndexes"":[{""IndexSpec"":""/taskNum/?""}],""CompositeIndexes"":[]},""PotentialIndexes"":{""SingleIndexes"":[],""CompositeIndexes"":[]}}", + indexMetrics); + + // Response object with null index metrics field + response.Headers.IndexUtilizationText = null; + indexMetrics = response.GetIndexMetrics(); + Assert.AreEqual( + null, + indexMetrics); + } + [DataRow(false)] [DataRow(true)] [TestMethod] @@ -1012,6 +1040,9 @@ private void VerifyResponse( { Assert.AreEqual(expectedValue, response.Resource); Assert.IsTrue(response.RequestCharge > 0); + Assert.IsNotNull(response.Headers.IndexUtilizationText); + Assert.IsNotNull(response.Headers.ActivityId); + Assert.IsNotNull(response.ActivityId); } } } 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 80efed4ee2..7f177160fa 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 @@ -6437,6 +6437,13 @@ ], "MethodInfo": "System.Linq.IOrderedQueryable`1[TSource] OrderByRank[TSource,TKey](System.Linq.IQueryable`1[TSource], System.Linq.Expressions.Expression`1[System.Func`2[TSource,TKey]]);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" }, + "System.String GetIndexMetrics[T](Microsoft.Azure.Cosmos.Response`1[T])[System.Runtime.CompilerServices.ExtensionAttribute()]": { + "Type": "Method", + "Attributes": [ + "ExtensionAttribute" + ], + "MethodInfo": "System.String GetIndexMetrics[T](Microsoft.Azure.Cosmos.Response`1[T]);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" + }, "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.Response`1[System.Decimal]] AverageAsync(System.Linq.IQueryable`1[System.Decimal], System.Threading.CancellationToken)[System.Runtime.CompilerServices.ExtensionAttribute()]": { "Type": "Method", "Attributes": [