Skip to content

Commit cebb68e

Browse files
committed
rebase
1 parent 7c7f56f commit cebb68e

File tree

13 files changed

+649
-220
lines changed

13 files changed

+649
-220
lines changed

src/Components/Components/src/PublicAPI.Unshipped.txt

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Components.Infrastructure.RenderingMetricsServiceCollectionExtensions
23
Microsoft.AspNetCore.Components.NavigationManager.OnNotFound -> System.EventHandler<Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs!>!
34
Microsoft.AspNetCore.Components.NavigationManager.NotFound() -> void
45
Microsoft.AspNetCore.Components.Routing.IHostEnvironmentNavigationManager.Initialize(string! baseUri, string! uri, System.Func<string!, System.Threading.Tasks.Task!>! onNavigateTo) -> void
@@ -11,5 +12,6 @@ Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttri
1112
Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttribute.SupplyParameterFromPersistentComponentStateAttribute() -> void
1213
Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions
1314
static Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration<TService>(Microsoft.Extensions.DependencyInjection.IServiceCollection! services, Microsoft.AspNetCore.Components.IComponentRenderMode! componentRenderMode) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
15+
static Microsoft.AspNetCore.Components.Infrastructure.RenderingMetricsServiceCollectionExtensions.AddRenderingMetrics(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
1416
static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
1517
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.SignalRendererToFinishRendering() -> void
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 Microsoft.AspNetCore.Components.Rendering;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.DependencyInjection.Extensions;
7+
8+
namespace Microsoft.AspNetCore.Components.Infrastructure;
9+
10+
/// <summary>
11+
/// Infrastructure APIs for registering diagnostic metrics.
12+
/// </summary>
13+
public static class RenderingMetricsServiceCollectionExtensions
14+
{
15+
/// <summary>
16+
/// Registers component rendering metrics
17+
/// </summary>
18+
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
19+
/// <returns>The <see cref="IServiceCollection"/>.</returns>
20+
public static IServiceCollection AddRenderingMetrics(
21+
IServiceCollection services)
22+
{
23+
if (RenderingMetrics.IsMetricsSupported)
24+
{
25+
services.AddMetrics();
26+
services.TryAddSingleton<RenderingMetrics>();
27+
}
28+
29+
return services;
30+
}
31+
}

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

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

66
using System.Diagnostics;
77
using System.Diagnostics.CodeAnalysis;
8-
using System.Diagnostics.Metrics;
98
using System.Linq;
109
using Microsoft.AspNetCore.Components.HotReload;
1110
using Microsoft.AspNetCore.Components.Reflection;
@@ -25,12 +24,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree;
2524
// dispatching events to them, and notifying when the user interface is being updated.
2625
public abstract partial class Renderer : IDisposable, IAsyncDisposable
2726
{
27+
internal static readonly Task CanceledRenderTask = Task.FromCanceled(new CancellationToken(canceled: true));
28+
2829
private readonly object _lockObject = new();
2930
private readonly IServiceProvider _serviceProvider;
3031
private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>();
3132
private readonly Dictionary<IComponent, ComponentState> _componentStateByComponent = new Dictionary<IComponent, ComponentState>();
3233
private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
33-
private readonly Dictionary<ulong, (int RenderedByComponentId, EventCallback Callback)> _eventBindings = new();
34+
private readonly Dictionary<ulong, (int RenderedByComponentId, EventCallback Callback, string? attributeName)> _eventBindings = new();
3435
private readonly Dictionary<ulong, ulong> _eventHandlerIdReplacements = new Dictionary<ulong, ulong>();
3536
private readonly ILogger _logger;
3637
private readonly ComponentFactory _componentFactory;
@@ -92,16 +93,18 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory,
9293
// logger name in here as a string literal.
9394
_logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Components.RenderTree.Renderer");
9495
_componentFactory = new ComponentFactory(componentActivator, this);
95-
96-
// TODO register RenderingMetrics as singleton in DI
97-
var meterFactory = serviceProvider.GetService<IMeterFactory>();
98-
_renderingMetrics = meterFactory != null ? new RenderingMetrics(meterFactory) : null;
96+
if (RenderingMetrics.IsMetricsSupported)
97+
{
98+
_renderingMetrics = serviceProvider.GetService<RenderingMetrics>();
99+
}
99100

100101
ServiceProviderCascadingValueSuppliers = serviceProvider.GetService<ICascadingValueSupplier>() is null
101102
? Array.Empty<ICascadingValueSupplier>()
102103
: serviceProvider.GetServices<ICascadingValueSupplier>().ToArray();
103104
}
104105

106+
internal RenderingMetrics? RenderingMetrics => RenderingMetrics.IsMetricsSupported ? _renderingMetrics : null;
107+
105108
internal ICascadingValueSupplier[] ServiceProviderCascadingValueSuppliers { get; }
106109

107110
internal HotReloadManager HotReloadManager { get; set; } = HotReloadManager.Default;
@@ -437,12 +440,14 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie
437440
{
438441
Dispatcher.AssertAccess();
439442

443+
var eventStartTimestamp = RenderingMetrics.IsMetricsSupported && RenderingMetrics != null && RenderingMetrics.IsEventDurationEnabled ? Stopwatch.GetTimestamp() : 0;
444+
440445
if (waitForQuiescence)
441446
{
442447
_pendingTasks ??= new();
443448
}
444449

445-
var (renderedByComponentId, callback) = GetRequiredEventBindingEntry(eventHandlerId);
450+
var (renderedByComponentId, callback, attributeName) = GetRequiredEventBindingEntry(eventHandlerId);
446451

447452
// If this event attribute was rendered by a component that's since been disposed, don't dispatch the event at all.
448453
// This can occur because event handler disposal is deferred, so event handler IDs can outlive their components.
@@ -484,9 +489,25 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie
484489
_isBatchInProgress = true;
485490

486491
task = callback.InvokeAsync(eventArgs);
492+
493+
// collect metrics
494+
if (RenderingMetrics.IsMetricsSupported && RenderingMetrics != null && RenderingMetrics.IsEventDurationEnabled)
495+
{
496+
var receiverName = (callback.Receiver?.GetType() ?? callback.Delegate.Target?.GetType())?.FullName;
497+
RenderingMetrics.EventDurationSync(eventStartTimestamp, receiverName, attributeName);
498+
_ = RenderingMetrics.CaptureEventDurationAsync(task, eventStartTimestamp, receiverName, attributeName);
499+
}
500+
if (RenderingMetrics.IsMetricsSupported && RenderingMetrics != null && RenderingMetrics.IsEventExceptionEnabled)
501+
{
502+
_ = RenderingMetrics.CaptureEventFailedAsync(task, callback, attributeName);
503+
}
487504
}
488505
catch (Exception e)
489506
{
507+
if (RenderingMetrics.IsMetricsSupported && RenderingMetrics != null && RenderingMetrics.IsEventExceptionEnabled)
508+
{
509+
RenderingMetrics.EventFailed(e.GetType().FullName, callback, attributeName);
510+
}
490511
HandleExceptionViaErrorBoundary(e, receiverComponentState);
491512
return Task.CompletedTask;
492513
}
@@ -497,6 +518,10 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie
497518
// Since the task has yielded - process any queued rendering work before we return control
498519
// to the caller.
499520
ProcessPendingRender();
521+
522+
//callback.Receiver
523+
//callback.Delegate.Method.
524+
500525
}
501526

502527
// Task completed synchronously or is still running. We already processed all of the rendering
@@ -638,15 +663,15 @@ internal void AssignEventHandlerId(int renderedByComponentId, ref RenderTreeFram
638663
//
639664
// When that happens we intentionally box the EventCallback because we need to hold on to
640665
// the receiver.
641-
_eventBindings.Add(id, (renderedByComponentId, callback));
666+
_eventBindings.Add(id, (renderedByComponentId, callback, frame.AttributeName));
642667
}
643668
else if (frame.AttributeValueField is MulticastDelegate @delegate)
644669
{
645670
// This is the common case for a delegate, where the receiver of the event
646671
// is the same as delegate.Target. In this case since the receiver is implicit we can
647672
// avoid boxing the EventCallback object and just re-hydrate it on the other side of the
648673
// render tree.
649-
_eventBindings.Add(id, (renderedByComponentId, new EventCallback(@delegate.Target as IHandleEvent, @delegate)));
674+
_eventBindings.Add(id, (renderedByComponentId, new EventCallback(@delegate.Target as IHandleEvent, @delegate), frame.AttributeName));
650675
}
651676

652677
// NOTE: we do not to handle EventCallback<T> here. EventCallback<T> is only used when passing
@@ -696,7 +721,7 @@ internal void TrackReplacedEventHandlerId(ulong oldEventHandlerId, ulong newEven
696721
_eventHandlerIdReplacements.Add(oldEventHandlerId, newEventHandlerId);
697722
}
698723

699-
private (int RenderedByComponentId, EventCallback Callback) GetRequiredEventBindingEntry(ulong eventHandlerId)
724+
private (int RenderedByComponentId, EventCallback Callback, string? attributeName) GetRequiredEventBindingEntry(ulong eventHandlerId)
700725
{
701726
if (!_eventBindings.TryGetValue(eventHandlerId, out var entry))
702727
{
@@ -770,6 +795,7 @@ private void ProcessRenderQueue()
770795

771796
_isBatchInProgress = true;
772797
var updateDisplayTask = Task.CompletedTask;
798+
var batchStartTimestamp = RenderingMetrics.IsMetricsSupported && RenderingMetrics != null && RenderingMetrics.IsBatchDurationEnabled ? Stopwatch.GetTimestamp() : 0;
773799

774800
try
775801
{
@@ -801,9 +827,23 @@ private void ProcessRenderQueue()
801827
// Fire off the execution of OnAfterRenderAsync, but don't wait for it
802828
// if there is async work to be done.
803829
_ = InvokeRenderCompletedCalls(batch.UpdatedComponents, updateDisplayTask);
830+
831+
if (RenderingMetrics.IsMetricsSupported && RenderingMetrics != null && RenderingMetrics.IsBatchDurationEnabled)
832+
{
833+
_renderingMetrics.BatchDuration(batchStartTimestamp, batch.UpdatedComponents.Count);
834+
}
835+
if (RenderingMetrics.IsMetricsSupported && RenderingMetrics != null && RenderingMetrics.IsBatchExceptionEnabled)
836+
{
837+
_ = _renderingMetrics.CaptureBatchFailedAsync(updateDisplayTask);
838+
}
804839
}
805840
catch (Exception e)
806841
{
842+
if (RenderingMetrics.IsMetricsSupported && RenderingMetrics != null && RenderingMetrics.IsBatchExceptionEnabled)
843+
{
844+
_renderingMetrics.BatchFailed(e.GetType().Name);
845+
}
846+
807847
// Ensure we catch errors while running the render functions of the components.
808848
HandleException(e);
809849
return;
@@ -947,15 +987,13 @@ private void RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
947987
{
948988
var componentState = renderQueueEntry.ComponentState;
949989
Log.RenderingComponent(_logger, componentState);
950-
var startTime = (_renderingMetrics != null && _renderingMetrics.IsDurationEnabled()) ? Stopwatch.GetTimestamp() : 0;
951-
_renderingMetrics?.RenderStart(componentState.Component.GetType().FullName);
990+
952991
componentState.RenderIntoBatch(_batchBuilder, renderQueueEntry.RenderFragment, out var renderFragmentException);
953992
if (renderFragmentException != null)
954993
{
955994
// If this returns, the error was handled by an error boundary. Otherwise it throws.
956995
HandleExceptionViaErrorBoundary(renderFragmentException, componentState);
957996
}
958-
_renderingMetrics?.RenderEnd(componentState.Component.GetType().FullName, renderFragmentException, startTime, Stopwatch.GetTimestamp());
959997

960998
// Process disposal queue now in case it causes further component renders to be enqueued
961999
ProcessDisposalQueueInExistingBatch();

src/Components/Components/src/Rendering/ComponentState.cs

+33-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class ComponentState : IAsyncDisposable
2323
private RenderTreeBuilder _nextRenderTree;
2424
private ArrayBuilder<RenderTreeFrame>? _latestDirectParametersSnapshot; // Lazily instantiated
2525
private bool _componentWasDisposed;
26+
private readonly string? _componentTypeName;
2627

2728
/// <summary>
2829
/// Constructs an instance of <see cref="ComponentState"/>.
@@ -51,6 +52,11 @@ public ComponentState(Renderer renderer, int componentId, IComponent component,
5152
_hasCascadingParameters = true;
5253
_hasAnyCascadingParameterSubscriptions = AddCascadingParameterSubscriptions();
5354
}
55+
56+
if (RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && (_renderer.RenderingMetrics.IsDiffDurationEnabled || _renderer.RenderingMetrics.IsStateDurationEnabled || _renderer.RenderingMetrics.IsStateExceptionEnabled))
57+
{
58+
_componentTypeName = component.GetType().FullName;
59+
}
5460
}
5561

5662
private static ComponentState? GetSectionOutletLogicalParent(Renderer renderer, SectionOutlet sectionOutlet)
@@ -102,6 +108,7 @@ internal void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment re
102108

103109
_nextRenderTree.Clear();
104110

111+
var diffStartTimestamp = RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && _renderer.RenderingMetrics.IsDiffDurationEnabled ? Stopwatch.GetTimestamp() : 0;
105112
try
106113
{
107114
renderFragment(_nextRenderTree);
@@ -118,6 +125,8 @@ internal void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment re
118125
// We don't want to make errors from this be recoverable, because there's no legitimate reason for them to happen
119126
_nextRenderTree.AssertTreeIsValid(Component);
120127

128+
var startCount = batchBuilder.EditsBuffer.Count;
129+
121130
// Swap the old and new tree builders
122131
(CurrentRenderTree, _nextRenderTree) = (_nextRenderTree, CurrentRenderTree);
123132

@@ -129,6 +138,11 @@ internal void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment re
129138
CurrentRenderTree.GetFrames());
130139
batchBuilder.UpdatedComponentDiffs.Append(diff);
131140
batchBuilder.InvalidateParameterViews();
141+
142+
if (RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && _renderer.RenderingMetrics.IsDiffDurationEnabled)
143+
{
144+
_renderer.RenderingMetrics.DiffDuration(diffStartTimestamp, _componentTypeName, batchBuilder.EditsBuffer.Count - startCount);
145+
}
132146
}
133147

134148
// Callers expect this method to always return a faulted task.
@@ -231,14 +245,32 @@ internal void NotifyCascadingValueChanged(in ParameterViewLifetime lifetime)
231245
// a consistent set to the recipient.
232246
private void SupplyCombinedParameters(ParameterView directAndCascadingParameters)
233247
{
234-
// Normalise sync and async exceptions into a Task
248+
// Normalize sync and async exceptions into a Task
235249
Task setParametersAsyncTask;
236250
try
237251
{
252+
var stateStartTimestamp = RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && _renderer.RenderingMetrics.IsStateDurationEnabled ? Stopwatch.GetTimestamp() : 0;
253+
238254
setParametersAsyncTask = Component.SetParametersAsync(directAndCascadingParameters);
255+
256+
// collect metrics
257+
if (RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && _renderer.RenderingMetrics.IsStateDurationEnabled)
258+
{
259+
_renderer.RenderingMetrics.ParametersDurationSync(stateStartTimestamp, _componentTypeName);
260+
_ = _renderer.RenderingMetrics.CaptureParametersDurationAsync(setParametersAsyncTask, stateStartTimestamp, _componentTypeName);
261+
}
262+
if (RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && _renderer.RenderingMetrics.IsStateExceptionEnabled)
263+
{
264+
_ = _renderer.RenderingMetrics.CapturePropertiesFailedAsync(setParametersAsyncTask, _componentTypeName);
265+
}
239266
}
240267
catch (Exception ex)
241268
{
269+
if (RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && _renderer.RenderingMetrics.IsStateExceptionEnabled)
270+
{
271+
_renderer.RenderingMetrics.PropertiesFailed(ex.GetType().FullName, _componentTypeName);
272+
}
273+
242274
setParametersAsyncTask = Task.FromException(ex);
243275
}
244276

0 commit comments

Comments
 (0)