From 46768655ffc39563ecd40a0280d97dfdfb6baa23 Mon Sep 17 00:00:00 2001 From: Asket Agarwal Date: Tue, 16 Mar 2021 15:06:19 +0530 Subject: [PATCH 1/6] Api for getting all regions contacted by a request --- .../src/Diagnostics/CosmosDiagnostics.cs | 7 ++ .../src/Diagnostics/CosmosTraceDiagnostics.cs | 27 ++++++++ .../ClientSideRequestStatisticsTraceDatum.cs | 7 +- .../Tracing/TraceWriter.TraceJsonWriter.cs | 18 ++++- .../Tracing/ContactedRegionsTests.cs | 67 +++++++++++++++++++ .../Tracing/TraceWriterBaselineTests.cs | 6 +- 6 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/ContactedRegionsTests.cs diff --git a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs index 8e8749374f..643fdaa0ac 100644 --- a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs +++ b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Cosmos { using System; + using System.Collections.Generic; /// /// Contains the cosmos diagnostic information for the current request to Azure Cosmos DB service. @@ -27,5 +28,11 @@ public virtual TimeSpan GetClientElapsedTime() /// /// The string field instance in the Azure CosmosDB database service. public abstract override string ToString(); + + /// + /// Gets the list of all regions that were contacted for a request + /// + /// The list of tuples containing the Region name and the URI + public abstract IReadOnlyList<(string, Uri)> GetContactedRegions(); } } diff --git a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs index cb2f5b6b2a..8e37f455e2 100644 --- a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs +++ b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs @@ -5,9 +5,12 @@ namespace Microsoft.Azure.Cosmos.Diagnostics { using System; + using System.Collections.Generic; + using System.Linq; using System.Text; using Microsoft.Azure.Cosmos.Json; using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.Azure.Cosmos.Tracing.TraceData; internal sealed class CosmosTraceDiagnostics : CosmosDiagnostics { @@ -40,6 +43,30 @@ public override TimeSpan GetClientElapsedTime() return this.Value.Duration; } + public override IReadOnlyList<(string, Uri)> GetContactedRegions() + { + HashSet<(string, Uri)> regionsContacted = new HashSet<(string, Uri)>(); + ITrace rootTrace = this.Value; + this.WalkTraceTree(rootTrace, regionsContacted); + return regionsContacted.ToList(); + } + + private void WalkTraceTree(ITrace currentTrace, HashSet<(string, Uri)> regionsContacted) + { + foreach (object datums in currentTrace.Data.Values) + { + if (datums is ClientSideRequestStatisticsTraceDatum clientSideRequestStatisticsTraceDatum) + { + regionsContacted.UnionWith(clientSideRequestStatisticsTraceDatum.RegionsContactedWithName); + } + } + + foreach (ITrace childTrace in currentTrace.Children) + { + this.WalkTraceTree(childTrace, regionsContacted); + } + } + private string ToJsonString() { ReadOnlyMemory utf8String = this.WriteTraceToJsonWriter(JsonSerializationFormat.Text); diff --git a/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientSideRequestStatisticsTraceDatum.cs b/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientSideRequestStatisticsTraceDatum.cs index 1515069006..2c95b4d2c3 100644 --- a/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientSideRequestStatisticsTraceDatum.cs +++ b/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientSideRequestStatisticsTraceDatum.cs @@ -29,7 +29,7 @@ public ClientSideRequestStatisticsTraceDatum(DateTime startTime) this.ContactedReplicas = new List(); this.StoreResponseStatisticsList = new List(); this.FailedReplicas = new HashSet(); - this.RegionsContacted = new HashSet(); + this.RegionsContactedWithName = new HashSet<(string, Uri)>(); this.clientSideRequestStatisticsCreateTime = Stopwatch.GetTimestamp(); } @@ -49,6 +49,8 @@ public ClientSideRequestStatisticsTraceDatum(DateTime startTime) public HashSet RegionsContacted { get; } + public HashSet<(string, Uri)> RegionsContactedWithName { get; } + public TimeSpan RequestLatency { get @@ -119,6 +121,7 @@ public void RecordResponse(DocumentServiceRequest request, StoreResult storeResu DateTime responseTime = DateTime.UtcNow; Uri locationEndpoint = request.RequestContext.LocationEndpointToRoute; + string regionName = request.RequestContext.RegionName; StoreResponseStatistics responseStatistics = new StoreResponseStatistics( startDateTime, responseTime, @@ -141,7 +144,7 @@ public void RecordResponse(DocumentServiceRequest request, StoreResult storeResu if (locationEndpoint != null) { - this.RegionsContacted.Add(locationEndpoint); + this.RegionsContactedWithName.Add((regionName, locationEndpoint)); } this.StoreResponseStatisticsList.Add(responseStatistics); diff --git a/Microsoft.Azure.Cosmos/src/Tracing/TraceWriter.TraceJsonWriter.cs b/Microsoft.Azure.Cosmos/src/Tracing/TraceWriter.TraceJsonWriter.cs index 144b5fee5b..6a06f354ae 100644 --- a/Microsoft.Azure.Cosmos/src/Tracing/TraceWriter.TraceJsonWriter.cs +++ b/Microsoft.Azure.Cosmos/src/Tracing/TraceWriter.TraceJsonWriter.cs @@ -193,7 +193,7 @@ public void Visit(ClientSideRequestStatisticsTraceDatum clientSideRequestStatist this.WriteJsonUriArrayWithDuplicatesCounted("ContactedReplicas", clientSideRequestStatisticsTraceDatum.ContactedReplicas); - this.WriteJsonUriArray("RegionsContacted", clientSideRequestStatisticsTraceDatum.RegionsContacted); + this.WriteRegionsContactedArray("RegionsContacted", clientSideRequestStatisticsTraceDatum.RegionsContactedWithName); this.WriteJsonUriArray("FailedReplicas", clientSideRequestStatisticsTraceDatum.FailedReplicas); this.jsonWriter.WriteFieldName("AddressResolutionStatistics"); @@ -321,6 +321,22 @@ private void WriteJsonUriArray(string propertyName, IEnumerable uris) this.jsonWriter.WriteArrayEnd(); } + private void WriteRegionsContactedArray(string propertyName, IEnumerable<(string, Uri)> uris) + { + this.jsonWriter.WriteFieldName(propertyName); + this.jsonWriter.WriteArrayStart(); + + if (uris != null) + { + foreach ((string _, Uri uri) contactedRegion in uris) + { + this.WriteStringValueOrNull(contactedRegion.uri?.ToString()); + } + } + + this.jsonWriter.WriteArrayEnd(); + } + /// /// Writes the list of URIs to JSON. /// Sequential duplicates are counted and written as a single object to prevent diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/ContactedRegionsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/ContactedRegionsTests.cs new file mode 100644 index 0000000000..34d8469829 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/ContactedRegionsTests.cs @@ -0,0 +1,67 @@ +namespace Microsoft.Azure.Cosmos.Tests.Tracing +{ + using System; + using System.Collections.Generic; + using System.Text; + using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.Azure.Cosmos.Tracing.TraceData; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ContactedRegionsTests + { + [TestMethod] + public void ContactedRegionsTest() + { + CosmosDiagnostics diagnostics = new CosmosTraceDiagnostics(this.CreateTestTraceTree()); + IReadOnlyList<(string, Uri)> regionsContacted = diagnostics.GetContactedRegions(); + Assert.IsNotNull(regionsContacted); + Assert.AreEqual(regionsContacted.Count, 4); + } + + private ITrace CreateTestTraceTree() + { + ITrace trace; + using (trace = Trace.GetRootTrace("Root Trace", TraceComponent.Unknown, TraceLevel.Info)) + { + using (ITrace firstLevel = trace.StartChild("First level Node", TraceComponent.Unknown, TraceLevel.Info)) + { + using (ITrace secondLevel = trace.StartChild("Second level Node", TraceComponent.Unknown, TraceLevel.Info)) + { + using (ITrace thirdLevel = trace.StartChild("Third level Node", TraceComponent.Unknown, TraceLevel.Info)) + { + thirdLevel.AddDatum("Client Side Request Stats", this.GetDatumObject(Regions.CentralUS)); + } + } + + using (ITrace secondLevel = trace.StartChild("Second level Node", TraceComponent.Unknown, TraceLevel.Info)) + { + secondLevel.AddDatum("Client Side Request Stats", this.GetDatumObject(Regions.CentralIndia, Regions.EastUS2)); + } + } + + using (ITrace firstLevel = trace.StartChild("First level Node", TraceComponent.Unknown, TraceLevel.Info)) + { + firstLevel.AddDatum("Client Side Request Stats", this.GetDatumObject(Regions.FranceCentral)); + } + } + + return trace; + } + + private TraceDatum GetDatumObject(string regionName1, string regionName2 = null) + { + ClientSideRequestStatisticsTraceDatum datum = new ClientSideRequestStatisticsTraceDatum(DateTime.UtcNow); + Uri uri1 = new Uri("http://someUri1.com"); + datum.RegionsContactedWithName.Add((regionName1, uri1)); + if (regionName2 != null) + { + Uri uri2 = new Uri("http://someUri2.com"); + datum.RegionsContactedWithName.Add((regionName2, uri2)); + } + + return datum; + } + } +} 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 dfda2d8ef7..c2a5f0285d 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 @@ -373,8 +373,8 @@ public void TraceData() datum.FailedReplicas.Add(uri1); datum.FailedReplicas.Add(uri2); - datum.RegionsContacted.Add(uri1); - datum.RegionsContacted.Add(uri2); + datum.RegionsContactedWithName.Add(("local", uri1)); + datum.RegionsContactedWithName.Add(("local", uri2)); datum.RequestEndTimeUtc = DateTime.MaxValue; @@ -426,7 +426,7 @@ public void TraceData() datum.FailedReplicas.Add(default); - datum.RegionsContacted.Add(default); + datum.RegionsContactedWithName.Add(default); datum.RequestEndTimeUtc = default; From 2d57cd35b866a3685b35f085f618fd268ab45d02 Mon Sep 17 00:00:00 2001 From: Asket Agarwal Date: Tue, 16 Mar 2021 15:11:16 +0530 Subject: [PATCH 2/6] Updating public contract --- .../TestBaseline/TraceWriterBaselineTests.TraceData.xml | 6 +++--- .../Contracts/DotNetSDKAPI.json | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) 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 a6c4cd071e..e2e2c85b9d 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 @@ -222,8 +222,8 @@ datum.FailedReplicas.Add(uri1); datum.FailedReplicas.Add(uri2); - datum.RegionsContacted.Add(uri1); - datum.RegionsContacted.Add(uri2); + datum.RegionsContactedWithName.Add(("local", uri1)); + datum.RegionsContactedWithName.Add(("local", uri2)); datum.RequestEndTimeUtc = DateTime.MaxValue; @@ -377,7 +377,7 @@ datum.FailedReplicas.Add(default); - datum.RegionsContacted.Add(default); + datum.RegionsContactedWithName.Add(default); datum.RequestEndTimeUtc = default; 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 a6544b2654..31507703ae 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 @@ -1936,6 +1936,11 @@ "Microsoft.Azure.Cosmos.CosmosDiagnostics;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { + "System.Collections.Generic.IReadOnlyList`1[System.ValueTuple`2[System.String,System.Uri]] GetContactedRegions()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.Collections.Generic.IReadOnlyList`1[System.ValueTuple`2[System.String,System.Uri]] GetContactedRegions();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.String ToString()": { "Type": "Method", "Attributes": [], From ce244239ebcaded773358c6e62b56dc161c0d69c Mon Sep 17 00:00:00 2001 From: Asket Agarwal Date: Tue, 16 Mar 2021 16:03:46 +0530 Subject: [PATCH 3/6] Updating tuple names --- Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs | 2 +- .../src/Diagnostics/CosmosTraceDiagnostics.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs index 643fdaa0ac..0d3d1b1f2f 100644 --- a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs +++ b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs @@ -33,6 +33,6 @@ public virtual TimeSpan GetClientElapsedTime() /// Gets the list of all regions that were contacted for a request /// /// The list of tuples containing the Region name and the URI - public abstract IReadOnlyList<(string, Uri)> GetContactedRegions(); + public abstract IReadOnlyList<(string RegionName, Uri Uri)> GetContactedRegions(); } } diff --git a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs index 8e37f455e2..ae218808bc 100644 --- a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs +++ b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs @@ -43,7 +43,7 @@ public override TimeSpan GetClientElapsedTime() return this.Value.Duration; } - public override IReadOnlyList<(string, Uri)> GetContactedRegions() + public override IReadOnlyList<(string RegionName, Uri Uri)> GetContactedRegions() { HashSet<(string, Uri)> regionsContacted = new HashSet<(string, Uri)>(); ITrace rootTrace = this.Value; From e5922d7770c1b547cd133f80706eeefe4467207b Mon Sep 17 00:00:00 2001 From: Asket Agarwal Date: Tue, 16 Mar 2021 19:06:39 +0530 Subject: [PATCH 4/6] Adding Emulator Test --- .../CosmosItemTests.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs index 57d491092d..bfa89efadb 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs @@ -2433,6 +2433,18 @@ public async Task CustomPropertiesItemRequestOptionsTest() Assert.AreEqual(HttpStatusCode.Created, responseAstype.StatusCode); } + [TestMethod] + public async Task RegionsContactedTest() + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(); + ItemResponse response = await this.Container.CreateItemAsync(item, new Cosmos.PartitionKey(item.pk)); + Assert.IsNotNull(response.Diagnostics); + IReadOnlyList<(string region, Uri uri)> regionsContacted = response.Diagnostics.GetContactedRegions(); + Assert.AreEqual(regionsContacted.Count, 1); + Assert.AreEqual(regionsContacted[0].region, Regions.SouthCentralUS); + Assert.IsNotNull(regionsContacted[0].uri); + } + #if PREVIEW [TestMethod] public async Task VerifyDocumentCrudWithMultiHashKind() From 5eee19394a55fcf3df16cd9068227b3608fe9fe5 Mon Sep 17 00:00:00 2001 From: Asket Agarwal Date: Tue, 16 Mar 2021 21:10:41 +0530 Subject: [PATCH 5/6] Renaming tuple --- Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs | 2 +- .../src/Diagnostics/CosmosTraceDiagnostics.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs index 0d3d1b1f2f..5734723c4d 100644 --- a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs +++ b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs @@ -33,6 +33,6 @@ public virtual TimeSpan GetClientElapsedTime() /// Gets the list of all regions that were contacted for a request /// /// The list of tuples containing the Region name and the URI - public abstract IReadOnlyList<(string RegionName, Uri Uri)> GetContactedRegions(); + public abstract IReadOnlyList<(string regionName, Uri uri)> GetContactedRegions(); } } diff --git a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs index ae218808bc..6f5d15a5cc 100644 --- a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs +++ b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs @@ -43,7 +43,7 @@ public override TimeSpan GetClientElapsedTime() return this.Value.Duration; } - public override IReadOnlyList<(string RegionName, Uri Uri)> GetContactedRegions() + public override IReadOnlyList<(string regionName, Uri uri)> GetContactedRegions() { HashSet<(string, Uri)> regionsContacted = new HashSet<(string, Uri)>(); ITrace rootTrace = this.Value; From 0ea080a887cc9f9b28932afd5ddfab0a76e86682 Mon Sep 17 00:00:00 2001 From: Asket Agarwal Date: Tue, 16 Mar 2021 22:19:23 +0530 Subject: [PATCH 6/6] optimizing tree search --- .../src/Diagnostics/CosmosTraceDiagnostics.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs index 6f5d15a5cc..ae7245d0eb 100644 --- a/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs +++ b/Microsoft.Azure.Cosmos/src/Diagnostics/CosmosTraceDiagnostics.cs @@ -47,23 +47,24 @@ public override TimeSpan GetClientElapsedTime() { HashSet<(string, Uri)> regionsContacted = new HashSet<(string, Uri)>(); ITrace rootTrace = this.Value; - this.WalkTraceTree(rootTrace, regionsContacted); + this.WalkTraceTreeForRegionsContated(rootTrace, regionsContacted); return regionsContacted.ToList(); } - private void WalkTraceTree(ITrace currentTrace, HashSet<(string, Uri)> regionsContacted) + private void WalkTraceTreeForRegionsContated(ITrace currentTrace, HashSet<(string, Uri)> regionsContacted) { foreach (object datums in currentTrace.Data.Values) { if (datums is ClientSideRequestStatisticsTraceDatum clientSideRequestStatisticsTraceDatum) { regionsContacted.UnionWith(clientSideRequestStatisticsTraceDatum.RegionsContactedWithName); + return; } } foreach (ITrace childTrace in currentTrace.Children) { - this.WalkTraceTree(childTrace, regionsContacted); + this.WalkTraceTreeForRegionsContated(childTrace, regionsContacted); } }