| File: Internal\HttpConnectionsMetrics.cs | Web Access |
| Project: src\aspnetcore\src\SignalR\common\Http.Connections\src\Microsoft.AspNetCore.Http.Connections.csproj (Microsoft.AspNetCore.Http.Connections) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Diagnostics.Metrics; namespace Microsoft.AspNetCore.Http.Connections.Internal; internal readonly struct MetricsContext { public MetricsContext(bool connectionDurationEnabled, bool currentConnectionsCounterEnabled) { ConnectionDurationEnabled = connectionDurationEnabled; CurrentConnectionsCounterEnabled = currentConnectionsCounterEnabled; } public bool ConnectionDurationEnabled { get; } public bool CurrentConnectionsCounterEnabled { get; } } internal sealed class HttpConnectionsMetrics : IDisposable { public const string MeterName = "Microsoft.AspNetCore.Http.Connections"; private readonly Meter _meter; private readonly Histogram<double> _connectionDuration; private readonly UpDownCounter<long> _currentConnectionsCounter; public HttpConnectionsMetrics(IMeterFactory meterFactory) { _meter = meterFactory.Create(MeterName); _connectionDuration = _meter.CreateHistogram<double>( "signalr.server.connection.duration", unit: "s", description: "The duration of connections on the server.", advice: new InstrumentAdvice<double> { HistogramBucketBoundaries = MetricsConstants.LongSecondsBucketBoundaries }); _currentConnectionsCounter = _meter.CreateUpDownCounter<long>( "signalr.server.active_connections", unit: "{connection}", description: "Number of connections that are currently active on the server."); } public void ConnectionStop(in MetricsContext metricsContext, HttpTransportType transportType, HttpConnectionStopStatus status, long startTimestamp, long currentTimestamp) { if (metricsContext.ConnectionDurationEnabled) { var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp); _connectionDuration.Record(duration.TotalSeconds, new KeyValuePair<string, object?>("signalr.connection.status", ResolveStopStatus(status)), new KeyValuePair<string, object?>("signalr.transport", ResolveTransportType(transportType))); } } public void ConnectionTransportStart(in MetricsContext metricsContext, HttpTransportType transportType) { Debug.Assert(transportType != HttpTransportType.None); // Tags must match transport end. if (metricsContext.CurrentConnectionsCounterEnabled) { _currentConnectionsCounter.Add(1, new KeyValuePair<string, object?>("signalr.transport", ResolveTransportType(transportType))); } } public void TransportStop(in MetricsContext metricsContext, HttpTransportType transportType) { if (metricsContext.CurrentConnectionsCounterEnabled) { // Tags must match transport start. // If the transport type is none then the transport was never started for this connection. if (transportType != HttpTransportType.None) { _currentConnectionsCounter.Add(-1, new KeyValuePair<string, object?>("signalr.transport", ResolveTransportType(transportType))); } } } private static string ResolveTransportType(HttpTransportType transportType) { return transportType switch { HttpTransportType.ServerSentEvents => "server_sent_events", HttpTransportType.LongPolling => "long_polling", HttpTransportType.WebSockets => "web_sockets", _ => throw new InvalidOperationException("Unexpected value: " + transportType) }; } private static string ResolveStopStatus(HttpConnectionStopStatus connectionStopStatus) { return connectionStopStatus switch { HttpConnectionStopStatus.NormalClosure => "normal_closure", HttpConnectionStopStatus.Timeout => "timeout", HttpConnectionStopStatus.AppShutdown => "app_shutdown", _ => throw new InvalidOperationException("Unexpected value: " + connectionStopStatus) }; } public void Dispose() { _meter.Dispose(); } public MetricsContext CreateContext() { return new MetricsContext(_connectionDuration.Enabled, _currentConnectionsCounter.Enabled); } }