Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Diagnostics: Adds Api for getting all regions contacted by a request #2312

Merged
merged 11 commits into from
Mar 17, 2021
7 changes: 7 additions & 0 deletions Microsoft.Azure.Cosmos/src/Diagnostics/CosmosDiagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Collections.Generic;

/// <summary>
/// Contains the cosmos diagnostic information for the current request to Azure Cosmos DB service.
Expand All @@ -27,5 +28,11 @@ public virtual TimeSpan GetClientElapsedTime()
/// </summary>
/// <returns>The string field <see cref="CosmosDiagnostics"/> instance in the Azure CosmosDB database service.</returns>
public abstract override string ToString();

/// <summary>
/// Gets the list of all regions that were contacted for a request
/// </summary>
/// <returns>The list of tuples containing the Region name and the URI</returns>
public abstract IReadOnlyList<(string regionName, Uri uri)> GetContactedRegions();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -40,6 +43,30 @@ public override TimeSpan GetClientElapsedTime()
return this.Value.Duration;
}

public override IReadOnlyList<(string regionName, Uri 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)
asketagarwal marked this conversation as resolved.
Show resolved Hide resolved
{
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);
asketagarwal marked this conversation as resolved.
Show resolved Hide resolved
}
}

private string ToJsonString()
{
ReadOnlyMemory<byte> utf8String = this.WriteTraceToJsonWriter(JsonSerializationFormat.Text);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public ClientSideRequestStatisticsTraceDatum(DateTime startTime)
this.ContactedReplicas = new List<Uri>();
this.StoreResponseStatisticsList = new List<StoreResponseStatistics>();
this.FailedReplicas = new HashSet<Uri>();
this.RegionsContacted = new HashSet<Uri>();
this.RegionsContactedWithName = new HashSet<(string, Uri)>();
this.clientSideRequestStatisticsCreateTime = Stopwatch.GetTimestamp();
}

Expand All @@ -49,6 +49,8 @@ public ClientSideRequestStatisticsTraceDatum(DateTime startTime)

public HashSet<Uri> RegionsContacted { get; }

public HashSet<(string, Uri)> RegionsContactedWithName { get; }

public TimeSpan RequestLatency
{
get
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ public void Visit(ClientSideRequestStatisticsTraceDatum clientSideRequestStatist

this.WriteJsonUriArrayWithDuplicatesCounted("ContactedReplicas", clientSideRequestStatisticsTraceDatum.ContactedReplicas);

this.WriteJsonUriArray("RegionsContacted", clientSideRequestStatisticsTraceDatum.RegionsContacted);
this.WriteRegionsContactedArray("RegionsContacted", clientSideRequestStatisticsTraceDatum.RegionsContactedWithName);
j82w marked this conversation as resolved.
Show resolved Hide resolved
this.WriteJsonUriArray("FailedReplicas", clientSideRequestStatisticsTraceDatum.FailedReplicas);

this.jsonWriter.WriteFieldName("AddressResolutionStatistics");
Expand Down Expand Up @@ -321,6 +321,22 @@ private void WriteJsonUriArray(string propertyName, IEnumerable<Uri> 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());
asketagarwal marked this conversation as resolved.
Show resolved Hide resolved
}
}

this.jsonWriter.WriteArrayEnd();
}

/// <summary>
/// Writes the list of URIs to JSON.
/// Sequential duplicates are counted and written as a single object to prevent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2433,6 +2433,18 @@ public async Task CustomPropertiesItemRequestOptionsTest()
Assert.AreEqual(HttpStatusCode.Created, responseAstype.StatusCode);
}

[TestMethod]
public async Task RegionsContactedTest()
{
ToDoActivity item = ToDoActivity.CreateRandomToDoActivity();
ItemResponse<ToDoActivity> response = await this.Container.CreateItemAsync<ToDoActivity>(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);
j82w marked this conversation as resolved.
Show resolved Hide resolved
Assert.IsNotNull(regionsContacted[0].uri);
}

#if PREVIEW
[TestMethod]
public async Task VerifyDocumentCrudWithMultiHashKind()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -377,7 +377,7 @@

datum.FailedReplicas.Add(default);

datum.RegionsContacted.Add(default);
datum.RegionsContactedWithName.Add(default);

datum.RequestEndTimeUtc = default;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
Expand Down
Original file line number Diff line number Diff line change
@@ -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
j82w marked this conversation as resolved.
Show resolved Hide resolved
{
[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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -426,7 +426,7 @@ public void TraceData()

datum.FailedReplicas.Add(default);

datum.RegionsContacted.Add(default);
datum.RegionsContactedWithName.Add(default);

datum.RequestEndTimeUtc = default;

Expand Down