Skip to content

Commit 60b77a6

Browse files
authored
[release/8.0] Adjust System.Net metrics for OTel conformance and consistency (#93414)
* Rename `server.socket.address` to `network.peer.address` (#93255) * Report Exception.FullName for http.client.request.duration/error.type (#93322) * Adjust the DNS lookup duration metric (#93254)
1 parent 9e8d548 commit 60b77a6

File tree

7 files changed

+71
-38
lines changed

7 files changed

+71
-38
lines changed

src/libraries/System.Net.Http/src/System/Net/Http/Metrics/MetricsHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ private static bool TryGetErrorType(HttpResponseMessage? response, Exception? ex
169169
HttpRequestError.ConfigurationLimitExceeded => "configuration_limit_exceeded",
170170

171171
// Fall back to the exception type name in case of HttpRequestError.Unknown or when exception is not an HttpRequestException.
172-
_ => exception.GetType().Name
172+
_ => exception.GetType().FullName!
173173
};
174174
return true;
175175
}

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Metrics/ConnectionMetrics.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@ internal sealed class ConnectionMetrics
1414
private readonly object _schemeTag;
1515
private readonly object _hostTag;
1616
private readonly object? _portTag;
17-
private readonly object? _socketAddressTag;
17+
private readonly object? _peerAddressTag;
1818
private bool _currentlyIdle;
1919

20-
public ConnectionMetrics(SocketsHttpHandlerMetrics metrics, string protocolVersion, string scheme, string host, int? port, string? socketAddress)
20+
public ConnectionMetrics(SocketsHttpHandlerMetrics metrics, string protocolVersion, string scheme, string host, int? port, string? peerAddress)
2121
{
2222
_metrics = metrics;
2323
_openConnectionsEnabled = _metrics.OpenConnections.Enabled;
2424
_protocolVersionTag = protocolVersion;
2525
_schemeTag = scheme;
2626
_hostTag = host;
2727
_portTag = port;
28-
_socketAddressTag = socketAddress;
28+
_peerAddressTag = peerAddress;
2929
}
3030

3131
// TagList is a huge struct, so we avoid storing it in a field to reduce the amount we allocate on the heap.
@@ -42,9 +42,9 @@ private TagList GetTags()
4242
tags.Add("server.port", _portTag);
4343
}
4444

45-
if (_socketAddressTag is not null)
45+
if (_peerAddressTag is not null)
4646
{
47-
tags.Add("server.socket.address", _socketAddressTag);
47+
tags.Add("network.peer.address", _peerAddressTag);
4848
}
4949

5050
return tags;

src/libraries/System.Net.Http/tests/FunctionalTests/MetricsTest.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ protected static void VerifyTag<T>(KeyValuePair<string, object?>[] tags, string
4747
}
4848
}
4949

50-
private static void VerifySocketAddress(KeyValuePair<string, object?>[] tags)
50+
private static void VerifyPeerAddress(KeyValuePair<string, object?>[] tags)
5151
{
52-
string ipString = (string)tags.Single(t => t.Key == "server.socket.address").Value;
52+
string ipString = (string)tags.Single(t => t.Key == "network.peer.address").Value;
5353
IPAddress ip = IPAddress.Parse(ipString);
5454
Assert.True(ip.Equals(IPAddress.Loopback.MapToIPv6()) ||
5555
ip.Equals(IPAddress.Loopback) ||
@@ -122,7 +122,7 @@ protected static void VerifyOpenConnections(string actualName, object measuremen
122122
VerifySchemeHostPortTags(tags, uri);
123123
VerifyTag(tags, "network.protocol.version", GetVersionString(protocolVersion));
124124
VerifyTag(tags, "http.connection.state", state);
125-
VerifySocketAddress(tags);
125+
VerifyPeerAddress(tags);
126126
}
127127

128128
protected static void VerifyConnectionDuration(string instrumentName, object measurement, KeyValuePair<string, object?>[] tags, Uri uri, Version? protocolVersion)
@@ -132,7 +132,7 @@ protected static void VerifyConnectionDuration(string instrumentName, object mea
132132
Assert.InRange(value, double.Epsilon, 60);
133133
VerifySchemeHostPortTags(tags, uri);
134134
VerifyTag(tags, "network.protocol.version", GetVersionString(protocolVersion));
135-
VerifySocketAddress(tags);
135+
VerifyPeerAddress(tags);
136136
}
137137

138138
protected static void VerifyTimeInQueue(string instrumentName, object measurement, KeyValuePair<string, object?>[] tags, Uri uri, Version? protocolVersion, string method = "GET")
@@ -670,8 +670,8 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
670670
_output.WriteLine($"Client exception: {clientException}");
671671

672672
string[] expectedExceptionTypes = TestAsync
673-
? [nameof(TaskCanceledException)]
674-
: [nameof(TaskCanceledException), nameof(OperationCanceledException)];
673+
? [typeof(TaskCanceledException).FullName]
674+
: [typeof(TaskCanceledException).FullName, typeof(OperationCanceledException).FullName];
675675

676676
Measurement<double> m = Assert.Single(recorder.GetMeasurements());
677677
VerifyRequestDuration(m, uri, acceptedErrorTypes: expectedExceptionTypes);
@@ -852,7 +852,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
852852
Assert.True(ex is HttpRequestException or TaskCanceledException);
853853

854854
Measurement<double> m = Assert.Single(recorder.GetMeasurements());
855-
VerifyRequestDuration(m, uri, acceptedErrorTypes: [nameof(TaskCanceledException), "response_ended"]);
855+
VerifyRequestDuration(m, uri, acceptedErrorTypes: [typeof(TaskCanceledException).FullName, "response_ended"]);
856856
}, async server =>
857857
{
858858
try

src/libraries/System.Net.NameResolution/src/System/Net/Dns.cs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ public static string GetHostName()
2323
{
2424
name = NameResolutionPal.GetHostName();
2525
}
26-
catch when (LogFailure(string.Empty, startingTimestamp))
26+
catch (Exception ex) when (LogFailure(string.Empty, startingTimestamp, ex))
2727
{
2828
Debug.Fail("LogFailure should return false");
2929
throw;
3030
}
3131

32-
NameResolutionTelemetry.Log.AfterResolution(string.Empty, startingTimestamp, successful: true);
32+
NameResolutionTelemetry.Log.AfterResolution(string.Empty, startingTimestamp);
3333

3434
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, name);
3535
return name;
@@ -394,13 +394,13 @@ private static object GetHostEntryOrAddressesCore(string hostName, bool justAddr
394394
Aliases = aliases
395395
};
396396
}
397-
catch when (LogFailure(hostName, startingTimestamp))
397+
catch (Exception ex) when (LogFailure(hostName, startingTimestamp, ex))
398398
{
399399
Debug.Fail("LogFailure should return false");
400400
throw;
401401
}
402402

403-
NameResolutionTelemetry.Log.AfterResolution(hostName, startingTimestamp, successful: true);
403+
NameResolutionTelemetry.Log.AfterResolution(hostName, startingTimestamp);
404404

405405
return result;
406406
}
@@ -434,13 +434,13 @@ private static object GetHostEntryOrAddressesCore(IPAddress address, bool justAd
434434
}
435435
Debug.Assert(name != null);
436436
}
437-
catch when (LogFailure(address, startingTimestamp))
437+
catch (Exception ex) when (LogFailure(address, startingTimestamp, ex))
438438
{
439439
Debug.Fail("LogFailure should return false");
440440
throw;
441441
}
442442

443-
NameResolutionTelemetry.Log.AfterResolution(address, startingTimestamp, successful: true);
443+
NameResolutionTelemetry.Log.AfterResolution(address, startingTimestamp);
444444

445445
// Do the forward lookup to get the IPs for that host name
446446
startingTimestamp = NameResolutionTelemetry.Log.BeforeResolution(name);
@@ -464,13 +464,13 @@ private static object GetHostEntryOrAddressesCore(IPAddress address, bool justAd
464464
AddressList = addresses
465465
};
466466
}
467-
catch when (LogFailure(name, startingTimestamp))
467+
catch (Exception ex) when (LogFailure(name, startingTimestamp, ex))
468468
{
469469
Debug.Fail("LogFailure should return false");
470470
throw;
471471
}
472472

473-
NameResolutionTelemetry.Log.AfterResolution(name, startingTimestamp, successful: true);
473+
NameResolutionTelemetry.Log.AfterResolution(name, startingTimestamp);
474474

475475
// One of three things happened:
476476
// 1. Success.
@@ -577,7 +577,7 @@ private static Task GetHostEntryOrAddressesCoreAsync(string hostName, bool justR
577577
}
578578

579579
private static Task<T>? GetAddrInfoWithTelemetryAsync<T>(string hostName, bool justAddresses, AddressFamily addressFamily, CancellationToken cancellationToken)
580-
where T : class
580+
where T : class
581581
{
582582
long startingTimestamp = Stopwatch.GetTimestamp();
583583
Task? task = NameResolutionPal.GetAddrInfoAsync(hostName, justAddresses, addressFamily, cancellationToken);
@@ -594,15 +594,19 @@ private static Task GetHostEntryOrAddressesCoreAsync(string hostName, bool justR
594594
static async Task<T> CompleteAsync(Task task, string hostName, long startingTimestamp)
595595
{
596596
_ = NameResolutionTelemetry.Log.BeforeResolution(hostName);
597-
T? result = null;
597+
Exception? exception = null;
598598
try
599599
{
600-
result = await ((Task<T>)task).ConfigureAwait(false);
601-
return result;
600+
return await ((Task<T>)task).ConfigureAwait(false);
601+
}
602+
catch (Exception ex)
603+
{
604+
exception = ex;
605+
throw;
602606
}
603607
finally
604608
{
605-
NameResolutionTelemetry.Log.AfterResolution(hostName, startingTimestamp, successful: result is not null);
609+
NameResolutionTelemetry.Log.AfterResolution(hostName, startingTimestamp, exception);
606610
}
607611
}
608612
}
@@ -627,9 +631,9 @@ private static void ValidateHostName(string hostName)
627631
}
628632
}
629633

630-
private static bool LogFailure(object hostNameOrAddress, long? startingTimestamp)
634+
private static bool LogFailure(object hostNameOrAddress, long? startingTimestamp, Exception exception)
631635
{
632-
NameResolutionTelemetry.Log.AfterResolution(hostNameOrAddress, startingTimestamp, successful: false);
636+
NameResolutionTelemetry.Log.AfterResolution(hostNameOrAddress, startingTimestamp, exception);
633637
return false;
634638
}
635639

src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionMetrics.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,35 @@ internal static class NameResolutionMetrics
1313
private static readonly Meter s_meter = new("System.Net.NameResolution");
1414

1515
private static readonly Histogram<double> s_lookupDuration = s_meter.CreateHistogram<double>(
16-
name: "dns.lookups.duration",
16+
name: "dns.lookup.duration",
1717
unit: "s",
1818
description: "Measures the time taken to perform a DNS lookup.");
1919

2020
public static bool IsEnabled() => s_lookupDuration.Enabled;
2121

22-
public static void AfterResolution(TimeSpan duration, string hostName)
22+
public static void AfterResolution(TimeSpan duration, string hostName, Exception? exception)
2323
{
24-
s_lookupDuration.Record(duration.TotalSeconds, KeyValuePair.Create("dns.question.name", (object?)hostName));
24+
var hostNameTag = KeyValuePair.Create("dns.question.name", (object?)hostName);
25+
26+
if (exception is null)
27+
{
28+
s_lookupDuration.Record(duration.TotalSeconds, hostNameTag);
29+
}
30+
else
31+
{
32+
var errorTypeTag = KeyValuePair.Create("error.type", (object?)GetErrorType(exception));
33+
s_lookupDuration.Record(duration.TotalSeconds, hostNameTag, errorTypeTag);
34+
}
2535
}
36+
37+
private static string GetErrorType(Exception exception) => (exception as SocketException)?.SocketErrorCode switch
38+
{
39+
SocketError.HostNotFound => "host_not_found",
40+
SocketError.TryAgain => "try_again",
41+
SocketError.AddressFamilyNotSupported => "address_family_not_supported",
42+
SocketError.NoRecovery => "no_recovery",
43+
44+
_ => exception.GetType().FullName!
45+
};
2646
}
2747
}

src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionTelemetry.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public long BeforeResolution(object hostNameOrAddress)
8181
}
8282

8383
[NonEvent]
84-
public void AfterResolution(object hostNameOrAddress, long? startingTimestamp, bool successful)
84+
public void AfterResolution(object hostNameOrAddress, long? startingTimestamp, Exception? exception = null)
8585
{
8686
Debug.Assert(startingTimestamp.HasValue);
8787
if (startingTimestamp == 0)
@@ -99,7 +99,7 @@ public void AfterResolution(object hostNameOrAddress, long? startingTimestamp, b
9999

100100
if (IsEnabled(EventLevel.Informational, EventKeywords.None))
101101
{
102-
if (!successful)
102+
if (exception is not null)
103103
{
104104
ResolutionFailed();
105105
}
@@ -110,7 +110,7 @@ public void AfterResolution(object hostNameOrAddress, long? startingTimestamp, b
110110

111111
if (NameResolutionMetrics.IsEnabled())
112112
{
113-
NameResolutionMetrics.AfterResolution(duration, GetHostnameFromStateObject(hostNameOrAddress));
113+
NameResolutionMetrics.AfterResolution(duration, GetHostnameFromStateObject(hostNameOrAddress), exception);
114114
}
115115
}
116116

src/libraries/System.Net.NameResolution/tests/FunctionalTests/MetricsTest.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace System.Net.NameResolution.Tests
1414
{
1515
public class MetricsTest
1616
{
17-
private const string DnsLookupDuration = "dns.lookups.duration";
17+
private const string DnsLookupDuration = "dns.lookup.duration";
1818

1919
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
2020
public static void ResolveValidHostName_MetricsRecorded()
@@ -57,17 +57,26 @@ public static async Task ResolveInvalidHostName_MetricsRecorded()
5757
Assert.ThrowsAny<SocketException>(() => Dns.EndGetHostEntry(Dns.BeginGetHostEntry(InvalidHostName, null, null)));
5858
Assert.ThrowsAny<SocketException>(() => Dns.EndGetHostAddresses(Dns.BeginGetHostAddresses(InvalidHostName, null, null)));
5959

60-
double[] measurements = GetMeasurementsForHostname(recorder, InvalidHostName);
60+
double[] measurements = GetMeasurementsForHostname(recorder, InvalidHostName, "host_not_found");
6161

6262
Assert.Equal(6, measurements.Length);
6363
Assert.All(measurements, m => Assert.True(m > double.Epsilon));
6464
}
6565

66-
private static double[] GetMeasurementsForHostname(InstrumentRecorder<double> recorder, string hostname)
66+
private static double[] GetMeasurementsForHostname(InstrumentRecorder<double> recorder, string hostname, string? expectedErrorType = null)
6767
{
6868
return recorder
6969
.GetMeasurements()
70-
.Where(m => m.Tags.ToArray().Any(t => t.Key == "dns.question.name" && t.Value is string hostnameTag && hostnameTag == hostname))
70+
.Where(m =>
71+
{
72+
KeyValuePair<string, object?>[] tags = m.Tags.ToArray();
73+
if (!tags.Any(t => t.Key == "dns.question.name" && t.Value is string hostnameTag && hostnameTag == hostname))
74+
{
75+
return false;
76+
}
77+
string? actualErrorType = tags.FirstOrDefault(t => t.Key == "error.type").Value as string;
78+
return expectedErrorType == actualErrorType;
79+
})
7180
.Select(m => m.Value)
7281
.ToArray();
7382
}

0 commit comments

Comments
 (0)