5
5
6
6
using System . Diagnostics ;
7
7
using System . Diagnostics . CodeAnalysis ;
8
- using System . Diagnostics . Metrics ;
9
8
using System . Linq ;
10
9
using Microsoft . AspNetCore . Components . HotReload ;
11
10
using Microsoft . AspNetCore . Components . Reflection ;
@@ -30,7 +29,7 @@ public abstract partial class Renderer : IDisposable, IAsyncDisposable
30
29
private readonly Dictionary < int , ComponentState > _componentStateById = new Dictionary < int , ComponentState > ( ) ;
31
30
private readonly Dictionary < IComponent , ComponentState > _componentStateByComponent = new Dictionary < IComponent , ComponentState > ( ) ;
32
31
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 ( ) ;
34
33
private readonly Dictionary < ulong , ulong > _eventHandlerIdReplacements = new Dictionary < ulong , ulong > ( ) ;
35
34
private readonly ILogger _logger ;
36
35
private readonly ComponentFactory _componentFactory ;
@@ -91,17 +90,18 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory,
91
90
// logger name in here as a string literal.
92
91
_logger = loggerFactory . CreateLogger ( "Microsoft.AspNetCore.Components.RenderTree.Renderer" ) ;
93
92
_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
+ }
99
97
100
98
ServiceProviderCascadingValueSuppliers = serviceProvider . GetService < ICascadingValueSupplier > ( ) is null
101
99
? Array . Empty < ICascadingValueSupplier > ( )
102
100
: serviceProvider . GetServices < ICascadingValueSupplier > ( ) . ToArray ( ) ;
103
101
}
104
102
103
+ internal RenderingMetrics ? RenderingMetrics => RenderingMetrics . IsMetricsSupported ? _renderingMetrics : null ;
104
+
105
105
internal ICascadingValueSupplier [ ] ServiceProviderCascadingValueSuppliers { get ; }
106
106
107
107
internal HotReloadManager HotReloadManager { get ; set ; } = HotReloadManager . Default ;
@@ -437,12 +437,14 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie
437
437
{
438
438
Dispatcher . AssertAccess ( ) ;
439
439
440
+ var eventStartTimestamp = RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsEventDurationEnabled ? Stopwatch . GetTimestamp ( ) : 0 ;
441
+
440
442
if ( waitForQuiescence )
441
443
{
442
444
_pendingTasks ??= new ( ) ;
443
445
}
444
446
445
- var ( renderedByComponentId , callback ) = GetRequiredEventBindingEntry ( eventHandlerId ) ;
447
+ var ( renderedByComponentId , callback , attributeName ) = GetRequiredEventBindingEntry ( eventHandlerId ) ;
446
448
447
449
// If this event attribute was rendered by a component that's since been disposed, don't dispatch the event at all.
448
450
// 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
484
486
_isBatchInProgress = true ;
485
487
486
488
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
+ }
487
514
}
488
515
catch ( Exception e )
489
516
{
517
+ if ( RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsEventExceptionEnabled )
518
+ {
519
+ RenderingMetrics . EventFailed ( e . GetType ( ) . FullName , Component . GetType ( ) . FullName , attributeName ) ;
520
+ }
490
521
HandleExceptionViaErrorBoundary ( e , receiverComponentState ) ;
491
522
return Task . CompletedTask ;
492
523
}
@@ -497,6 +528,10 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie
497
528
// Since the task has yielded - process any queued rendering work before we return control
498
529
// to the caller.
499
530
ProcessPendingRender ( ) ;
531
+
532
+ //callback.Receiver
533
+ //callback.Delegate.Method.
534
+
500
535
}
501
536
502
537
// 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
638
673
//
639
674
// When that happens we intentionally box the EventCallback because we need to hold on to
640
675
// the receiver.
641
- _eventBindings . Add ( id , ( renderedByComponentId , callback ) ) ;
676
+ _eventBindings . Add ( id , ( renderedByComponentId , callback , frame . AttributeName ) ) ;
642
677
}
643
678
else if ( frame . AttributeValueField is MulticastDelegate @delegate )
644
679
{
645
680
// This is the common case for a delegate, where the receiver of the event
646
681
// is the same as delegate.Target. In this case since the receiver is implicit we can
647
682
// avoid boxing the EventCallback object and just re-hydrate it on the other side of the
648
683
// 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 ) ) ;
650
685
}
651
686
652
687
// 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
690
725
_eventHandlerIdReplacements . Add ( oldEventHandlerId , newEventHandlerId ) ;
691
726
}
692
727
693
- private ( int RenderedByComponentId , EventCallback Callback ) GetRequiredEventBindingEntry ( ulong eventHandlerId )
728
+ private ( int RenderedByComponentId , EventCallback Callback , string ? attributeName ) GetRequiredEventBindingEntry ( ulong eventHandlerId )
694
729
{
695
730
if ( ! _eventBindings . TryGetValue ( eventHandlerId , out var entry ) )
696
731
{
@@ -756,6 +791,7 @@ private void ProcessRenderQueue()
756
791
757
792
_isBatchInProgress = true ;
758
793
var updateDisplayTask = Task . CompletedTask ;
794
+ var batchStartTimestamp = RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsBatchDurationEnabled ? Stopwatch . GetTimestamp ( ) : 0 ;
759
795
760
796
try
761
797
{
@@ -787,6 +823,11 @@ private void ProcessRenderQueue()
787
823
// Fire off the execution of OnAfterRenderAsync, but don't wait for it
788
824
// if there is async work to be done.
789
825
_ = InvokeRenderCompletedCalls ( batch . UpdatedComponents , updateDisplayTask ) ;
826
+
827
+ if ( RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsBatchDurationEnabled )
828
+ {
829
+ _renderingMetrics . BatchEnd ( batchStartTimestamp , Stopwatch . GetTimestamp ( ) , batch . UpdatedComponents . Count ) ;
830
+ }
790
831
}
791
832
catch ( Exception e )
792
833
{
@@ -815,6 +856,10 @@ private Task InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updatedCompon
815
856
{
816
857
if ( updateDisplayTask . IsCanceled )
817
858
{
859
+ if ( RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsBatchExceptionEnabled )
860
+ {
861
+ _renderingMetrics . BatchFailed ( typeof ( TaskCanceledException ) . FullName ) ;
862
+ }
818
863
// The display update was canceled.
819
864
// This can be due to a timeout on the components server-side case, or the renderer being disposed.
820
865
@@ -825,6 +870,11 @@ private Task InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updatedCompon
825
870
}
826
871
if ( updateDisplayTask . IsFaulted )
827
872
{
873
+ if ( RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsBatchExceptionEnabled )
874
+ {
875
+ _renderingMetrics . BatchFailed ( updateDisplayTask . Exception . GetType ( ) . FullName ) ;
876
+ }
877
+
828
878
// The display update failed so we don't care any more about running on render completed
829
879
// fallbacks as the entire rendering process is going to be torn down.
830
880
HandleException ( updateDisplayTask . Exception ) ;
@@ -933,15 +983,13 @@ private void RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
933
983
{
934
984
var componentState = renderQueueEntry . ComponentState ;
935
985
Log . RenderingComponent ( _logger , componentState ) ;
936
- var startTime = ( _renderingMetrics != null && _renderingMetrics . IsDurationEnabled ( ) ) ? Stopwatch . GetTimestamp ( ) : 0 ;
937
- _renderingMetrics ? . RenderStart ( componentState . Component . GetType ( ) . FullName ) ;
986
+
938
987
componentState . RenderIntoBatch ( _batchBuilder , renderQueueEntry . RenderFragment , out var renderFragmentException ) ;
939
988
if ( renderFragmentException != null )
940
989
{
941
990
// If this returns, the error was handled by an error boundary. Otherwise it throws.
942
991
HandleExceptionViaErrorBoundary ( renderFragmentException , componentState ) ;
943
992
}
944
- _renderingMetrics ? . RenderEnd ( componentState . Component . GetType ( ) . FullName , renderFragmentException , startTime , Stopwatch . GetTimestamp ( ) ) ;
945
993
946
994
// Process disposal queue now in case it causes further component renders to be enqueued
947
995
ProcessDisposalQueueInExistingBatch ( ) ;
0 commit comments