File: Latency\Internal\HttpRequestLatencyListener.cs
Web Access
Project: src\src\Libraries\Microsoft.Extensions.Http.Diagnostics\Microsoft.Extensions.Http.Diagnostics.csproj (Microsoft.Extensions.Http.Diagnostics)
// 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.Frozen;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Threading;
using Microsoft.Extensions.Diagnostics.Latency;
 
namespace Microsoft.Extensions.Http.Latency.Internal;
 
internal sealed class HttpRequestLatencyListener : EventListener
{
    private const string SocketProviderName = "System.Net.Sockets";
    private const string HttpProviderName = "System.Net.Http";
    private const string NameResolutionProivderName = "System.Net.NameResolution";
 
    private readonly FrozenDictionary<string, FrozenDictionary<string, CheckpointToken>> _eventToTokenMap;
 
    internal HttpClientLatencyContext LatencyContext { get; }
 
    private int _enabled;
 
    internal bool Enabled => _enabled == 1;
 
    public HttpRequestLatencyListener(HttpClientLatencyContext latencyContext, ILatencyContextTokenIssuer tokenIssuer)
    {
        LatencyContext = latencyContext;
        _eventToTokenMap = EventToCheckpointToken.Build(tokenIssuer);
    }
 
    public void Enable()
    {
        if (Interlocked.CompareExchange(ref _enabled, 1, 0) == 0)
        {
#if NETSTANDARD
            foreach (var eventSource in EventSource.GetSources())
            {
                OnEventSourceCreated(eventSource.Name, eventSource);
            }
#else
            // process already existing listeners once again
            EventSourceCreated += (_, args) => OnEventSourceCreated(args.EventSource!);
#endif
 
        }
    }
 
    internal void OnEventWritten(string eventSourceName, string? eventName)
    {
        // If event of interest, add a checkpoint for it.
        if (eventName != null && _eventToTokenMap[eventSourceName].TryGetValue(eventName, out var token))
        {
            LatencyContext.Get()?.AddCheckpoint(token);
        }
    }
 
    internal void OnEventSourceCreated(string eventSourceName, EventSource eventSource)
    {
        if (Enabled && _eventToTokenMap.ContainsKey(eventSourceName))
        {
            EnableEvents(eventSource, EventLevel.Informational);
        }
    }
 
    protected override void OnEventSourceCreated(EventSource eventSource)
    {
        OnEventSourceCreated(eventSource.Name, eventSource);
    }
 
    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
        OnEventWritten(eventData.EventSource.Name, eventData.EventName);
    }
 
    private static class EventToCheckpointToken
    {
        private static readonly Dictionary<string, string> _socketMap = new()
        {
            { "ConnectStart", HttpCheckpoints.SocketConnectStart },
            { "ConnectStop", HttpCheckpoints.SocketConnectEnd }
        };
 
        private static readonly Dictionary<string, string> _nameResolutionMap = new()
        {
            { "ResolutionStart", HttpCheckpoints.NameResolutionStart },
            { "ResolutionStop", HttpCheckpoints.NameResolutionEnd }
        };
 
        private static readonly Dictionary<string, string> _httpMap = new()
        {
            { "ConnectionEstablished", HttpCheckpoints.ConnectionEstablished },
            { "RequestLeftQueue", HttpCheckpoints.RequestLeftQueue },
            { "RequestHeadersStart", HttpCheckpoints.RequestHeadersStart },
            { "RequestHeadersStop", HttpCheckpoints.RequestHeadersEnd },
            { "RequestContentStart", HttpCheckpoints.RequestContentStart },
            { "RequestContentStop", HttpCheckpoints.RequestContentEnd },
            { "ResponseHeadersStart", HttpCheckpoints.ResponseHeadersStart },
            { "ResponseHeadersStop", HttpCheckpoints.ResponseHeadersEnd },
            { "ResponseContentStart", HttpCheckpoints.ResponseContentStart },
            { "ResponseContentStop", HttpCheckpoints.ResponseContentEnd }
        };
 
        public static FrozenDictionary<string, FrozenDictionary<string, CheckpointToken>> Build(ILatencyContextTokenIssuer tokenIssuer)
        {
            Dictionary<string, CheckpointToken> socket = [];
            foreach (var kv in _socketMap)
            {
                socket[kv.Key] = tokenIssuer.GetCheckpointToken(kv.Value);
            }
 
            Dictionary<string, CheckpointToken> nameResolution = [];
            foreach (var kv in _nameResolutionMap)
            {
                nameResolution[kv.Key] = tokenIssuer.GetCheckpointToken(kv.Value);
            }
 
            Dictionary<string, CheckpointToken> http = [];
            foreach (var kv in _httpMap)
            {
                http[kv.Key] = tokenIssuer.GetCheckpointToken(kv.Value);
            }
 
            return new Dictionary<string, FrozenDictionary<string, CheckpointToken>>
            {
                { SocketProviderName, socket.ToFrozenDictionary() },
                { NameResolutionProivderName, nameResolution.ToFrozenDictionary() },
                { HttpProviderName, http.ToFrozenDictionary() }
            }.ToFrozenDictionary();
        }
    }
}