55
66using System . Diagnostics ;
77using System . Diagnostics . CodeAnalysis ;
8- using System . Diagnostics . Metrics ;
98using System . Linq ;
109using Microsoft . AspNetCore . Components . HotReload ;
1110using 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.
2625public 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 ( ) ;
0 commit comments