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 ;
@@ -25,12 +24,14 @@ namespace Microsoft.AspNetCore.Components.RenderTree;
25
24
// dispatching events to them, and notifying when the user interface is being updated.
26
25
public abstract partial class Renderer : IDisposable , IAsyncDisposable
27
26
{
27
+ internal static readonly Task CanceledRenderTask = Task . FromCanceled ( new CancellationToken ( canceled : true ) ) ;
28
+
28
29
private readonly object _lockObject = new ( ) ;
29
30
private readonly IServiceProvider _serviceProvider ;
30
31
private readonly Dictionary < int , ComponentState > _componentStateById = new Dictionary < int , ComponentState > ( ) ;
31
32
private readonly Dictionary < IComponent , ComponentState > _componentStateByComponent = new Dictionary < IComponent , ComponentState > ( ) ;
32
33
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 ( ) ;
34
35
private readonly Dictionary < ulong , ulong > _eventHandlerIdReplacements = new Dictionary < ulong , ulong > ( ) ;
35
36
private readonly ILogger _logger ;
36
37
private readonly ComponentFactory _componentFactory ;
@@ -92,16 +93,18 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory,
92
93
// logger name in here as a string literal.
93
94
_logger = loggerFactory . CreateLogger ( "Microsoft.AspNetCore.Components.RenderTree.Renderer" ) ;
94
95
_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
+ }
99
100
100
101
ServiceProviderCascadingValueSuppliers = serviceProvider . GetService < ICascadingValueSupplier > ( ) is null
101
102
? Array . Empty < ICascadingValueSupplier > ( )
102
103
: serviceProvider . GetServices < ICascadingValueSupplier > ( ) . ToArray ( ) ;
103
104
}
104
105
106
+ internal RenderingMetrics ? RenderingMetrics => RenderingMetrics . IsMetricsSupported ? _renderingMetrics : null ;
107
+
105
108
internal ICascadingValueSupplier [ ] ServiceProviderCascadingValueSuppliers { get ; }
106
109
107
110
internal HotReloadManager HotReloadManager { get ; set ; } = HotReloadManager . Default ;
@@ -437,12 +440,14 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie
437
440
{
438
441
Dispatcher . AssertAccess ( ) ;
439
442
443
+ var eventStartTimestamp = RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsEventDurationEnabled ? Stopwatch . GetTimestamp ( ) : 0 ;
444
+
440
445
if ( waitForQuiescence )
441
446
{
442
447
_pendingTasks ??= new ( ) ;
443
448
}
444
449
445
- var ( renderedByComponentId , callback ) = GetRequiredEventBindingEntry ( eventHandlerId ) ;
450
+ var ( renderedByComponentId , callback , attributeName ) = GetRequiredEventBindingEntry ( eventHandlerId ) ;
446
451
447
452
// If this event attribute was rendered by a component that's since been disposed, don't dispatch the event at all.
448
453
// 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
484
489
_isBatchInProgress = true ;
485
490
486
491
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
+ }
487
504
}
488
505
catch ( Exception e )
489
506
{
507
+ if ( RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsEventExceptionEnabled )
508
+ {
509
+ RenderingMetrics . EventFailed ( e . GetType ( ) . FullName , callback , attributeName ) ;
510
+ }
490
511
HandleExceptionViaErrorBoundary ( e , receiverComponentState ) ;
491
512
return Task . CompletedTask ;
492
513
}
@@ -497,6 +518,10 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo? fie
497
518
// Since the task has yielded - process any queued rendering work before we return control
498
519
// to the caller.
499
520
ProcessPendingRender ( ) ;
521
+
522
+ //callback.Receiver
523
+ //callback.Delegate.Method.
524
+
500
525
}
501
526
502
527
// 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
638
663
//
639
664
// When that happens we intentionally box the EventCallback because we need to hold on to
640
665
// the receiver.
641
- _eventBindings . Add ( id , ( renderedByComponentId , callback ) ) ;
666
+ _eventBindings . Add ( id , ( renderedByComponentId , callback , frame . AttributeName ) ) ;
642
667
}
643
668
else if ( frame . AttributeValueField is MulticastDelegate @delegate )
644
669
{
645
670
// This is the common case for a delegate, where the receiver of the event
646
671
// is the same as delegate.Target. In this case since the receiver is implicit we can
647
672
// avoid boxing the EventCallback object and just re-hydrate it on the other side of the
648
673
// 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 ) ) ;
650
675
}
651
676
652
677
// 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
696
721
_eventHandlerIdReplacements . Add ( oldEventHandlerId , newEventHandlerId ) ;
697
722
}
698
723
699
- private ( int RenderedByComponentId , EventCallback Callback ) GetRequiredEventBindingEntry ( ulong eventHandlerId )
724
+ private ( int RenderedByComponentId , EventCallback Callback , string ? attributeName ) GetRequiredEventBindingEntry ( ulong eventHandlerId )
700
725
{
701
726
if ( ! _eventBindings . TryGetValue ( eventHandlerId , out var entry ) )
702
727
{
@@ -770,6 +795,7 @@ private void ProcessRenderQueue()
770
795
771
796
_isBatchInProgress = true ;
772
797
var updateDisplayTask = Task . CompletedTask ;
798
+ var batchStartTimestamp = RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsBatchDurationEnabled ? Stopwatch . GetTimestamp ( ) : 0 ;
773
799
774
800
try
775
801
{
@@ -801,9 +827,23 @@ private void ProcessRenderQueue()
801
827
// Fire off the execution of OnAfterRenderAsync, but don't wait for it
802
828
// if there is async work to be done.
803
829
_ = 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
+ }
804
839
}
805
840
catch ( Exception e )
806
841
{
842
+ if ( RenderingMetrics . IsMetricsSupported && RenderingMetrics != null && RenderingMetrics . IsBatchExceptionEnabled )
843
+ {
844
+ _renderingMetrics . BatchFailed ( e . GetType ( ) . Name ) ;
845
+ }
846
+
807
847
// Ensure we catch errors while running the render functions of the components.
808
848
HandleException ( e ) ;
809
849
return ;
@@ -947,15 +987,13 @@ private void RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
947
987
{
948
988
var componentState = renderQueueEntry . ComponentState ;
949
989
Log . RenderingComponent ( _logger , componentState ) ;
950
- var startTime = ( _renderingMetrics != null && _renderingMetrics . IsDurationEnabled ( ) ) ? Stopwatch . GetTimestamp ( ) : 0 ;
951
- _renderingMetrics ? . RenderStart ( componentState . Component . GetType ( ) . FullName ) ;
990
+
952
991
componentState . RenderIntoBatch ( _batchBuilder , renderQueueEntry . RenderFragment , out var renderFragmentException ) ;
953
992
if ( renderFragmentException != null )
954
993
{
955
994
// If this returns, the error was handled by an error boundary. Otherwise it throws.
956
995
HandleExceptionViaErrorBoundary ( renderFragmentException , componentState ) ;
957
996
}
958
- _renderingMetrics ? . RenderEnd ( componentState . Component . GetType ( ) . FullName , renderFragmentException , startTime , Stopwatch . GetTimestamp ( ) ) ;
959
997
960
998
// Process disposal queue now in case it causes further component renders to be enqueued
961
999
ProcessDisposalQueueInExistingBatch ( ) ;
0 commit comments