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