< Summary

Information
Class: System.Net.Http.Metrics.MetricsHandler
Assembly: System.Net.Http
File(s): D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\Metrics\MetricsHandler.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 105
Coverable lines: 105
Total lines: 171
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 30
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)0%440%
SendAsync(...)0%660%
SendAsyncWithMetrics()0%220%
Dispose(...)0%220%
RequestStart(...)0%220%
RequestStop(...)0%10100%
InitializeCommonTags(...)0%440%
.ctor()100%110%
Dispose(...)100%110%

File(s)

D:\runner\runtime\src\libraries\System.Net.Http\src\System\Net\Http\Metrics\MetricsHandler.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Diagnostics.Metrics;
 8using System.Threading;
 9using System.Threading.Tasks;
 10
 11namespace System.Net.Http.Metrics
 12{
 13    internal sealed class MetricsHandler : HttpMessageHandlerStage
 14    {
 15        private readonly HttpMessageHandler _innerHandler;
 16        private readonly UpDownCounter<long> _activeRequests;
 17        private readonly Histogram<double> _requestsDuration;
 18        private readonly IWebProxy? _proxy;
 19
 020        public MetricsHandler(HttpMessageHandler innerHandler, IMeterFactory? meterFactory, IWebProxy? proxy, out Meter 
 021        {
 022            Debug.Assert(GlobalHttpSettings.MetricsHandler.IsGloballyEnabled);
 23
 024            _innerHandler = innerHandler;
 025            _proxy = proxy;
 26
 027            meter = meterFactory?.Create("System.Net.Http") ?? SharedMeter.Instance;
 28
 29            // Meter has a cache for the instruments it owns
 030            _activeRequests = meter.CreateUpDownCounter<long>(
 031                "http.client.active_requests",
 032                unit: "{request}",
 033                description: "Number of outbound HTTP requests that are currently active on the client.");
 034            _requestsDuration = meter.CreateHistogram<double>(
 035                "http.client.request.duration",
 036                unit: "s",
 037                description: "Duration of HTTP client requests.",
 038                advice: DiagnosticsHelper.ShortHistogramAdvice);
 039        }
 40
 41        internal override ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, CancellationT
 042        {
 043            if (_activeRequests.Enabled || _requestsDuration.Enabled)
 044            {
 045                return SendAsyncWithMetrics(request, async, cancellationToken);
 46            }
 47            else
 048            {
 049                return async ?
 050                    new ValueTask<HttpResponseMessage>(_innerHandler.SendAsync(request, cancellationToken)) :
 051                    new ValueTask<HttpResponseMessage>(_innerHandler.Send(request, cancellationToken));
 52            }
 053        }
 54
 55        private async ValueTask<HttpResponseMessage> SendAsyncWithMetrics(HttpRequestMessage request, bool async, Cancel
 056        {
 057            Debug.Assert(GlobalHttpSettings.MetricsHandler.IsGloballyEnabled);
 58
 059            (long startTimestamp, bool recordCurrentRequests) = RequestStart(request);
 060            HttpResponseMessage? response = null;
 061            Exception? exception = null;
 62            try
 063            {
 064                response = async ?
 065                    await _innerHandler.SendAsync(request, cancellationToken).ConfigureAwait(false) :
 066                    _innerHandler.Send(request, cancellationToken);
 067                return response;
 68            }
 069            catch (Exception ex)
 070            {
 071                exception = ex;
 072                throw;
 73            }
 74            finally
 075            {
 076                RequestStop(request, response, exception, startTimestamp, recordCurrentRequests);
 077            }
 078        }
 79
 80        protected override void Dispose(bool disposing)
 081        {
 082            if (disposing)
 083            {
 084                _innerHandler.Dispose();
 085            }
 86
 087            base.Dispose(disposing);
 088        }
 89
 90        private (long StartTimestamp, bool RecordCurrentRequests) RequestStart(HttpRequestMessage request)
 091        {
 092            bool recordCurrentRequests = _activeRequests.Enabled;
 093            long startTimestamp = Stopwatch.GetTimestamp();
 94
 095            if (recordCurrentRequests)
 096            {
 097                TagList tags = InitializeCommonTags(request);
 098                _activeRequests.Add(1, tags);
 099            }
 100
 0101            return (startTimestamp, recordCurrentRequests);
 0102        }
 103
 104        private void RequestStop(HttpRequestMessage request, HttpResponseMessage? response, Exception? exception, long s
 0105        {
 0106            TagList tags = InitializeCommonTags(request);
 107
 0108            if (recordCurrentRequests)
 0109            {
 0110                _activeRequests.Add(-1, tags);
 0111            }
 112
 0113            if (!_requestsDuration.Enabled)
 0114            {
 0115                return;
 116            }
 117
 0118            if (response is not null)
 0119            {
 0120                tags.Add("http.response.status_code", DiagnosticsHelper.GetBoxedInt32((int)response.StatusCode));
 0121                tags.Add("network.protocol.version", DiagnosticsHelper.GetProtocolVersionString(response.Version));
 0122            }
 123
 0124            if (DiagnosticsHelper.TryGetErrorType(response, exception, out string? errorType))
 0125            {
 0126                tags.Add("error.type", errorType);
 0127            }
 128
 0129            TimeSpan durationTime = Stopwatch.GetElapsedTime(startTimestamp, Stopwatch.GetTimestamp());
 130
 0131            List<Action<HttpMetricsEnrichmentContext>>? callbacks = HttpMetricsEnrichmentContext.GetEnrichmentCallbacksF
 0132            if (callbacks is null)
 0133            {
 0134                _requestsDuration.Record(durationTime.TotalSeconds, tags);
 0135            }
 136            else
 0137            {
 0138                HttpMetricsEnrichmentContext.RecordDurationWithEnrichment(callbacks, request, response, exception, durat
 0139            }
 0140        }
 141
 142        private TagList InitializeCommonTags(HttpRequestMessage request)
 0143        {
 0144            TagList tags = default;
 145
 0146            if (request.RequestUri is Uri requestUri && requestUri.IsAbsoluteUri)
 0147            {
 0148                tags.Add("url.scheme", requestUri.Scheme);
 0149                tags.Add("server.address", DiagnosticsHelper.GetServerAddress(request, _proxy));
 0150                tags.Add("server.port", DiagnosticsHelper.GetBoxedInt32(requestUri.Port));
 0151            }
 0152            tags.Add(DiagnosticsHelper.GetMethodTag(request.Method, out _));
 153
 0154            return tags;
 0155        }
 156
 157        private sealed class SharedMeter : Meter
 158        {
 0159            public static Meter Instance { get; } = new SharedMeter();
 160            private SharedMeter()
 0161                : base("System.Net.Http")
 0162            {
 0163            }
 164
 165            protected override void Dispose(bool disposing)
 0166            {
 167                // NOP to prevent disposing the global instance from MeterListener callbacks.
 0168            }
 169        }
 170    }
 171}