| File: System\Net\NameResolutionTelemetry.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.NameResolution\src\System.Net.NameResolution.csproj (System.Net.NameResolution) |
// 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.Diagnostics.Tracing; using System.Net.Sockets; using System.Threading; namespace System.Net { [EventSource(Name = "System.Net.NameResolution")] internal sealed partial class NameResolutionTelemetry : EventSource { public static readonly NameResolutionTelemetry Log = new NameResolutionTelemetry(); private const int ResolutionStartEventId = 1; private const int ResolutionStopEventId = 2; private const int ResolutionFailedEventId = 3; private PollingCounter? _lookupsRequestedCounter; private PollingCounter? _currentLookupsCounter; private EventCounter? _lookupsDuration; private long _lookupsRequested; private long _currentLookups; protected override void OnEventCommand(EventCommandEventArgs command) { if (command.Command == EventCommand.Enable) { // The cumulative number of name resolution requests started since events were enabled _lookupsRequestedCounter ??= new PollingCounter("dns-lookups-requested", this, () => Interlocked.Read(ref _lookupsRequested)) { DisplayName = "DNS Lookups Requested" }; // Current number of DNS requests pending _currentLookupsCounter ??= new PollingCounter("current-dns-lookups", this, () => Interlocked.Read(ref _currentLookups)) { DisplayName = "Current DNS Lookups" }; _lookupsDuration ??= new EventCounter("dns-lookups-duration", this) { DisplayName = "Average DNS Lookup Duration", DisplayUnits = "ms" }; } } [Event(ResolutionStartEventId, Level = EventLevel.Informational)] private void ResolutionStart(string hostNameOrAddress) => WriteEvent(ResolutionStartEventId, hostNameOrAddress); [Event(ResolutionStopEventId, Level = EventLevel.Informational)] private void ResolutionStop() => WriteEvent(ResolutionStopEventId); [Event(ResolutionFailedEventId, Level = EventLevel.Informational)] private void ResolutionFailed() => WriteEvent(ResolutionFailedEventId); [NonEvent] public static bool AnyDiagnosticsEnabled() => !OperatingSystem.IsWasi() && (Log.IsEnabled() || NameResolutionMetrics.IsEnabled() || NameResolutionActivity.IsTracingEnabled()); [NonEvent] public NameResolutionActivity BeforeResolution(object hostNameOrAddress, long startingTimestamp = 0) { if (!AnyDiagnosticsEnabled()) { return default; } if (IsEnabled()) { Interlocked.Increment(ref _lookupsRequested); Interlocked.Increment(ref _currentLookups); if (IsEnabled(EventLevel.Informational, EventKeywords.None)) { string host = GetHostnameFromStateObject(hostNameOrAddress); ResolutionStart(host); } startingTimestamp = startingTimestamp is not 0 ? startingTimestamp : Stopwatch.GetTimestamp(); } startingTimestamp = startingTimestamp is not 0 ? startingTimestamp : NameResolutionMetrics.IsEnabled() ? Stopwatch.GetTimestamp() : 0; return new NameResolutionActivity(hostNameOrAddress, startingTimestamp); } [NonEvent] public void AfterResolution(object hostNameOrAddress, in NameResolutionActivity activity, object? answer, Exception? exception = null) { if (OperatingSystem.IsWasi()) return; if (!activity.Stop(answer, exception, out TimeSpan duration)) { // We stopped the System.Diagnostics.Activity at this point and neither metrics nor EventSource is enabled. return; } if (IsEnabled()) { Interlocked.Decrement(ref _currentLookups); _lookupsDuration?.WriteMetric(duration.TotalMilliseconds); if (IsEnabled(EventLevel.Informational, EventKeywords.None)) { if (exception is not null) { ResolutionFailed(); } ResolutionStop(); } } if (NameResolutionMetrics.IsEnabled()) { NameResolutionMetrics.AfterResolution(duration, GetHostnameFromStateObject(hostNameOrAddress), exception); } } [NonEvent] internal static string GetHostnameFromStateObject(object hostNameOrAddress) { Debug.Assert(hostNameOrAddress is not null); string host = hostNameOrAddress switch { string h => h, KeyValuePair<string, AddressFamily> t => t.Key, IPAddress a => a.ToString(), KeyValuePair<IPAddress, AddressFamily> t => t.Key.ToString(), _ => null! }; Debug.Assert(host is not null, $"Unknown hostNameOrAddress type: {hostNameOrAddress.GetType().Name}"); return host; } [NonEvent] internal static string GetErrorType(Exception exception) => (exception as SocketException)?.SocketErrorCode switch { SocketError.HostNotFound => "host_not_found", SocketError.TryAgain => "try_again", SocketError.AddressFamilyNotSupported => "address_family_not_supported", SocketError.NoRecovery => "no_recovery", _ => exception.GetType().FullName! }; } /// <summary> /// Encapsulates the starting timestamp together with an optional Activity, to represent the name resolution span for various telemetry pillars. /// </summary> internal readonly struct NameResolutionActivity { private const string ActivitySourceName = "Experimental.System.Net.NameResolution"; private const string ActivityName = ActivitySourceName + ".DnsLookup"; private static readonly ActivitySource s_activitySource = new ActivitySource(ActivitySourceName); // _startingTimestamp == 0 means NameResolutionTelemetry and NameResolutionMetrics are both disabled. private readonly long _startingTimestamp; private readonly Activity? _activity; public NameResolutionActivity(object hostNameOrAddress, long startingTimestamp) { _startingTimestamp = startingTimestamp; _activity = s_activitySource.StartActivity(ActivityName); if (_activity is not null) { string host = NameResolutionTelemetry.GetHostnameFromStateObject(hostNameOrAddress); _activity.DisplayName = hostNameOrAddress is IPAddress ? $"DNS reverse lookup {host}" : $"DNS lookup {host}"; if (_activity.IsAllDataRequested) { _activity.SetTag("dns.question.name", host); } } } public static bool IsTracingEnabled() => s_activitySource.HasListeners(); // Returns true if either NameResolutionTelemetry or NameResolutionMetrics is enabled. public bool Stop(object? answer, Exception? exception, out TimeSpan duration) { if (_activity is not null) { if (_activity.IsAllDataRequested) { if (answer is not null) { string[]? answerValues = answer switch { string h => [h], string[] values => values, IPAddress[] addresses => GetStringValues(addresses), IPHostEntry entry => GetStringValues(entry.AddressList), _ => null }; Debug.Assert(answerValues is not null); _activity.SetTag("dns.answers", answerValues); } else { Debug.Assert(exception is not null); string errorType = NameResolutionTelemetry.GetErrorType(exception); _activity.SetTag("error.type", errorType); } } if (exception is not null) { _activity.SetStatus(ActivityStatusCode.Error); } _activity.Stop(); } if (_startingTimestamp == 0) { duration = default; return false; } duration = Stopwatch.GetElapsedTime(_startingTimestamp); return true; static string[] GetStringValues(IPAddress[] addresses) { string[] result = new string[addresses.Length]; for (int i = 0; i < addresses.Length; i++) { result[i] = addresses[i].ToString(); } return result; } } } }