File: Rendering\RenderingMetrics.cs
Web Access
Project: src\src\Components\Components\src\Microsoft.AspNetCore.Components.csproj (Microsoft.AspNetCore.Components)
// 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;
using Microsoft.AspNetCore.Http;
 
namespace Microsoft.AspNetCore.Components.Rendering;
 
internal sealed class RenderingMetrics : IDisposable
{
    public const string MeterName = "Microsoft.AspNetCore.Components.Rendering";
 
    private readonly Meter _meter;
    private readonly Counter<long> _renderTotalCounter;
    private readonly UpDownCounter<long> _renderActiveCounter;
    private readonly Histogram<double> _renderDuration;
 
    public RenderingMetrics(IMeterFactory meterFactory)
    {
        Debug.Assert(meterFactory != null);
 
        _meter = meterFactory.Create(MeterName);
 
        _renderTotalCounter = _meter.CreateCounter<long>(
            "aspnetcore.components.rendering.count",
            unit: "{renders}",
            description: "Number of component renders performed.");
 
        _renderActiveCounter = _meter.CreateUpDownCounter<long>(
            "aspnetcore.components.rendering.active_renders",
            unit: "{renders}",
            description: "Number of component renders performed.");
 
        _renderDuration = _meter.CreateHistogram<double>(
            "aspnetcore.components.rendering.duration",
            unit: "ms",
            description: "Duration of component rendering operations per component.",
            advice: new InstrumentAdvice<double> { HistogramBucketBoundaries = MetricsConstants.ShortSecondsBucketBoundaries });
    }
 
    public void RenderStart(string componentType)
    {
        var tags = new TagList();
        tags = InitializeRequestTags(componentType, tags);
 
        if (_renderActiveCounter.Enabled)
        {
            _renderActiveCounter.Add(1, tags);
        }
        if (_renderTotalCounter.Enabled)
        {
            _renderTotalCounter.Add(1, tags);
        }
    }
 
    public void RenderEnd(string componentType, Exception? exception, long startTimestamp, long currentTimestamp)
    {
        // Tags must match request start.
        var tags = new TagList();
        tags = InitializeRequestTags(componentType, tags);
 
        if (_renderActiveCounter.Enabled)
        {
            _renderActiveCounter.Add(-1, tags);
        }
 
        if (_renderDuration.Enabled)
        {
            if (exception != null)
            {
                TryAddTag(ref tags, "error.type", exception.GetType().FullName);
            }
 
            var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp);
            _renderDuration.Record(duration.TotalMilliseconds, tags);
        }
    }
 
    private static TagList InitializeRequestTags(string componentType, TagList tags)
    {
        tags.Add("component.type", componentType);
        return tags;
    }
 
    public bool IsDurationEnabled() => _renderDuration.Enabled;
 
    public void Dispose()
    {
        _meter.Dispose();
    }
 
    private static bool TryAddTag(ref TagList tags, string name, object? value)
    {
        for (var i = 0; i < tags.Count; i++)
        {
            if (tags[i].Key == name)
            {
                return false;
            }
        }
 
        tags.Add(new KeyValuePair<string, object?>(name, value));
        return true;
    }
}