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 ;
@@ -30,7 +29,7 @@ public abstract partial class Renderer : IDisposable, IAsyncDisposable
3029 private readonly Dictionary < int , ComponentState > _componentStateById = new Dictionary < int , ComponentState > ( ) ;
3130 private readonly Dictionary < IComponent , ComponentState > _componentStateByComponent = new Dictionary < IComponent , ComponentState > ( ) ;
3231 private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder ( ) ;
33- private readonly Dictionary < ulong , ( int RenderedByComponentId , EventCallback Callback ) > _eventBindings = new ( ) ;
32+ private readonly Dictionary < ulong , ( int RenderedByComponentId , EventCallback Callback , string ? attributeName ) > _eventBindings = new ( ) ;
3433 private readonly Dictionary < ulong , ulong > _eventHandlerIdReplacements = new Dictionary < ulong , ulong > ( ) ;
3534 private readonly ILogger _logger ;
3635 private readonly ComponentFactory _componentFactory ;
@@ -91,17 +90,18 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory,
9190 // logger name in here as a string literal.
9291 _logger = loggerFactory . CreateLogger ( "Microsoft.AspNetCore.Components.RenderTree.Renderer" ) ;
9392 _componentFactory = new ComponentFactory ( componentActivator , this ) ;
94-
95- // TODO register RenderingMetrics as singleton in DI
96- var meterFactory = serviceProvider . GetService < IMeterFactory > ( ) ;
97- Debug . Assert ( meterFactory != null , "IMeterFactory should be registered in DI" ) ;
98- _renderingMetrics = new RenderingMetrics ( meterFactory ) ;
93+ if ( RenderingMetrics . IsMetricsSupported )
94+ {
95+ _renderingMetrics = serviceProvider . GetService < RenderingMetrics > ( ) ;
96+ }
9997
10098 ServiceProviderCascadingValueSuppliers = serviceProvider . GetService < ICascadingValueSupplier > ( ) is null
10199 ? Array . Empty < ICascadingValueSupplier > ( )
102100 : serviceProvider . GetServices < ICascadingValueSupplier > ( ) . ToArray ( ) ;
103101 }
104102
103+ internal RenderingMetrics ? RenderingMetrics => RenderingMetrics . IsMetricsSupported ? _renderingMetrics : null ;
104+
105105 internal ICascadingValueSupplier [ ] ServiceProviderCascadingValueSuppliers { get ; }
106106
107107 internal HotReloadManager HotReloadManager { get ; set ; } = HotReloadManager . Default ;
@@ -437,12 +437,14 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie
437437 {
438438 Dispatcher . AssertAccess ( ) ;
439439
440+ var eventStartTimestamp = RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsEventDurationEnabled ? Stopwatch . GetTimestamp ( ) : 0 ;
441+
440442 if ( waitForQuiescence )
441443 {
442444 _pendingTasks ??= new ( ) ;
443445 }
444446
445- var ( renderedByComponentId , callback ) = GetRequiredEventBindingEntry ( eventHandlerId ) ;
447+ var ( renderedByComponentId , callback , attributeName ) = GetRequiredEventBindingEntry ( eventHandlerId ) ;
446448
447449 // If this event attribute was rendered by a component that's since been disposed, don't dispatch the event at all.
448450 // This can occur because event handler disposal is deferred, so event handler IDs can outlive their components.
@@ -484,9 +486,38 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie
484486 _isBatchInProgress = true ;
485487
486488 task = callback . InvokeAsync ( eventArgs ) ;
489+
490+ // collect metrics
491+ if ( RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsEventDurationEnabled )
492+ {
493+ var receiverName = ( callback . Receiver ? . GetType ( ) ?? callback . Delegate . Target ? . GetType ( ) ) ? . FullName ;
494+ RenderingMetrics . EventSyncEnd ( eventStartTimestamp , Stopwatch . GetTimestamp ( ) , receiverName , attributeName ) ;
495+ task . ContinueWith ( t =>
496+ {
497+ if ( ! t . IsFaulted )
498+ {
499+ RenderingMetrics . EventAsyncEnd ( eventStartTimestamp , Stopwatch . GetTimestamp ( ) , receiverName , attributeName ) ;
500+ }
501+ } , CancellationToken . None , TaskContinuationOptions . ExecuteSynchronously , TaskScheduler . Default ) ;
502+ }
503+ if ( RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsEventExceptionEnabled )
504+ {
505+ task . ContinueWith ( t =>
506+ {
507+ if ( t . IsFaulted )
508+ {
509+ var receiverName = ( callback . Receiver ? . GetType ( ) ?? callback . Delegate . Target ? . GetType ( ) ) ? . FullName ;
510+ RenderingMetrics . EventFailed ( t . Exception . GetType ( ) . FullName , receiverName , attributeName ) ;
511+ }
512+ } , CancellationToken . None , TaskContinuationOptions . ExecuteSynchronously , TaskScheduler . Default ) ;
513+ }
487514 }
488515 catch ( Exception e )
489516 {
517+ if ( RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsEventExceptionEnabled )
518+ {
519+ RenderingMetrics . EventFailed ( e . GetType ( ) . FullName , Component . GetType ( ) . FullName , attributeName ) ;
520+ }
490521 HandleExceptionViaErrorBoundary ( e , receiverComponentState ) ;
491522 return Task . CompletedTask ;
492523 }
@@ -497,6 +528,10 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie
497528 // Since the task has yielded - process any queued rendering work before we return control
498529 // to the caller.
499530 ProcessPendingRender ( ) ;
531+
532+ //callback.Receiver
533+ //callback.Delegate.Method.
534+
500535 }
501536
502537 // Task completed synchronously or is still running. We already processed all of the rendering
@@ -638,15 +673,15 @@ internal void AssignEventHandlerId(int renderedByComponentId, ref RenderTreeFram
638673 //
639674 // When that happens we intentionally box the EventCallback because we need to hold on to
640675 // the receiver.
641- _eventBindings . Add ( id , ( renderedByComponentId , callback ) ) ;
676+ _eventBindings . Add ( id , ( renderedByComponentId , callback , frame . AttributeName ) ) ;
642677 }
643678 else if ( frame . AttributeValueField is MulticastDelegate @delegate )
644679 {
645680 // This is the common case for a delegate, where the receiver of the event
646681 // is the same as delegate.Target. In this case since the receiver is implicit we can
647682 // avoid boxing the EventCallback object and just re-hydrate it on the other side of the
648683 // render tree.
649- _eventBindings . Add ( id , ( renderedByComponentId , new EventCallback ( @delegate . Target as IHandleEvent , @delegate ) ) ) ;
684+ _eventBindings . Add ( id , ( renderedByComponentId , new EventCallback ( @delegate . Target as IHandleEvent , @delegate ) , frame . AttributeName ) ) ;
650685 }
651686
652687 // NOTE: we do not to handle EventCallback<T> here. EventCallback<T> is only used when passing
@@ -690,7 +725,7 @@ internal void TrackReplacedEventHandlerId(ulong oldEventHandlerId, ulong newEven
690725 _eventHandlerIdReplacements . Add ( oldEventHandlerId , newEventHandlerId ) ;
691726 }
692727
693- private ( int RenderedByComponentId , EventCallback Callback ) GetRequiredEventBindingEntry ( ulong eventHandlerId )
728+ private ( int RenderedByComponentId , EventCallback Callback , string ? attributeName ) GetRequiredEventBindingEntry ( ulong eventHandlerId )
694729 {
695730 if ( ! _eventBindings . TryGetValue ( eventHandlerId , out var entry ) )
696731 {
@@ -756,6 +791,7 @@ private void ProcessRenderQueue()
756791
757792 _isBatchInProgress = true ;
758793 var updateDisplayTask = Task . CompletedTask ;
794+ var batchStartTimestamp = RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsBatchDurationEnabled ? Stopwatch . GetTimestamp ( ) : 0 ;
759795
760796 try
761797 {
@@ -787,6 +823,11 @@ private void ProcessRenderQueue()
787823 // Fire off the execution of OnAfterRenderAsync, but don't wait for it
788824 // if there is async work to be done.
789825 _ = InvokeRenderCompletedCalls ( batch . UpdatedComponents , updateDisplayTask ) ;
826+
827+ if ( RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsBatchDurationEnabled )
828+ {
829+ _renderingMetrics . BatchEnd ( batchStartTimestamp , Stopwatch . GetTimestamp ( ) , batch . UpdatedComponents . Count ) ;
830+ }
790831 }
791832 catch ( Exception e )
792833 {
@@ -815,6 +856,10 @@ private Task InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updatedCompon
815856 {
816857 if ( updateDisplayTask . IsCanceled )
817858 {
859+ if ( RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsBatchExceptionEnabled )
860+ {
861+ _renderingMetrics . BatchFailed ( typeof ( TaskCanceledException ) . FullName ) ;
862+ }
818863 // The display update was canceled.
819864 // This can be due to a timeout on the components server-side case, or the renderer being disposed.
820865
@@ -825,6 +870,11 @@ private Task InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updatedCompon
825870 }
826871 if ( updateDisplayTask . IsFaulted )
827872 {
873+ if ( RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsBatchExceptionEnabled )
874+ {
875+ _renderingMetrics . BatchFailed ( updateDisplayTask . Exception . GetType ( ) . FullName ) ;
876+ }
877+
828878 // The display update failed so we don't care any more about running on render completed
829879 // fallbacks as the entire rendering process is going to be torn down.
830880 HandleException ( updateDisplayTask . Exception ) ;
@@ -933,15 +983,13 @@ private void RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
933983 {
934984 var componentState = renderQueueEntry . ComponentState ;
935985 Log . RenderingComponent ( _logger , componentState ) ;
936- var startTime = ( _renderingMetrics != null && _renderingMetrics . IsDurationEnabled ( ) ) ? Stopwatch . GetTimestamp ( ) : 0 ;
937- _renderingMetrics ? . RenderStart ( componentState . Component . GetType ( ) . FullName ) ;
986+
938987 componentState . RenderIntoBatch ( _batchBuilder , renderQueueEntry . RenderFragment , out var renderFragmentException ) ;
939988 if ( renderFragmentException != null )
940989 {
941990 // If this returns, the error was handled by an error boundary. Otherwise it throws.
942991 HandleExceptionViaErrorBoundary ( renderFragmentException , componentState ) ;
943992 }
944- _renderingMetrics ? . RenderEnd ( componentState . Component . GetType ( ) . FullName , renderFragmentException , startTime , Stopwatch . GetTimestamp ( ) ) ;
945993
946994 // Process disposal queue now in case it causes further component renders to be enqueued
947995 ProcessDisposalQueueInExistingBatch ( ) ;
0 commit comments