-
Notifications
You must be signed in to change notification settings - Fork 494
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Query: Adds PopulateIndexMetrics request options (#2612)
This PR adds the option for customer to enable PopulateIndexMetrics in query request options. This allows customer to obtain the index utilization of their query. The information is aggregated and automatically showed up in QueryMetrics once this request option is enabled. Please note that enabling this incurs overhead, so please set it only during debugging. It also adds a field called IndexMetrics to FeedResponse to expose the result to users.
- Loading branch information
Showing
13 changed files
with
330 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
Microsoft.Azure.Cosmos/src/Query/Core/Metrics/IndexMetricWriter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
//------------------------------------------------------------ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
//------------------------------------------------------------ | ||
namespace Microsoft.Azure.Cosmos.Query.Core.Metrics | ||
{ | ||
using System; | ||
using System.Globalization; | ||
using System.Linq; | ||
using System.Text; | ||
|
||
/// <summary> | ||
/// Base class for visiting and serializing a <see cref="QueryMetrics"/>. | ||
/// </summary> | ||
#if INTERNAL | ||
#pragma warning disable SA1600 | ||
#pragma warning disable CS1591 | ||
public | ||
#else | ||
internal | ||
#endif | ||
class IndexMetricWriter | ||
{ | ||
private const string IndexUtilizationInfo = "Index Utilization Information"; | ||
private const string UtilizedSingleIndexes = "Utilized Single Indexes"; | ||
private const string PotentialSingleIndexes = "Potential Single Indexes"; | ||
private const string UtilizedCompositeIndexes = "Utilized Composite Indexes"; | ||
private const string PotentialCompositeIndexes = "Potential Composite Indexes"; | ||
private const string IndexExpression = "Index Spec"; | ||
private const string IndexImpactScore = "Index Impact Score"; | ||
|
||
private const string IndexUtilizationSeparator = "---"; | ||
|
||
private readonly StringBuilder stringBuilder; | ||
|
||
public IndexMetricWriter(StringBuilder stringBuilder) | ||
{ | ||
this.stringBuilder = stringBuilder ?? throw new ArgumentNullException($"{nameof(stringBuilder)} must not be null."); | ||
} | ||
|
||
public void WriteIndexMetrics(IndexUtilizationInfo indexUtilizationInfo) | ||
{ | ||
// IndexUtilizationInfo | ||
this.WriteBeforeIndexUtilizationInfo(); | ||
|
||
this.WriteIndexUtilizationInfo(indexUtilizationInfo); | ||
|
||
this.WriteAfterIndexUtilizationInfo(); | ||
} | ||
|
||
#region IndexUtilizationInfo | ||
protected void WriteBeforeIndexUtilizationInfo() | ||
{ | ||
IndexMetricWriter.AppendNewlineToStringBuilder(this.stringBuilder); | ||
IndexMetricWriter.AppendHeaderToStringBuilder( | ||
this.stringBuilder, | ||
IndexMetricWriter.IndexUtilizationInfo, | ||
indentLevel: 0); | ||
} | ||
|
||
protected void WriteIndexUtilizationInfo(IndexUtilizationInfo indexUtilizationInfo) | ||
{ | ||
IndexMetricWriter.AppendHeaderToStringBuilder(this.stringBuilder, IndexMetricWriter.UtilizedSingleIndexes, indentLevel: 1); | ||
|
||
foreach (SingleIndexUtilizationEntity indexUtilizationEntity in indexUtilizationInfo.UtilizedSingleIndexes) | ||
{ | ||
WriteSingleIndexUtilizationEntity(indexUtilizationEntity); | ||
} | ||
|
||
IndexMetricWriter.AppendHeaderToStringBuilder(this.stringBuilder, IndexMetricWriter.PotentialSingleIndexes, indentLevel: 1); | ||
|
||
foreach (SingleIndexUtilizationEntity indexUtilizationEntity in indexUtilizationInfo.PotentialSingleIndexes) | ||
{ | ||
WriteSingleIndexUtilizationEntity(indexUtilizationEntity); | ||
} | ||
|
||
IndexMetricWriter.AppendHeaderToStringBuilder(this.stringBuilder, IndexMetricWriter.UtilizedCompositeIndexes, indentLevel: 1); | ||
|
||
foreach (CompositeIndexUtilizationEntity indexUtilizationEntity in indexUtilizationInfo.UtilizedCompositeIndexes) | ||
{ | ||
WriteCompositeIndexUtilizationEntity(indexUtilizationEntity); | ||
} | ||
|
||
IndexMetricWriter.AppendHeaderToStringBuilder(this.stringBuilder, IndexMetricWriter.PotentialCompositeIndexes, indentLevel: 1); | ||
|
||
foreach (CompositeIndexUtilizationEntity indexUtilizationEntity in indexUtilizationInfo.PotentialCompositeIndexes) | ||
{ | ||
WriteCompositeIndexUtilizationEntity(indexUtilizationEntity); | ||
} | ||
|
||
void WriteSingleIndexUtilizationEntity(SingleIndexUtilizationEntity indexUtilizationEntity) | ||
{ | ||
IndexMetricWriter.AppendHeaderToStringBuilder(this.stringBuilder, $"{IndexMetricWriter.IndexExpression}: {indexUtilizationEntity.IndexDocumentExpression}", indentLevel: 2); | ||
IndexMetricWriter.AppendHeaderToStringBuilder(this.stringBuilder, $"{IndexMetricWriter.IndexImpactScore}: {indexUtilizationEntity.IndexImpactScore}", indentLevel: 2); | ||
IndexMetricWriter.AppendHeaderToStringBuilder(this.stringBuilder, IndexMetricWriter.IndexUtilizationSeparator, indentLevel: 2); | ||
} | ||
|
||
void WriteCompositeIndexUtilizationEntity(CompositeIndexUtilizationEntity indexUtilizationEntity) | ||
{ | ||
IndexMetricWriter.AppendHeaderToStringBuilder(this.stringBuilder, $"{IndexMetricWriter.IndexExpression}: {String.Join(", ", indexUtilizationEntity.IndexDocumentExpressions)}", indentLevel: 2); | ||
IndexMetricWriter.AppendHeaderToStringBuilder(this.stringBuilder, $"{IndexMetricWriter.IndexImpactScore}: {indexUtilizationEntity.IndexImpactScore}", indentLevel: 2); | ||
IndexMetricWriter.AppendHeaderToStringBuilder(this.stringBuilder, IndexMetricWriter.IndexUtilizationSeparator, indentLevel: 2); | ||
} | ||
} | ||
|
||
protected void WriteAfterIndexUtilizationInfo() | ||
{ | ||
// Do nothing | ||
} | ||
#endregion | ||
|
||
#region Helpers | ||
private static void AppendHeaderToStringBuilder(StringBuilder stringBuilder, string headerTitle, int indentLevel) | ||
{ | ||
const string Indent = " "; | ||
const string FormatString = "{0}{1}"; | ||
|
||
stringBuilder.AppendFormat( | ||
CultureInfo.InvariantCulture, | ||
FormatString, | ||
string.Concat(Enumerable.Repeat(Indent, indentLevel)) + headerTitle, | ||
Environment.NewLine); | ||
} | ||
|
||
private static void AppendNewlineToStringBuilder(StringBuilder stringBuilder) | ||
{ | ||
IndexMetricWriter.AppendHeaderToStringBuilder( | ||
stringBuilder, | ||
string.Empty, | ||
indentLevel: 0); | ||
} | ||
#endregion | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
...Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/PopulateIndexMetricsTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
namespace Microsoft.Azure.Cosmos.EmulatorTests.Query | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Microsoft.Azure.Cosmos.CosmosElements; | ||
using Microsoft.Azure.Cosmos.Query.Core.Metrics; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Microsoft.Azure.Documents; | ||
using Newtonsoft.Json; | ||
using Microsoft.Azure.Cosmos.SDK.EmulatorTests.QueryOracle; | ||
|
||
[TestClass] | ||
public sealed class PopulateIndexMetricsTest : QueryTestsBase | ||
{ | ||
[TestMethod] | ||
public async Task TestIndexMetricsHeaderExistence() | ||
{ | ||
int seed = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; | ||
uint numberOfDocuments = 1; | ||
QueryOracleUtil util = new QueryOracle2(seed); | ||
IEnumerable<string> inputDocuments = util.GetDocuments(numberOfDocuments); | ||
|
||
await this.CreateIngestQueryDeleteAsync( | ||
ConnectionModes.Direct, | ||
CollectionTypes.SinglePartition | CollectionTypes.MultiPartition, | ||
inputDocuments, | ||
ImplementationAsync, | ||
"/id"); | ||
|
||
static async Task ImplementationAsync(Container container, IReadOnlyList<CosmosObject> documents) | ||
{ | ||
string query = string.Format("SELECT * FROM c WHERE c.name = 'ABC' AND c.age > 12"); | ||
|
||
// Build the expected string | ||
Assert.IsTrue(IndexUtilizationInfo.TryCreateFromDelimitedString("eyJVdGlsaXplZFNpbmdsZUluZGV4ZXMiOlt7IkZpbHRlckV4cHJlc3Npb24iOiIoUk9PVC5uYW1lID0gXCJBQkNcIikiLCJJbmRleFNwZWMiOiJcL25hbWVcLz8iLCJGaWx0ZXJQcmVjaXNlU2V0Ijp0cnVlLCJJbmRleFByZWNpc2VTZXQiOnRydWUsIkluZGV4SW1wYWN0U2NvcmUiOiJIaWdoIn0seyJGaWx0ZXJFeHByZXNzaW9uIjoiKFJPT1QuYWdlID4gMTIpIiwiSW5kZXhTcGVjIjoiXC9hZ2VcLz8iLCJGaWx0ZXJQcmVjaXNlU2V0Ijp0cnVlLCJJbmRleFByZWNpc2VTZXQiOnRydWUsIkluZGV4SW1wYWN0U2NvcmUiOiJIaWdoIn1dLCJQb3RlbnRpYWxTaW5nbGVJbmRleGVzIjpbXSwiVXRpbGl6ZWRDb21wb3NpdGVJbmRleGVzIjpbXSwiUG90ZW50aWFsQ29tcG9zaXRlSW5kZXhlcyI6W3siSW5kZXhTcGVjcyI6WyJcL25hbWUgQVNDIiwiXC9hZ2UgQVNDIl0sIkluZGV4UHJlY2lzZVNldCI6ZmFsc2UsIkluZGV4SW1wYWN0U2NvcmUiOiJIaWdoIn1dfQ==", | ||
out IndexUtilizationInfo parsedInfo)); | ||
StringBuilder stringBuilder = new StringBuilder(); | ||
IndexMetricWriter indexMetricWriter = new IndexMetricWriter(stringBuilder); | ||
indexMetricWriter.WriteIndexMetrics(parsedInfo); | ||
string expectedIndexMetricsString = stringBuilder.ToString(); | ||
|
||
// Test using GetItemQueryIterator | ||
QueryRequestOptions requestOptions = new QueryRequestOptions() { PopulateIndexMetrics = true }; | ||
FeedIterator<CosmosElement> itemQuery = container.GetItemQueryIterator<CosmosElement>( | ||
query, | ||
requestOptions: requestOptions); | ||
|
||
while (itemQuery.HasMoreResults) | ||
{ | ||
FeedResponse<CosmosElement> page = await itemQuery.ReadNextAsync(); | ||
Assert.IsTrue(page.Headers.AllKeys().Length > 1); | ||
Assert.IsNotNull(page.Headers.Get(HttpConstants.HttpHeaders.IndexUtilization), "Expected index utilization headers for query"); | ||
Assert.IsNotNull(page.IndexMetrics, "Expected index metrics response for query"); | ||
Assert.AreEqual(expectedIndexMetricsString, page.IndexMetrics); | ||
} | ||
|
||
// Test using Stream API | ||
using (FeedIterator feedIterator = container.GetItemQueryStreamIterator( | ||
queryText: query, | ||
continuationToken: null, | ||
requestOptions: new QueryRequestOptions | ||
{ | ||
PopulateIndexMetrics = true, | ||
})) | ||
{ | ||
using (ResponseMessage response = await feedIterator.ReadNextAsync()) | ||
{ | ||
Assert.IsNotNull(response.Content); | ||
Assert.IsTrue(response.Headers.AllKeys().Length > 1); | ||
Assert.IsNotNull(response.Headers.Get(HttpConstants.HttpHeaders.IndexUtilization), "Expected index utilization headers for query"); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.