-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Activities for Http Connections, Dns, Sockets and SslStream (#103922)
Final design: #103922 (comment)
- Loading branch information
1 parent
ac0eeb0
commit ee5770d
Showing
36 changed files
with
1,458 additions
and
204 deletions.
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
src/libraries/Common/tests/System/Net/ActivityRecorder.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,136 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using System.Text; | ||
using Xunit; | ||
|
||
namespace System.Net.Test.Common | ||
{ | ||
internal class ActivityRecorder : IDisposable | ||
{ | ||
private string _activitySourceName; | ||
private string _activityName; | ||
|
||
private readonly ActivityListener _listener; | ||
private List<Activity> _finishedActivities = new(); | ||
|
||
public Predicate<Activity> Filter { get; set; } = _ => true; | ||
public bool VerifyParent { get; set; } = true; | ||
public Activity ExpectedParent { get; set; } | ||
|
||
public int Started { get; private set; } | ||
public int Stopped { get; private set; } | ||
public Activity LastStartedActivity { get; private set; } | ||
public Activity LastFinishedActivity { get; private set; } | ||
public IEnumerable<Activity> FinishedActivities => _finishedActivities; | ||
|
||
public ActivityRecorder(string activitySourceName, string activityName) | ||
{ | ||
_activitySourceName = activitySourceName; | ||
_activityName = activityName; | ||
_listener = new ActivityListener | ||
{ | ||
ShouldListenTo = (activitySource) => activitySource.Name == _activitySourceName, | ||
Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllData, | ||
ActivityStarted = (activity) => { | ||
if (activity.OperationName == _activityName && Filter(activity)) | ||
{ | ||
if (VerifyParent) | ||
{ | ||
Assert.Same(ExpectedParent, activity.Parent); | ||
} | ||
|
||
Started++; | ||
LastStartedActivity = activity; | ||
} | ||
}, | ||
ActivityStopped = (activity) => { | ||
if (activity.OperationName == _activityName && Filter(activity)) | ||
{ | ||
if (VerifyParent) | ||
{ | ||
Assert.Same(ExpectedParent, activity.Parent); | ||
} | ||
|
||
Stopped++; | ||
LastFinishedActivity = activity; | ||
_finishedActivities.Add(activity); | ||
} | ||
} | ||
}; | ||
|
||
ActivitySource.AddActivityListener(_listener); | ||
} | ||
|
||
public void Dispose() => _listener.Dispose(); | ||
|
||
public void VerifyActivityRecorded(int times) | ||
{ | ||
Assert.Equal(times, Started); | ||
Assert.Equal(times, Stopped); | ||
} | ||
|
||
public Activity VerifyActivityRecordedOnce() | ||
{ | ||
VerifyActivityRecorded(1); | ||
return LastFinishedActivity; | ||
} | ||
} | ||
|
||
internal static class ActivityAssert | ||
{ | ||
public static KeyValuePair<string, object> HasTag(Activity activity, string name) | ||
{ | ||
KeyValuePair<string, object> tag = activity.TagObjects.SingleOrDefault(t => t.Key == name); | ||
if (tag.Key is null) | ||
{ | ||
Assert.Fail($"The Activity tags should contain {name}."); | ||
} | ||
return tag; | ||
} | ||
|
||
public static void HasTag<T>(Activity activity, string name, T expectedValue) | ||
{ | ||
KeyValuePair<string, object> tag = HasTag(activity, name); | ||
Assert.Equal(expectedValue, (T)tag.Value); | ||
} | ||
|
||
public static void HasTag<T>(Activity activity, string name, Func<T, bool> verifyValue) | ||
{ | ||
T? value = (T?)activity.TagObjects.SingleOrDefault(t => t.Key == name).Value; | ||
Assert.False(value is null, $"The Activity tags should contain {name}."); | ||
Assert.True(verifyValue(value)); | ||
} | ||
|
||
public static void HasNoTag(Activity activity, string name) | ||
{ | ||
bool contains = activity.TagObjects.Any(t => t.Key == name); | ||
Assert.False(contains, $"The Activity tags should not contain {name}."); | ||
} | ||
|
||
public static void FinishedInOrder(Activity first, Activity second) | ||
{ | ||
Assert.True(first.StartTimeUtc + first.Duration < second.StartTimeUtc + second.Duration, $"{first.OperationName} should stop before {second.OperationName}"); | ||
} | ||
|
||
public static string CamelToSnake(string camel) | ||
{ | ||
if (string.IsNullOrEmpty(camel)) return camel; | ||
StringBuilder bld = new(); | ||
bld.Append(char.ToLower(camel[0])); | ||
for (int i = 1; i < camel.Length; i++) | ||
{ | ||
char c = camel[i]; | ||
if (char.IsUpper(c)) | ||
{ | ||
bld.Append('_'); | ||
} | ||
bld.Append(char.ToLower(c)); | ||
} | ||
return bld.ToString(); | ||
} | ||
} | ||
} |
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
98 changes: 98 additions & 0 deletions
98
...rc/System/Net/Http/SocketsHttpHandler/ConnectionPool/ConnectionSetupDistributedTracing.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,98 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Threading; | ||
|
||
namespace System.Net.Http | ||
{ | ||
// Implements distributed tracing logic for managing the "HTTP connection_setup" and "HTTP wait_for_connection" Activities. | ||
internal static class ConnectionSetupDistributedTracing | ||
{ | ||
private static readonly ActivitySource s_connectionsActivitySource = new ActivitySource(DiagnosticsHandlerLoggingStrings.ConnectionsNamespace); | ||
|
||
public static Activity? StartConnectionSetupActivity(bool isSecure, HttpAuthority authority) | ||
{ | ||
Activity? activity = null; | ||
if (s_connectionsActivitySource.HasListeners()) | ||
{ | ||
// Connection activities should be new roots and not parented under whatever | ||
// request happens to be in progress when the connection is started. | ||
Activity.Current = null; | ||
activity = s_connectionsActivitySource.StartActivity(DiagnosticsHandlerLoggingStrings.ConnectionSetupActivityName); | ||
} | ||
|
||
if (activity is not null) | ||
{ | ||
activity.DisplayName = $"HTTP connection_setup {authority.HostValue}:{authority.Port}"; | ||
if (activity.IsAllDataRequested) | ||
{ | ||
activity.SetTag("server.address", authority.HostValue); | ||
activity.SetTag("server.port", authority.Port); | ||
activity.SetTag("url.scheme", isSecure ? "https" : "http"); | ||
} | ||
} | ||
|
||
return activity; | ||
} | ||
|
||
public static void StopConnectionSetupActivity(Activity activity, Exception? exception, IPEndPoint? remoteEndPoint) | ||
{ | ||
Debug.Assert(activity is not null); | ||
if (exception is not null) | ||
{ | ||
ReportError(activity, exception); | ||
} | ||
else | ||
{ | ||
if (activity.IsAllDataRequested && remoteEndPoint is not null) | ||
{ | ||
activity.SetTag("network.peer.address", remoteEndPoint.Address.ToString()); | ||
} | ||
} | ||
|
||
activity.Stop(); | ||
} | ||
|
||
public static void ReportError(Activity? activity, Exception exception) | ||
{ | ||
Debug.Assert(exception is not null); | ||
if (activity is null) return; | ||
activity.SetStatus(ActivityStatusCode.Error); | ||
|
||
if (activity.IsAllDataRequested) | ||
{ | ||
DiagnosticsHelper.TryGetErrorType(null, exception, out string? errorType); | ||
Debug.Assert(errorType is not null, "DiagnosticsHelper.TryGetErrorType() should succeed whenever an exception is provided."); | ||
activity.SetTag("error.type", errorType); | ||
} | ||
} | ||
|
||
public static Activity? StartWaitForConnectionActivity(HttpAuthority authority) | ||
{ | ||
Activity? activity = s_connectionsActivitySource.StartActivity(DiagnosticsHandlerLoggingStrings.WaitForConnectionActivityName); | ||
if (activity is not null) | ||
{ | ||
activity.DisplayName = $"HTTP wait_for_connection {authority.HostValue}:{authority.Port}"; | ||
} | ||
|
||
return activity; | ||
} | ||
|
||
public static void AddConnectionLinkToRequestActivity(Activity connectionSetupActivity) | ||
{ | ||
Debug.Assert(connectionSetupActivity is not null); | ||
|
||
// We only support links for request activities created by the "System.Net.Http" ActivitySource. | ||
if (DiagnosticsHandler.s_activitySource.HasListeners()) | ||
{ | ||
Activity? requestActivity = Activity.Current; | ||
if (requestActivity?.Source == DiagnosticsHandler.s_activitySource) | ||
{ | ||
requestActivity.AddLink(new ActivityLink(connectionSetupActivity.Context)); | ||
} | ||
} | ||
} | ||
} | ||
} |
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
Oops, something went wrong.