Skip to content

Commit 9b75d60

Browse files
committed
initial
1 parent 8491a27 commit 9b75d60

File tree

3 files changed

+100
-0
lines changed

3 files changed

+100
-0
lines changed

src/Components/Components/src/Microsoft.AspNetCore.Components.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<Compile Include="$(SharedSourceRoot)Debugger\DictionaryItemDebugView.cs" LinkBase="Shared" />
2424
<Compile Include="$(SharedSourceRoot)Debugger\DictionaryDebugView.cs" LinkBase="Shared" />
2525
<Compile Include="$(SharedSourceRoot)UrlDecoder\UrlDecoder.cs" LinkBase="Shared" />
26+
<Compile Include="$(SharedSourceRoot)Metrics\MetricsConstants.cs" LinkBase="Shared" />
2627
</ItemGroup>
2728

2829
<Import Project="Microsoft.AspNetCore.Components.Routing.targets" />

src/Components/Components/src/RenderTree/Renderer.cs

+8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using System.Diagnostics;
77
using System.Diagnostics.CodeAnalysis;
8+
using System.Diagnostics.Metrics;
89
using System.Linq;
910
using Microsoft.AspNetCore.Components.HotReload;
1011
using Microsoft.AspNetCore.Components.Reflection;
@@ -24,6 +25,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree;
2425
// dispatching events to them, and notifying when the user interface is being updated.
2526
public abstract partial class Renderer : IDisposable, IAsyncDisposable
2627
{
28+
private readonly RenderingMetrics? _renderingMetrics;
2729
private readonly object _lockObject = new();
2830
private readonly IServiceProvider _serviceProvider;
2931
private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>();
@@ -90,6 +92,9 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory,
9092
_logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Components.RenderTree.Renderer");
9193
_componentFactory = new ComponentFactory(componentActivator, this);
9294

95+
var meterFactory = serviceProvider.GetService<IMeterFactory>();
96+
_renderingMetrics = meterFactory != null ? new RenderingMetrics(meterFactory) : null;
97+
9398
ServiceProviderCascadingValueSuppliers = serviceProvider.GetService<ICascadingValueSupplier>() is null
9499
? Array.Empty<ICascadingValueSupplier>()
95100
: serviceProvider.GetServices<ICascadingValueSupplier>().ToArray();
@@ -926,12 +931,15 @@ private void RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
926931
{
927932
var componentState = renderQueueEntry.ComponentState;
928933
Log.RenderingComponent(_logger, componentState);
934+
var startTime = _renderingMetrics != null ? Stopwatch.GetTimestamp() : 0;
935+
_renderingMetrics?.RenderStart(componentState.Component.GetType().FullName);
929936
componentState.RenderIntoBatch(_batchBuilder, renderQueueEntry.RenderFragment, out var renderFragmentException);
930937
if (renderFragmentException != null)
931938
{
932939
// If this returns, the error was handled by an error boundary. Otherwise it throws.
933940
HandleExceptionViaErrorBoundary(renderFragmentException, componentState);
934941
}
942+
_renderingMetrics?.RenderEnd(componentState.Component.GetType().FullName, renderFragmentException, startTime, Stopwatch.GetTimestamp());
935943

936944
// Process disposal queue now in case it causes further component renders to be enqueued
937945
ProcessDisposalQueueInExistingBatch();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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+
4+
using System.Diagnostics;
5+
using System.Diagnostics.Metrics;
6+
using Microsoft.AspNetCore.Http;
7+
8+
namespace Microsoft.AspNetCore.Components.Rendering;
9+
10+
internal sealed class RenderingMetrics : IDisposable
11+
{
12+
public const string MeterName = "Microsoft.AspNetCore.Components.Rendering";
13+
14+
private readonly Meter _meter;
15+
private readonly Counter<long> _renderCounter;
16+
private readonly Histogram<double> _renderDuration;
17+
18+
public RenderingMetrics(IMeterFactory meterFactory)
19+
{
20+
_meter = meterFactory.Create(MeterName);
21+
22+
_renderCounter = _meter.CreateCounter<long>(
23+
"aspnetcore.components.rendering.count",
24+
unit: "{render}",
25+
description: "Number of component renders performed.");
26+
27+
_renderDuration = _meter.CreateHistogram<double>(
28+
"aspnetcore.components.rendering.duration",
29+
unit: "ms",
30+
description: "Duration of component rendering operations per component.",
31+
advice: new InstrumentAdvice<double> { HistogramBucketBoundaries = MetricsConstants.ShortSecondsBucketBoundaries });
32+
}
33+
34+
public void RenderStart(string componentType)
35+
{
36+
var tags = new TagList();
37+
tags = InitializeRequestTags(componentType, tags);
38+
39+
_renderCounter.Add(1, tags);
40+
}
41+
42+
public void RenderEnd(string componentType, Exception? exception, long startTimestamp, long currentTimestamp)
43+
{
44+
var tags = new TagList();
45+
tags = InitializeRequestTags(componentType, tags);
46+
47+
// Tags must match request start.
48+
if (_renderCounter.Enabled)
49+
{
50+
_renderCounter.Add(-1, tags);
51+
}
52+
53+
if (_renderDuration.Enabled)
54+
{
55+
if (exception != null)
56+
{
57+
TryAddTag(ref tags, "error.type", exception.GetType().FullName);
58+
}
59+
60+
var duration = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp);
61+
_renderDuration.Record(duration.TotalMilliseconds, tags);
62+
}
63+
}
64+
65+
private static TagList InitializeRequestTags(string componentType, TagList tags)
66+
{
67+
tags.Add("component.type", componentType);
68+
return tags;
69+
}
70+
71+
public bool IsEnabled() => _renderCounter.Enabled || _renderDuration.Enabled;
72+
73+
public void Dispose()
74+
{
75+
_meter.Dispose();
76+
}
77+
78+
private static bool TryAddTag(ref TagList tags, string name, object? value)
79+
{
80+
for (var i = 0; i < tags.Count; i++)
81+
{
82+
if (tags[i].Key == name)
83+
{
84+
return false;
85+
}
86+
}
87+
88+
tags.Add(new KeyValuePair<string, object?>(name, value));
89+
return true;
90+
}
91+
}

0 commit comments

Comments
 (0)