Skip to content

Commit 328a584

Browse files
committed
- DI feedback
- new set of rendering metrics - draft of trimming for WASM
1 parent e2984d9 commit 328a584

File tree

9 files changed

+565
-151
lines changed

9 files changed

+565
-151
lines changed

src/Components/Components/src/PublicAPI.Unshipped.txt

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Components.Infrastructure.RenderingMetricsServiceCollectionExtensions
23
Microsoft.AspNetCore.Components.NavigationManager.OnNotFound -> System.EventHandler<Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs!>!
34
Microsoft.AspNetCore.Components.NavigationManager.NotFound() -> void
45
Microsoft.AspNetCore.Components.Routing.NotFoundEventArgs
@@ -10,4 +11,5 @@ Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttri
1011
Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttribute.SupplyParameterFromPersistentComponentStateAttribute() -> void
1112
Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions
1213
static Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration<TService>(Microsoft.Extensions.DependencyInjection.IServiceCollection! services, Microsoft.AspNetCore.Components.IComponentRenderMode! componentRenderMode) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
14+
static Microsoft.AspNetCore.Components.Infrastructure.RenderingMetricsServiceCollectionExtensions.AddRenderingMetrics(Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
1315
static Microsoft.Extensions.DependencyInjection.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Components.Rendering;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.DependencyInjection.Extensions;
7+
8+
namespace Microsoft.AspNetCore.Components.Infrastructure;
9+
10+
/// <summary>
11+
/// Infrastructure APIs for registering diagnostic metrics.
12+
/// </summary>
13+
public static class RenderingMetricsServiceCollectionExtensions
14+
{
15+
/// <summary>
16+
/// Registers component rendering metrics
17+
/// </summary>
18+
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
19+
/// <returns>The <see cref="IServiceCollection"/>.</returns>
20+
public static IServiceCollection AddRenderingMetrics(
21+
IServiceCollection services)
22+
{
23+
if (RenderingMetrics.IsMetricsSupported)
24+
{
25+
services.AddMetrics();
26+
services.TryAddSingleton<RenderingMetrics>();
27+
}
28+
29+
return services;
30+
}
31+
}

src/Components/Components/src/RenderTree/Renderer.cs

+62-14
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

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

src/Components/Components/src/Rendering/ComponentState.cs

+53-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class ComponentState : IAsyncDisposable
2323
private RenderTreeBuilder _nextRenderTree;
2424
private ArrayBuilder<RenderTreeFrame>? _latestDirectParametersSnapshot; // Lazily instantiated
2525
private bool _componentWasDisposed;
26+
private readonly string? _componentTypeName;
2627

2728
/// <summary>
2829
/// Constructs an instance of <see cref="ComponentState"/>.
@@ -51,6 +52,11 @@ public ComponentState(Renderer renderer, int componentId, IComponent component,
5152
_hasCascadingParameters = true;
5253
_hasAnyCascadingParameterSubscriptions = AddCascadingParameterSubscriptions();
5354
}
55+
56+
if (RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && (_renderer.RenderingMetrics.IsDiffDurationEnabled || _renderer.RenderingMetrics.IsStateDurationEnabled || _renderer.RenderingMetrics.IsStateExceptionEnabled))
57+
{
58+
_componentTypeName = component.GetType().FullName;
59+
}
5460
}
5561

5662
private static ComponentState? GetSectionOutletLogicalParent(Renderer renderer, SectionOutlet sectionOutlet)
@@ -102,6 +108,7 @@ internal void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment re
102108

103109
_nextRenderTree.Clear();
104110

111+
var diffStartTimestamp = RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && _renderer.RenderingMetrics.IsDiffDurationEnabled ? Stopwatch.GetTimestamp() : 0;
105112
try
106113
{
107114
renderFragment(_nextRenderTree);
@@ -118,6 +125,8 @@ internal void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment re
118125
// We don't want to make errors from this be recoverable, because there's no legitimate reason for them to happen
119126
_nextRenderTree.AssertTreeIsValid(Component);
120127

128+
var startCount = batchBuilder.EditsBuffer.Count;
129+
121130
// Swap the old and new tree builders
122131
(CurrentRenderTree, _nextRenderTree) = (_nextRenderTree, CurrentRenderTree);
123132

@@ -129,6 +138,15 @@ internal void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment re
129138
CurrentRenderTree.GetFrames());
130139
batchBuilder.UpdatedComponentDiffs.Append(diff);
131140
batchBuilder.InvalidateParameterViews();
141+
142+
if (RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && _renderer.RenderingMetrics.IsDiffDurationEnabled)
143+
{
144+
_renderer.RenderingMetrics.DiffEnd(
145+
diffStartTimestamp,
146+
Stopwatch.GetTimestamp(),
147+
_componentTypeName,
148+
batchBuilder.EditsBuffer.Count - startCount);
149+
}
132150
}
133151

134152
// Callers expect this method to always return a faulted task.
@@ -231,14 +249,48 @@ internal void NotifyCascadingValueChanged(in ParameterViewLifetime lifetime)
231249
// a consistent set to the recipient.
232250
private void SupplyCombinedParameters(ParameterView directAndCascadingParameters)
233251
{
234-
// Normalise sync and async exceptions into a Task
252+
// Normalize sync and async exceptions into a Task
235253
Task setParametersAsyncTask;
236254
try
237255
{
256+
var stateStartTimestamp = RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && _renderer.RenderingMetrics.IsStateDurationEnabled ? Stopwatch.GetTimestamp() : 0;
257+
238258
setParametersAsyncTask = Component.SetParametersAsync(directAndCascadingParameters);
259+
260+
// collect metrics
261+
if (RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && _renderer.RenderingMetrics.IsStateDurationEnabled)
262+
{
263+
_renderer.RenderingMetrics.StateSyncEnd(stateStartTimestamp, Stopwatch.GetTimestamp(), _componentTypeName);
264+
setParametersAsyncTask.ContinueWith(t =>
265+
{
266+
if (!t.IsFaulted)
267+
{
268+
_renderer.RenderingMetrics.StateAsyncEnd(stateStartTimestamp, Stopwatch.GetTimestamp(), _componentTypeName);
269+
}
270+
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
271+
}
272+
if (RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && _renderer.RenderingMetrics.IsStateExceptionEnabled)
273+
{
274+
setParametersAsyncTask.ContinueWith(t =>
275+
{
276+
if (t.IsFaulted)
277+
{
278+
_renderer.RenderingMetrics.StateFailed(t.Exception.GetType().FullName, _componentTypeName);
279+
}
280+
else if (t.IsCanceled)
281+
{
282+
_renderer.RenderingMetrics.StateFailed(typeof(TaskCanceledException).FullName, _componentTypeName);
283+
}
284+
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
285+
}
239286
}
240287
catch (Exception ex)
241288
{
289+
if (RenderingMetrics.IsMetricsSupported && _renderer.RenderingMetrics != null && _renderer.RenderingMetrics.IsStateExceptionEnabled)
290+
{
291+
_renderer.RenderingMetrics.StateFailed(ex.GetType().FullName, _componentTypeName);
292+
}
293+
242294
setParametersAsyncTask = Task.FromException(ex);
243295
}
244296

0 commit comments

Comments
 (0)