Blazor WebAssembly internal profiling infrastructure (#23510)
This commit is contained in:
parent
1688b5a30b
commit
3cbe02f2fc
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0;$(DefaultNetCoreTargetFramework)</TargetFrameworks>
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="$(ComponentsSharedSourceRoot)src\ArrayBuilder.cs" LinkBase="RenderTree" />
|
<Compile Include="$(ComponentsSharedSourceRoot)src\ArrayBuilder.cs" LinkBase="RenderTree" />
|
||||||
|
<Compile Include="$(ComponentsSharedSourceRoot)src\WebAssemblyJSInteropInternalCalls.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Profiling
|
||||||
|
{
|
||||||
|
internal abstract class ComponentsProfiling
|
||||||
|
{
|
||||||
|
// For now, this is only intended for use on Blazor WebAssembly, and will have no effect
|
||||||
|
// when running on Blazor Server. The reason for having the ComponentsProfiling abstraction
|
||||||
|
// is so that if we later have two different implementations (one for WebAssembly, one for
|
||||||
|
// Server), the execution characteristics of calling Start/End will be unchanged and historical
|
||||||
|
// perf data will still be comparable to newer data.
|
||||||
|
public static readonly ComponentsProfiling Instance;
|
||||||
|
|
||||||
|
static ComponentsProfiling()
|
||||||
|
{
|
||||||
|
Instance = RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))
|
||||||
|
? new WebAssemblyComponentsProfiling()
|
||||||
|
: (ComponentsProfiling)new NoOpComponentsProfiling();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void Start([CallerMemberName] string? name = null);
|
||||||
|
public abstract void End([CallerMemberName] string? name = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Profiling
|
||||||
|
{
|
||||||
|
internal class NoOpComponentsProfiling : ComponentsProfiling
|
||||||
|
{
|
||||||
|
public override void Start(string? name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void End(string? name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using WebAssembly.JSInterop;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Profiling
|
||||||
|
{
|
||||||
|
// Later on, we will likely want to move this into the WebAssembly package. However it needs to
|
||||||
|
// be inlined into the Components package directly until we're ready to make the underlying
|
||||||
|
// ComponentsProfile abstraction into a public API. It's possible that this API will never become
|
||||||
|
// public, or that it will be replaced by something more standard for .NET, if it's possible to
|
||||||
|
// make that work performantly on WebAssembly.
|
||||||
|
|
||||||
|
internal class WebAssemblyComponentsProfiling : ComponentsProfiling
|
||||||
|
{
|
||||||
|
static bool IsCapturing = false;
|
||||||
|
|
||||||
|
public static void SetCapturing(bool isCapturing)
|
||||||
|
{
|
||||||
|
IsCapturing = isCapturing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Start(string? name)
|
||||||
|
{
|
||||||
|
if (IsCapturing)
|
||||||
|
{
|
||||||
|
InternalCalls.InvokeJSUnmarshalled<string, object, object, object>(
|
||||||
|
out _, "_blazorProfileStart", name, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void End(string? name)
|
||||||
|
{
|
||||||
|
if (IsCapturing)
|
||||||
|
{
|
||||||
|
InternalCalls.InvokeJSUnmarshalled<string, object, object, object>(
|
||||||
|
out _, "_blazorProfileEnd", name, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Microsoft.AspNetCore.Components.Profiling;
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.RenderTree
|
namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
|
|
@ -25,6 +28,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
ArrayRange<RenderTreeFrame> oldTree,
|
ArrayRange<RenderTreeFrame> oldTree,
|
||||||
ArrayRange<RenderTreeFrame> newTree)
|
ArrayRange<RenderTreeFrame> newTree)
|
||||||
{
|
{
|
||||||
|
ComponentsProfiling.Instance.Start();
|
||||||
var editsBuffer = batchBuilder.EditsBuffer;
|
var editsBuffer = batchBuilder.EditsBuffer;
|
||||||
var editsBufferStartLength = editsBuffer.Count;
|
var editsBufferStartLength = editsBuffer.Count;
|
||||||
|
|
||||||
|
|
@ -32,7 +36,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
AppendDiffEntriesForRange(ref diffContext, 0, oldTree.Count, 0, newTree.Count);
|
AppendDiffEntriesForRange(ref diffContext, 0, oldTree.Count, 0, newTree.Count);
|
||||||
|
|
||||||
var editsSegment = editsBuffer.ToSegment(editsBufferStartLength, editsBuffer.Count);
|
var editsSegment = editsBuffer.ToSegment(editsBufferStartLength, editsBuffer.Count);
|
||||||
return new RenderTreeDiff(componentId, editsSegment);
|
var result = new RenderTreeDiff(componentId, editsSegment);
|
||||||
|
ComponentsProfiling.Instance.End();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DisposeFrames(RenderBatchBuilder batchBuilder, ArrayRange<RenderTreeFrame> frames)
|
public static void DisposeFrames(RenderBatchBuilder batchBuilder, ArrayRange<RenderTreeFrame> frames)
|
||||||
|
|
@ -43,6 +49,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
int oldStartIndex, int oldEndIndexExcl,
|
int oldStartIndex, int oldEndIndexExcl,
|
||||||
int newStartIndex, int newEndIndexExcl)
|
int newStartIndex, int newEndIndexExcl)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
// This is deliberately a very large method. Parts of it could be factored out
|
// This is deliberately a very large method. Parts of it could be factored out
|
||||||
// into other private methods, but doing so comes at a consequential perf cost,
|
// into other private methods, but doing so comes at a consequential perf cost,
|
||||||
// because it involves so much parameter passing. You can think of the code here
|
// because it involves so much parameter passing. You can think of the code here
|
||||||
|
|
@ -293,10 +300,12 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
diffContext.KeyedItemInfoDictionaryPool.Return(keyedItemInfos);
|
diffContext.KeyedItemInfoDictionaryPool.Return(keyedItemInfos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<object, KeyedItemInfo> BuildKeyToInfoLookup(DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl)
|
private static Dictionary<object, KeyedItemInfo> BuildKeyToInfoLookup(DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var result = diffContext.KeyedItemInfoDictionaryPool.Get();
|
var result = diffContext.KeyedItemInfoDictionaryPool.Get();
|
||||||
var oldTree = diffContext.OldTree;
|
var oldTree = diffContext.OldTree;
|
||||||
var newTree = diffContext.NewTree;
|
var newTree = diffContext.NewTree;
|
||||||
|
|
@ -342,6 +351,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
newStartIndex = NextSiblingIndex(frame, newStartIndex);
|
newStartIndex = NextSiblingIndex(frame, newStartIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProfilingEnd();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,6 +384,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
int oldStartIndex, int oldEndIndexExcl,
|
int oldStartIndex, int oldEndIndexExcl,
|
||||||
int newStartIndex, int newEndIndexExcl)
|
int newStartIndex, int newEndIndexExcl)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
// The overhead of the dictionary used by AppendAttributeDiffEntriesForRangeSlow is
|
// The overhead of the dictionary used by AppendAttributeDiffEntriesForRangeSlow is
|
||||||
// significant, so we want to try and do a merge-join if possible, but fall back to
|
// significant, so we want to try and do a merge-join if possible, but fall back to
|
||||||
// a hash-join if not. We'll do a merge join until we hit a case we can't handle and
|
// a hash-join if not. We'll do a merge join until we hit a case we can't handle and
|
||||||
|
|
@ -422,6 +433,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
ref diffContext,
|
ref diffContext,
|
||||||
oldStartIndex, oldEndIndexExcl,
|
oldStartIndex, oldEndIndexExcl,
|
||||||
newStartIndex, newEndIndexExcl);
|
newStartIndex, newEndIndexExcl);
|
||||||
|
ProfilingEnd();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -447,9 +459,12 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
ref diffContext,
|
ref diffContext,
|
||||||
oldStartIndex, oldEndIndexExcl,
|
oldStartIndex, oldEndIndexExcl,
|
||||||
newStartIndex, newEndIndexExcl);
|
newStartIndex, newEndIndexExcl);
|
||||||
|
ProfilingEnd();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AppendAttributeDiffEntriesForRangeSlow(
|
private static void AppendAttributeDiffEntriesForRangeSlow(
|
||||||
|
|
@ -457,6 +472,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
int oldStartIndex, int oldEndIndexExcl,
|
int oldStartIndex, int oldEndIndexExcl,
|
||||||
int newStartIndex, int newEndIndexExcl)
|
int newStartIndex, int newEndIndexExcl)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var oldTree = diffContext.OldTree;
|
var oldTree = diffContext.OldTree;
|
||||||
var newTree = diffContext.NewTree;
|
var newTree = diffContext.NewTree;
|
||||||
|
|
||||||
|
|
@ -495,6 +511,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
|
|
||||||
// We should have processed any additions at this point. Reset for the next batch.
|
// We should have processed any additions at this point. Reset for the next batch.
|
||||||
diffContext.AttributeDiffSet.Clear();
|
diffContext.AttributeDiffSet.Clear();
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateRetainedChildComponent(
|
private static void UpdateRetainedChildComponent(
|
||||||
|
|
@ -502,6 +519,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
int oldComponentIndex,
|
int oldComponentIndex,
|
||||||
int newComponentIndex)
|
int newComponentIndex)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var oldTree = diffContext.OldTree;
|
var oldTree = diffContext.OldTree;
|
||||||
var newTree = diffContext.NewTree;
|
var newTree = diffContext.NewTree;
|
||||||
ref var oldComponentFrame = ref oldTree[oldComponentIndex];
|
ref var oldComponentFrame = ref oldTree[oldComponentIndex];
|
||||||
|
|
@ -528,6 +546,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
{
|
{
|
||||||
componentState.SetDirectParameters(newParameters);
|
componentState.SetDirectParameters(newParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int NextSiblingIndex(in RenderTreeFrame frame, int frameIndex)
|
private static int NextSiblingIndex(in RenderTreeFrame frame, int frameIndex)
|
||||||
|
|
@ -550,6 +570,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
int oldFrameIndex,
|
int oldFrameIndex,
|
||||||
int newFrameIndex)
|
int newFrameIndex)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var oldTree = diffContext.OldTree;
|
var oldTree = diffContext.OldTree;
|
||||||
var newTree = diffContext.NewTree;
|
var newTree = diffContext.NewTree;
|
||||||
ref var oldFrame = ref oldTree[oldFrameIndex];
|
ref var oldFrame = ref oldTree[oldFrameIndex];
|
||||||
|
|
@ -562,6 +583,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
{
|
{
|
||||||
InsertNewFrame(ref diffContext, newFrameIndex);
|
InsertNewFrame(ref diffContext, newFrameIndex);
|
||||||
RemoveOldFrame(ref diffContext, oldFrameIndex);
|
RemoveOldFrame(ref diffContext, oldFrameIndex);
|
||||||
|
ProfilingEnd();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -687,6 +709,8 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Encountered unsupported frame type during diffing: {newTree[newFrameIndex].FrameType}");
|
throw new NotImplementedException($"Encountered unsupported frame type during diffing: {newTree[newFrameIndex].FrameType}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should only be called for attributes that have the same name. This is an
|
// This should only be called for attributes that have the same name. This is an
|
||||||
|
|
@ -696,6 +720,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
int oldFrameIndex,
|
int oldFrameIndex,
|
||||||
int newFrameIndex)
|
int newFrameIndex)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var oldTree = diffContext.OldTree;
|
var oldTree = diffContext.OldTree;
|
||||||
var newTree = diffContext.NewTree;
|
var newTree = diffContext.NewTree;
|
||||||
ref var oldFrame = ref oldTree[oldFrameIndex];
|
ref var oldFrame = ref oldTree[oldFrameIndex];
|
||||||
|
|
@ -724,10 +749,13 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
// since it was unchanged.
|
// since it was unchanged.
|
||||||
newFrame = oldFrame;
|
newFrame = oldFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InsertNewFrame(ref DiffContext diffContext, int newFrameIndex)
|
private static void InsertNewFrame(ref DiffContext diffContext, int newFrameIndex)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var newTree = diffContext.NewTree;
|
var newTree = diffContext.NewTree;
|
||||||
ref var newFrame = ref newTree[newFrameIndex];
|
ref var newFrame = ref newTree[newFrameIndex];
|
||||||
switch (newFrame.FrameType)
|
switch (newFrame.FrameType)
|
||||||
|
|
@ -780,10 +808,12 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unexpected frame type during {nameof(InsertNewFrame)}: {newFrame.FrameType}");
|
throw new NotImplementedException($"Unexpected frame type during {nameof(InsertNewFrame)}: {newFrame.FrameType}");
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RemoveOldFrame(ref DiffContext diffContext, int oldFrameIndex)
|
private static void RemoveOldFrame(ref DiffContext diffContext, int oldFrameIndex)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var oldTree = diffContext.OldTree;
|
var oldTree = diffContext.OldTree;
|
||||||
ref var oldFrame = ref oldTree[oldFrameIndex];
|
ref var oldFrame = ref oldTree[oldFrameIndex];
|
||||||
switch (oldFrame.FrameType)
|
switch (oldFrame.FrameType)
|
||||||
|
|
@ -825,6 +855,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unexpected frame type during {nameof(RemoveOldFrame)}: {oldFrame.FrameType}");
|
throw new NotImplementedException($"Unexpected frame type during {nameof(RemoveOldFrame)}: {oldFrame.FrameType}");
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetAttributesEndIndexExclusive(RenderTreeFrame[] tree, int rootIndex)
|
private static int GetAttributesEndIndexExclusive(RenderTreeFrame[] tree, int rootIndex)
|
||||||
|
|
@ -858,6 +889,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
|
|
||||||
private static void InitializeNewSubtree(ref DiffContext diffContext, int frameIndex)
|
private static void InitializeNewSubtree(ref DiffContext diffContext, int frameIndex)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var frames = diffContext.NewTree;
|
var frames = diffContext.NewTree;
|
||||||
var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength;
|
var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength;
|
||||||
for (var i = frameIndex; i < endIndexExcl; i++)
|
for (var i = frameIndex; i < endIndexExcl; i++)
|
||||||
|
|
@ -879,10 +911,12 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InitializeNewComponentFrame(ref DiffContext diffContext, int frameIndex)
|
private static void InitializeNewComponentFrame(ref DiffContext diffContext, int frameIndex)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var frames = diffContext.NewTree;
|
var frames = diffContext.NewTree;
|
||||||
ref var frame = ref frames[frameIndex];
|
ref var frame = ref frames[frameIndex];
|
||||||
|
|
||||||
|
|
@ -899,6 +933,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
var initialParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder);
|
var initialParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder);
|
||||||
var initialParameters = new ParameterView(initialParametersLifetime, frames, frameIndex);
|
var initialParameters = new ParameterView(initialParametersLifetime, frames, frameIndex);
|
||||||
childComponentState.SetDirectParameters(initialParameters);
|
childComponentState.SetDirectParameters(initialParameters);
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InitializeNewAttributeFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
|
private static void InitializeNewAttributeFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
|
||||||
|
|
@ -943,6 +978,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
|
|
||||||
private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
|
private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
for (var i = startIndex; i < endIndexExcl; i++)
|
for (var i = startIndex; i < endIndexExcl; i++)
|
||||||
{
|
{
|
||||||
ref var frame = ref frames[i];
|
ref var frame = ref frames[i];
|
||||||
|
|
@ -955,6 +991,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
batchBuilder.DisposedEventHandlerIds.Append(frame.AttributeEventHandlerId);
|
batchBuilder.DisposedEventHandlerIds.Append(frame.AttributeEventHandlerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -996,5 +1033,18 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
SiblingIndex = 0;
|
SiblingIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Having too many calls to ComponentsProfiling.Instance.Start/End has a measurable perf impact
|
||||||
|
// even when capturing is disabled. So, to enable detailed profiling for this class, define the
|
||||||
|
// Profile_RenderTreeDiffBuilder compiler symbol, otherwise the calls are compiled out entirely.
|
||||||
|
// Enabling detailed profiling adds about 5% to rendering benchmark times.
|
||||||
|
|
||||||
|
[Conditional("Profile_RenderTreeDiffBuilder")]
|
||||||
|
private static void ProfilingStart([CallerMemberName] string? name = null)
|
||||||
|
=> ComponentsProfiling.Instance.Start(name);
|
||||||
|
|
||||||
|
[Conditional("Profile_RenderTreeDiffBuilder")]
|
||||||
|
private static void ProfilingEnd([CallerMemberName] string? name = null)
|
||||||
|
=> ComponentsProfiling.Instance.End(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components.Profiling;
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
@ -220,6 +221,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs)
|
public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs)
|
||||||
{
|
{
|
||||||
|
ComponentsProfiling.Instance.Start();
|
||||||
Dispatcher.AssertAccess();
|
Dispatcher.AssertAccess();
|
||||||
|
|
||||||
if (!_eventBindings.TryGetValue(eventHandlerId, out var callback))
|
if (!_eventBindings.TryGetValue(eventHandlerId, out var callback))
|
||||||
|
|
@ -247,6 +249,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
HandleException(e);
|
HandleException(e);
|
||||||
|
ComponentsProfiling.Instance.End();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
@ -260,7 +263,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
|
|
||||||
// Task completed synchronously or is still running. We already processed all of the rendering
|
// Task completed synchronously or is still running. We already processed all of the rendering
|
||||||
// work that was queued so let our error handler deal with it.
|
// work that was queued so let our error handler deal with it.
|
||||||
return GetErrorHandledTask(task);
|
var result = GetErrorHandledTask(task);
|
||||||
|
ComponentsProfiling.Instance.End();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void InstantiateChildComponentOnFrame(ref RenderTreeFrame frame, int parentComponentId)
|
internal void InstantiateChildComponentOnFrame(ref RenderTreeFrame frame, int parentComponentId)
|
||||||
|
|
@ -410,6 +415,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
|
|
||||||
private void ProcessRenderQueue()
|
private void ProcessRenderQueue()
|
||||||
{
|
{
|
||||||
|
ComponentsProfiling.Instance.Start();
|
||||||
Dispatcher.AssertAccess();
|
Dispatcher.AssertAccess();
|
||||||
|
|
||||||
if (_isBatchInProgress)
|
if (_isBatchInProgress)
|
||||||
|
|
@ -424,6 +430,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
{
|
{
|
||||||
if (_batchBuilder.ComponentRenderQueue.Count == 0)
|
if (_batchBuilder.ComponentRenderQueue.Count == 0)
|
||||||
{
|
{
|
||||||
|
ComponentsProfiling.Instance.End();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,7 +442,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
}
|
}
|
||||||
|
|
||||||
var batch = _batchBuilder.ToBatch();
|
var batch = _batchBuilder.ToBatch();
|
||||||
|
ComponentsProfiling.Instance.Start(nameof(UpdateDisplayAsync));
|
||||||
updateDisplayTask = UpdateDisplayAsync(batch);
|
updateDisplayTask = UpdateDisplayAsync(batch);
|
||||||
|
ComponentsProfiling.Instance.End(nameof(UpdateDisplayAsync));
|
||||||
|
|
||||||
// Fire off the execution of OnAfterRenderAsync, but don't wait for it
|
// Fire off the execution of OnAfterRenderAsync, but don't wait for it
|
||||||
// if there is async work to be done.
|
// if there is async work to be done.
|
||||||
|
|
@ -445,6 +454,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
{
|
{
|
||||||
// Ensure we catch errors while running the render functions of the components.
|
// Ensure we catch errors while running the render functions of the components.
|
||||||
HandleException(e);
|
HandleException(e);
|
||||||
|
ComponentsProfiling.Instance.End();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
@ -462,6 +472,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
||||||
{
|
{
|
||||||
ProcessRenderQueue();
|
ProcessRenderQueue();
|
||||||
}
|
}
|
||||||
|
ComponentsProfiling.Instance.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updatedComponents, Task updateDisplayTask)
|
private Task InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updatedComponents, Task updateDisplayTask)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Components.Profiling;
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Rendering
|
namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
@ -56,6 +57,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
public void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)
|
public void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)
|
||||||
{
|
{
|
||||||
|
ComponentsProfiling.Instance.Start();
|
||||||
// A component might be in the render queue already before getting disposed by an
|
// A component might be in the render queue already before getting disposed by an
|
||||||
// earlier entry in the render queue. In that case, rendering is a no-op.
|
// earlier entry in the render queue. In that case, rendering is a no-op.
|
||||||
if (_componentWasDisposed)
|
if (_componentWasDisposed)
|
||||||
|
|
@ -67,7 +69,9 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
(CurrentRenderTree, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, CurrentRenderTree);
|
(CurrentRenderTree, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, CurrentRenderTree);
|
||||||
|
|
||||||
CurrentRenderTree.Clear();
|
CurrentRenderTree.Clear();
|
||||||
|
ComponentsProfiling.Instance.Start("BuildRenderTree");
|
||||||
renderFragment(CurrentRenderTree);
|
renderFragment(CurrentRenderTree);
|
||||||
|
ComponentsProfiling.Instance.End("BuildRenderTree");
|
||||||
|
|
||||||
var diff = RenderTreeDiffBuilder.ComputeDiff(
|
var diff = RenderTreeDiffBuilder.ComputeDiff(
|
||||||
_renderer,
|
_renderer,
|
||||||
|
|
@ -77,6 +81,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
CurrentRenderTree.GetFrames());
|
CurrentRenderTree.GetFrames());
|
||||||
batchBuilder.UpdatedComponentDiffs.Append(diff);
|
batchBuilder.UpdatedComponentDiffs.Append(diff);
|
||||||
batchBuilder.InvalidateParameterViews();
|
batchBuilder.InvalidateParameterViews();
|
||||||
|
ComponentsProfiling.Instance.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryDisposeInBatch(RenderBatchBuilder batchBuilder, [NotNullWhen(false)] out Exception? exception)
|
public bool TryDisposeInBatch(RenderBatchBuilder batchBuilder, [NotNullWhen(false)] out Exception? exception)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Microsoft.AspNetCore.Components.Profiling;
|
||||||
using Microsoft.AspNetCore.Components.RenderTree;
|
using Microsoft.AspNetCore.Components.RenderTree;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Rendering
|
namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
@ -43,6 +45,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="elementName">A value representing the type of the element.</param>
|
/// <param name="elementName">A value representing the type of the element.</param>
|
||||||
public void OpenElement(int sequence, string elementName)
|
public void OpenElement(int sequence, string elementName)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
// We are entering a new scope, since we track the "duplicate attributes" per
|
// We are entering a new scope, since we track the "duplicate attributes" per
|
||||||
// element/component we might need to clean them up now.
|
// element/component we might need to clean them up now.
|
||||||
if (_hasSeenAddMultipleAttributes)
|
if (_hasSeenAddMultipleAttributes)
|
||||||
|
|
@ -53,6 +56,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
_openElementIndices.Push(_entries.Count);
|
_openElementIndices.Push(_entries.Count);
|
||||||
Append(RenderTreeFrame.Element(sequence, elementName));
|
Append(RenderTreeFrame.Element(sequence, elementName));
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -61,6 +65,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CloseElement()
|
public void CloseElement()
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var indexOfEntryBeingClosed = _openElementIndices.Pop();
|
var indexOfEntryBeingClosed = _openElementIndices.Pop();
|
||||||
|
|
||||||
// We might be closing an element with only attributes, run the duplicate cleanup pass
|
// We might be closing an element with only attributes, run the duplicate cleanup pass
|
||||||
|
|
@ -72,6 +77,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
|
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
|
||||||
entry = entry.WithElementSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
|
entry = entry.WithElementSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -80,7 +86,11 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||||
/// <param name="markupContent">Content for the new markup frame.</param>
|
/// <param name="markupContent">Content for the new markup frame.</param>
|
||||||
public void AddMarkupContent(int sequence, string? markupContent)
|
public void AddMarkupContent(int sequence, string? markupContent)
|
||||||
=> Append(RenderTreeFrame.Markup(sequence, markupContent ?? string.Empty));
|
{
|
||||||
|
ProfilingStart();
|
||||||
|
Append(RenderTreeFrame.Markup(sequence, markupContent ?? string.Empty));
|
||||||
|
ProfilingEnd();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appends a frame representing text content.
|
/// Appends a frame representing text content.
|
||||||
|
|
@ -88,7 +98,11 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||||
/// <param name="textContent">Content for the new text frame.</param>
|
/// <param name="textContent">Content for the new text frame.</param>
|
||||||
public void AddContent(int sequence, string? textContent)
|
public void AddContent(int sequence, string? textContent)
|
||||||
=> Append(RenderTreeFrame.Text(sequence, textContent ?? string.Empty));
|
{
|
||||||
|
ProfilingStart();
|
||||||
|
Append(RenderTreeFrame.Text(sequence, textContent ?? string.Empty));
|
||||||
|
ProfilingEnd();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appends frames representing an arbitrary fragment of content.
|
/// Appends frames representing an arbitrary fragment of content.
|
||||||
|
|
@ -97,6 +111,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="fragment">Content to append.</param>
|
/// <param name="fragment">Content to append.</param>
|
||||||
public void AddContent(int sequence, RenderFragment? fragment)
|
public void AddContent(int sequence, RenderFragment? fragment)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
if (fragment != null)
|
if (fragment != null)
|
||||||
{
|
{
|
||||||
// We surround the fragment with a region delimiter to indicate that the
|
// We surround the fragment with a region delimiter to indicate that the
|
||||||
|
|
@ -107,6 +122,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
fragment(this);
|
fragment(this);
|
||||||
CloseRegion();
|
CloseRegion();
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -117,10 +133,12 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="value">The value used by <paramref name="fragment"/>.</param>
|
/// <param name="value">The value used by <paramref name="fragment"/>.</param>
|
||||||
public void AddContent<TValue>(int sequence, RenderFragment<TValue>? fragment, TValue value)
|
public void AddContent<TValue>(int sequence, RenderFragment<TValue>? fragment, TValue value)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
if (fragment != null)
|
if (fragment != null)
|
||||||
{
|
{
|
||||||
AddContent(sequence, fragment(value));
|
AddContent(sequence, fragment(value));
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -153,6 +171,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="value">The value of the attribute.</param>
|
/// <param name="value">The value of the attribute.</param>
|
||||||
public void AddAttribute(int sequence, string name, bool value)
|
public void AddAttribute(int sequence, string name, bool value)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
AssertCanAddAttribute();
|
AssertCanAddAttribute();
|
||||||
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
||||||
{
|
{
|
||||||
|
|
@ -168,6 +187,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
{
|
{
|
||||||
TrackAttributeName(name);
|
TrackAttributeName(name);
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -184,6 +204,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="value">The value of the attribute.</param>
|
/// <param name="value">The value of the attribute.</param>
|
||||||
public void AddAttribute(int sequence, string name, string? value)
|
public void AddAttribute(int sequence, string name, string? value)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
AssertCanAddAttribute();
|
AssertCanAddAttribute();
|
||||||
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
||||||
{
|
{
|
||||||
|
|
@ -193,6 +214,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
{
|
{
|
||||||
TrackAttributeName(name);
|
TrackAttributeName(name);
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -209,6 +231,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="value">The value of the attribute.</param>
|
/// <param name="value">The value of the attribute.</param>
|
||||||
public void AddAttribute(int sequence, string name, MulticastDelegate? value)
|
public void AddAttribute(int sequence, string name, MulticastDelegate? value)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
AssertCanAddAttribute();
|
AssertCanAddAttribute();
|
||||||
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
||||||
{
|
{
|
||||||
|
|
@ -218,6 +241,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
{
|
{
|
||||||
TrackAttributeName(name);
|
TrackAttributeName(name);
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -238,6 +262,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public void AddAttribute(int sequence, string name, EventCallback value)
|
public void AddAttribute(int sequence, string name, EventCallback value)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
AssertCanAddAttribute();
|
AssertCanAddAttribute();
|
||||||
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
||||||
{
|
{
|
||||||
|
|
@ -262,6 +287,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
// Track the attribute name if needed since we elided the frame.
|
// Track the attribute name if needed since we elided the frame.
|
||||||
TrackAttributeName(name);
|
TrackAttributeName(name);
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -282,6 +308,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public void AddAttribute<TArgument>(int sequence, string name, EventCallback<TArgument> value)
|
public void AddAttribute<TArgument>(int sequence, string name, EventCallback<TArgument> value)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
AssertCanAddAttribute();
|
AssertCanAddAttribute();
|
||||||
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
||||||
{
|
{
|
||||||
|
|
@ -306,6 +333,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
// Track the attribute name if needed since we elided the frame.
|
// Track the attribute name if needed since we elided the frame.
|
||||||
TrackAttributeName(name);
|
TrackAttributeName(name);
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -319,6 +347,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="value">The value of the attribute.</param>
|
/// <param name="value">The value of the attribute.</param>
|
||||||
public void AddAttribute(int sequence, string name, object? value)
|
public void AddAttribute(int sequence, string name, object? value)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
// This looks a bit daunting because we need to handle the boxed/object version of all of the
|
// This looks a bit daunting because we need to handle the boxed/object version of all of the
|
||||||
// types that AddAttribute special cases.
|
// types that AddAttribute special cases.
|
||||||
if (_lastNonAttributeFrameType == RenderTreeFrameType.Element)
|
if (_lastNonAttributeFrameType == RenderTreeFrameType.Element)
|
||||||
|
|
@ -371,6 +400,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
// This is going to throw. Calling it just to get a consistent exception message.
|
// This is going to throw. Calling it just to get a consistent exception message.
|
||||||
AssertCanAddAttribute();
|
AssertCanAddAttribute();
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -385,6 +415,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="frame">A <see cref="RenderTreeFrame"/> holding the name and value of the attribute.</param>
|
/// <param name="frame">A <see cref="RenderTreeFrame"/> holding the name and value of the attribute.</param>
|
||||||
public void AddAttribute(int sequence, in RenderTreeFrame frame)
|
public void AddAttribute(int sequence, in RenderTreeFrame frame)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
if (frame.FrameType != RenderTreeFrameType.Attribute)
|
if (frame.FrameType != RenderTreeFrameType.Attribute)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"The {nameof(frame.FrameType)} must be {RenderTreeFrameType.Attribute}.");
|
throw new ArgumentException($"The {nameof(frame.FrameType)} must be {RenderTreeFrameType.Attribute}.");
|
||||||
|
|
@ -392,6 +423,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
AssertCanAddAttribute();
|
AssertCanAddAttribute();
|
||||||
Append(frame.WithAttributeSequence(sequence));
|
Append(frame.WithAttributeSequence(sequence));
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -401,6 +433,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="attributes">A collection of key-value pairs representing attributes.</param>
|
/// <param name="attributes">A collection of key-value pairs representing attributes.</param>
|
||||||
public void AddMultipleAttributes(int sequence, IEnumerable<KeyValuePair<string, object>>? attributes)
|
public void AddMultipleAttributes(int sequence, IEnumerable<KeyValuePair<string, object>>? attributes)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
// Calling this up-front just to make sure we validate before mutating anything.
|
// Calling this up-front just to make sure we validate before mutating anything.
|
||||||
AssertCanAddAttribute();
|
AssertCanAddAttribute();
|
||||||
|
|
||||||
|
|
@ -417,6 +450,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
AddAttribute(sequence, attribute.Key, attribute.Value);
|
AddAttribute(sequence, attribute.Key, attribute.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -433,6 +467,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="updatesAttributeName">The name of another attribute whose value can be updated when the event handler is executed.</param>
|
/// <param name="updatesAttributeName">The name of another attribute whose value can be updated when the event handler is executed.</param>
|
||||||
public void SetUpdatesAttributeName(string updatesAttributeName)
|
public void SetUpdatesAttributeName(string updatesAttributeName)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
if (_entries.Count == 0)
|
if (_entries.Count == 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("No preceding attribute frame exists.");
|
throw new InvalidOperationException("No preceding attribute frame exists.");
|
||||||
|
|
@ -445,6 +480,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
}
|
}
|
||||||
|
|
||||||
prevFrame = prevFrame.WithAttributeEventUpdatesAttributeName(updatesAttributeName);
|
prevFrame = prevFrame.WithAttributeEventUpdatesAttributeName(updatesAttributeName);
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -476,10 +512,12 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="value">The value for the key.</param>
|
/// <param name="value">The value for the key.</param>
|
||||||
public void SetKey(object? value)
|
public void SetKey(object? value)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
// Null is equivalent to not having set a key, which is valuable because Razor syntax doesn't have an
|
// Null is equivalent to not having set a key, which is valuable because Razor syntax doesn't have an
|
||||||
// easy way to have conditional directive attributes
|
// easy way to have conditional directive attributes
|
||||||
|
ProfilingEnd();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -502,10 +540,12 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException($"Cannot set a key on a frame of type {parentFrame.FrameType}.");
|
throw new InvalidOperationException($"Cannot set a key on a frame of type {parentFrame.FrameType}.");
|
||||||
}
|
}
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenComponentUnchecked(int sequence, Type componentType)
|
private void OpenComponentUnchecked(int sequence, Type componentType)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
// We are entering a new scope, since we track the "duplicate attributes" per
|
// We are entering a new scope, since we track the "duplicate attributes" per
|
||||||
// element/component we might need to clean them up now.
|
// element/component we might need to clean them up now.
|
||||||
if (_hasSeenAddMultipleAttributes)
|
if (_hasSeenAddMultipleAttributes)
|
||||||
|
|
@ -516,6 +556,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
_openElementIndices.Push(_entries.Count);
|
_openElementIndices.Push(_entries.Count);
|
||||||
Append(RenderTreeFrame.ChildComponent(sequence, componentType));
|
Append(RenderTreeFrame.ChildComponent(sequence, componentType));
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -524,6 +565,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CloseComponent()
|
public void CloseComponent()
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var indexOfEntryBeingClosed = _openElementIndices.Pop();
|
var indexOfEntryBeingClosed = _openElementIndices.Pop();
|
||||||
|
|
||||||
// We might be closing a component with only attributes. Run the attribute cleanup pass
|
// We might be closing a component with only attributes. Run the attribute cleanup pass
|
||||||
|
|
@ -535,6 +577,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
|
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
|
||||||
entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
|
entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -544,12 +587,14 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="elementReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
|
/// <param name="elementReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
|
||||||
public void AddElementReferenceCapture(int sequence, Action<ElementReference> elementReferenceCaptureAction)
|
public void AddElementReferenceCapture(int sequence, Action<ElementReference> elementReferenceCaptureAction)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
if (GetCurrentParentFrameType() != RenderTreeFrameType.Element)
|
if (GetCurrentParentFrameType() != RenderTreeFrameType.Element)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Element reference captures may only be added as children of frames of type {RenderTreeFrameType.Element}");
|
throw new InvalidOperationException($"Element reference captures may only be added as children of frames of type {RenderTreeFrameType.Element}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Append(RenderTreeFrame.ElementReferenceCapture(sequence, elementReferenceCaptureAction));
|
Append(RenderTreeFrame.ElementReferenceCapture(sequence, elementReferenceCaptureAction));
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -559,6 +604,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="componentReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
|
/// <param name="componentReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
|
||||||
public void AddComponentReferenceCapture(int sequence, Action<object?> componentReferenceCaptureAction)
|
public void AddComponentReferenceCapture(int sequence, Action<object?> componentReferenceCaptureAction)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var parentFrameIndex = GetCurrentParentFrameIndex();
|
var parentFrameIndex = GetCurrentParentFrameIndex();
|
||||||
if (!parentFrameIndex.HasValue)
|
if (!parentFrameIndex.HasValue)
|
||||||
{
|
{
|
||||||
|
|
@ -572,6 +618,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
}
|
}
|
||||||
|
|
||||||
Append(RenderTreeFrame.ComponentReferenceCapture(sequence, componentReferenceCaptureAction, parentFrameIndexValue));
|
Append(RenderTreeFrame.ComponentReferenceCapture(sequence, componentReferenceCaptureAction, parentFrameIndexValue));
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -580,6 +627,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||||
public void OpenRegion(int sequence)
|
public void OpenRegion(int sequence)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
// We are entering a new scope, since we track the "duplicate attributes" per
|
// We are entering a new scope, since we track the "duplicate attributes" per
|
||||||
// element/component we might need to clean them up now.
|
// element/component we might need to clean them up now.
|
||||||
if (_hasSeenAddMultipleAttributes)
|
if (_hasSeenAddMultipleAttributes)
|
||||||
|
|
@ -590,6 +638,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
_openElementIndices.Push(_entries.Count);
|
_openElementIndices.Push(_entries.Count);
|
||||||
Append(RenderTreeFrame.Region(sequence));
|
Append(RenderTreeFrame.Region(sequence));
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -598,9 +647,11 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void CloseRegion()
|
public void CloseRegion()
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
var indexOfEntryBeingClosed = _openElementIndices.Pop();
|
var indexOfEntryBeingClosed = _openElementIndices.Pop();
|
||||||
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
|
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
|
||||||
entry = entry.WithRegionSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
|
entry = entry.WithRegionSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AssertCanAddAttribute()
|
private void AssertCanAddAttribute()
|
||||||
|
|
@ -628,24 +679,29 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
_entries.Clear();
|
_entries.Clear();
|
||||||
_openElementIndices.Clear();
|
_openElementIndices.Clear();
|
||||||
_lastNonAttributeFrameType = null;
|
_lastNonAttributeFrameType = null;
|
||||||
_hasSeenAddMultipleAttributes = false;
|
_hasSeenAddMultipleAttributes = false;
|
||||||
_seenAttributeNames?.Clear();
|
_seenAttributeNames?.Clear();
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal because this should only be used during the post-event tree patching logic
|
// internal because this should only be used during the post-event tree patching logic
|
||||||
// It's expensive because it involves copying all the subsequent memory in the array
|
// It's expensive because it involves copying all the subsequent memory in the array
|
||||||
internal void InsertAttributeExpensive(int insertAtIndex, int sequence, string attributeName, object? attributeValue)
|
internal void InsertAttributeExpensive(int insertAtIndex, int sequence, string attributeName, object? attributeValue)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
// Replicate the same attribute omission logic as used elsewhere
|
// Replicate the same attribute omission logic as used elsewhere
|
||||||
if ((attributeValue == null) || (attributeValue is bool boolValue && !boolValue))
|
if ((attributeValue == null) || (attributeValue is bool boolValue && !boolValue))
|
||||||
{
|
{
|
||||||
|
ProfilingEnd();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_entries.InsertExpensive(insertAtIndex, RenderTreeFrame.Attribute(sequence, attributeName, attributeValue));
|
_entries.InsertExpensive(insertAtIndex, RenderTreeFrame.Attribute(sequence, attributeName, attributeValue));
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -669,6 +725,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
internal void ProcessDuplicateAttributes(int first)
|
internal void ProcessDuplicateAttributes(int first)
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
Debug.Assert(_hasSeenAddMultipleAttributes);
|
Debug.Assert(_hasSeenAddMultipleAttributes);
|
||||||
|
|
||||||
// When AddMultipleAttributes method has been called, we need to postprocess attributes while closing
|
// When AddMultipleAttributes method has been called, we need to postprocess attributes while closing
|
||||||
|
|
@ -750,6 +807,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
seenAttributeNames.Clear();
|
seenAttributeNames.Clear();
|
||||||
_hasSeenAddMultipleAttributes = false;
|
_hasSeenAddMultipleAttributes = false;
|
||||||
|
ProfilingEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
|
|
@ -766,7 +824,22 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
|
|
||||||
void IDisposable.Dispose()
|
void IDisposable.Dispose()
|
||||||
{
|
{
|
||||||
|
ProfilingStart();
|
||||||
_entries.Dispose();
|
_entries.Dispose();
|
||||||
}
|
ProfilingEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Having too many calls to ComponentsProfiling.Instance.Start/End has a measurable perf impact
|
||||||
|
// even when capturing is disabled. So, to enable detailed profiling for this class, define the
|
||||||
|
// Profile_RenderTreeBuilder compiler symbol, otherwise the calls are compiled out entirely.
|
||||||
|
// Enabling detailed profiling adds about 5% to rendering benchmark times.
|
||||||
|
|
||||||
|
[Conditional("Profile_RenderTreeBuilder")]
|
||||||
|
private static void ProfilingStart([CallerMemberName] string? name = null)
|
||||||
|
=> ComponentsProfiling.Instance.Start(name);
|
||||||
|
|
||||||
|
[Conditional("Profile_RenderTreeBuilder")]
|
||||||
|
private static void ProfilingEnd([CallerMemberName] string? name = null)
|
||||||
|
=> ComponentsProfiling.Instance.End(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace WebAssembly.JSInterop
|
namespace WebAssembly.JSInterop
|
||||||
|
|
@ -21,6 +22,6 @@ namespace WebAssembly.JSInterop
|
||||||
public static extern string InvokeJSMarshalled(out string exception, ref long asyncHandle, string functionIdentifier, string argsJson);
|
public static extern string InvokeJSMarshalled(out string exception, ref long asyncHandle, string functionIdentifier, string argsJson);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||||
public static extern TRes InvokeJSUnmarshalled<T0, T1, T2, TRes>(out string exception, string functionIdentifier, T0 arg0, T1 arg1, T2 arg2);
|
public static extern TRes InvokeJSUnmarshalled<T0, T1, T2, TRes>(out string exception, string functionIdentifier, [AllowNull] T0 arg0, [AllowNull] T1 arg1, [AllowNull] T2 arg2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -12,6 +12,7 @@ import { setEventDispatcher } from './Rendering/RendererEventDispatcher';
|
||||||
import { resolveOptions, CircuitStartOptions } from './Platform/Circuits/CircuitStartOptions';
|
import { resolveOptions, CircuitStartOptions } from './Platform/Circuits/CircuitStartOptions';
|
||||||
import { DefaultReconnectionHandler } from './Platform/Circuits/DefaultReconnectionHandler';
|
import { DefaultReconnectionHandler } from './Platform/Circuits/DefaultReconnectionHandler';
|
||||||
import { attachRootComponentToLogicalElement } from './Rendering/Renderer';
|
import { attachRootComponentToLogicalElement } from './Rendering/Renderer';
|
||||||
|
import { initializeProfiling } from './Platform/Profiling';
|
||||||
|
|
||||||
let renderingFailed = false;
|
let renderingFailed = false;
|
||||||
let started = false;
|
let started = false;
|
||||||
|
|
@ -21,6 +22,7 @@ async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> {
|
||||||
throw new Error('Blazor has already started.');
|
throw new Error('Blazor has already started.');
|
||||||
}
|
}
|
||||||
started = true;
|
started = true;
|
||||||
|
initializeProfiling(null);
|
||||||
|
|
||||||
// Establish options to be used
|
// Establish options to be used
|
||||||
const options = resolveOptions(userOptions);
|
const options = resolveOptions(userOptions);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { WebAssemblyConfigLoader } from './Platform/WebAssemblyConfigLoader';
|
||||||
import { BootConfigResult } from './Platform/BootConfig';
|
import { BootConfigResult } from './Platform/BootConfig';
|
||||||
import { Pointer } from './Platform/Platform';
|
import { Pointer } from './Platform/Platform';
|
||||||
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
|
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
|
||||||
|
import { profileStart, profileEnd } from './Platform/Profiling';
|
||||||
|
|
||||||
let started = false;
|
let started = false;
|
||||||
|
|
||||||
|
|
@ -27,7 +28,9 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
|
||||||
const platform = Environment.setPlatform(monoPlatform);
|
const platform = Environment.setPlatform(monoPlatform);
|
||||||
window['Blazor'].platform = platform;
|
window['Blazor'].platform = platform;
|
||||||
window['Blazor']._internal.renderBatch = (browserRendererId: number, batchAddress: Pointer) => {
|
window['Blazor']._internal.renderBatch = (browserRendererId: number, batchAddress: Pointer) => {
|
||||||
|
profileStart('renderBatch');
|
||||||
renderBatch(browserRendererId, new SharedMemoryRenderBatch(batchAddress));
|
renderBatch(browserRendererId, new SharedMemoryRenderBatch(batchAddress));
|
||||||
|
profileEnd('renderBatch');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Configure navigation via JS Interop
|
// Configure navigation via JS Interop
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { navigateTo, internalFunctions as navigationManagerInternalFunctions } from './Services/NavigationManager';
|
import { navigateTo, internalFunctions as navigationManagerInternalFunctions } from './Services/NavigationManager';
|
||||||
import { attachRootComponentToElement } from './Rendering/Renderer';
|
import { attachRootComponentToElement } from './Rendering/Renderer';
|
||||||
import { domFunctions } from './DomWrapper';
|
import { domFunctions } from './DomWrapper';
|
||||||
|
import { setProfilingEnabled } from './Platform/Profiling';
|
||||||
|
|
||||||
// Make the following APIs available in global scope for invocation from JS
|
// Make the following APIs available in global scope for invocation from JS
|
||||||
window['Blazor'] = {
|
window['Blazor'] = {
|
||||||
|
|
@ -10,5 +11,6 @@ window['Blazor'] = {
|
||||||
attachRootComponentToElement,
|
attachRootComponentToElement,
|
||||||
navigationManager: navigationManagerInternalFunctions,
|
navigationManager: navigationManagerInternalFunctions,
|
||||||
domWrapper: domFunctions,
|
domWrapper: domFunctions,
|
||||||
|
setProfilingEnabled: setProfilingEnabled,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { WebAssemblyResourceLoader, LoadingResource } from '../WebAssemblyResour
|
||||||
import { Platform, System_Array, Pointer, System_Object, System_String } from '../Platform';
|
import { Platform, System_Array, Pointer, System_Object, System_String } from '../Platform';
|
||||||
import { loadTimezoneData } from './TimezoneDataFile';
|
import { loadTimezoneData } from './TimezoneDataFile';
|
||||||
import { WebAssemblyBootResourceType } from '../WebAssemblyStartOptions';
|
import { WebAssemblyBootResourceType } from '../WebAssemblyStartOptions';
|
||||||
|
import { initializeProfiling } from '../Profiling';
|
||||||
|
|
||||||
let mono_string_get_utf8: (managedString: System_String) => Pointer;
|
let mono_string_get_utf8: (managedString: System_String) => Pointer;
|
||||||
let mono_wasm_add_assembly: (name: string, heapAddress: number, length: number) => void;
|
let mono_wasm_add_assembly: (name: string, heapAddress: number, length: number) => void;
|
||||||
|
|
@ -34,6 +35,10 @@ export const monoPlatform: Platform = {
|
||||||
start: function start(resourceLoader: WebAssemblyResourceLoader) {
|
start: function start(resourceLoader: WebAssemblyResourceLoader) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
attachDebuggerHotkey(resourceLoader);
|
attachDebuggerHotkey(resourceLoader);
|
||||||
|
initializeProfiling(isCapturing => {
|
||||||
|
const setCapturingMethod = bindStaticMethod('Microsoft.AspNetCore.Components', 'Microsoft.AspNetCore.Components.Profiling.WebAssemblyComponentsProfiling', 'SetCapturing');
|
||||||
|
setCapturingMethod(isCapturing);
|
||||||
|
});
|
||||||
|
|
||||||
// dotnet.js assumes the existence of this
|
// dotnet.js assumes the existence of this
|
||||||
window['Browser'] = {
|
window['Browser'] = {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
import { System_String } from './Platform';
|
||||||
|
|
||||||
|
interface TimingEntry {
|
||||||
|
// To minimize overhead, don't even decode the strings that arrive from .NET. Assume they are compile-time constants
|
||||||
|
// and hence the memory address will be fixed, so we can just store the pointer value.
|
||||||
|
name: string | System_String;
|
||||||
|
type: 'start' | 'end';
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TraceEvent {
|
||||||
|
// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview
|
||||||
|
name: string;
|
||||||
|
cat: string; // Category
|
||||||
|
ph: 'B' | 'E'; // Phase
|
||||||
|
ts: number; // Timestamp in microseconds
|
||||||
|
pid: number; // Process ID
|
||||||
|
tid: number; // Thread ID
|
||||||
|
}
|
||||||
|
|
||||||
|
let updateCapturingStateInHost: (isCapturing: boolean) => void;
|
||||||
|
let captureStartTime = 0;
|
||||||
|
const blazorProfilingEnabledKey = 'blazorProfilingEnabled';
|
||||||
|
const profilingEnabled = !!localStorage[blazorProfilingEnabledKey];
|
||||||
|
const entryLog: TimingEntry[] = [];
|
||||||
|
const openRegionsStack: (string | System_String)[] = [];
|
||||||
|
|
||||||
|
export function setProfilingEnabled(enabled: boolean) {
|
||||||
|
// We only wire up the hotkeys etc. if the following localStorage entry is present during startup
|
||||||
|
// This is to ensure we're not interfering with any other hotkeys that developers might want to
|
||||||
|
// use for different purposes, plus it gives us a single point where we can notify .NET code during
|
||||||
|
// startup about whether profiling should be enabled.
|
||||||
|
localStorage[blazorProfilingEnabledKey] = (enabled !== false);
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initializeProfiling(setCapturingCallback: ((isCapturing: boolean) => void) | null) {
|
||||||
|
if (!profilingEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCapturingStateInHost = setCapturingCallback || (() => {});
|
||||||
|
|
||||||
|
// Attach hotkey (alt/cmd)+shift+p
|
||||||
|
const altKeyName = navigator.platform.match(/^Mac/i) ? 'Cmd' : 'Alt';
|
||||||
|
console.info(`Profiling hotkey: Shift+${altKeyName}+P (when application has focus)`);
|
||||||
|
document.addEventListener('keydown', evt => {
|
||||||
|
if (evt.shiftKey && (evt.metaKey || evt.altKey) && evt.code === 'KeyP') {
|
||||||
|
toggleCaptureEnabled();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function profileStart(name: System_String | string) {
|
||||||
|
if (!captureStartTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
openRegionsStack.push(name);
|
||||||
|
entryLog.push({ name: name, type: 'start', timestamp: startTime });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function profileEnd(name: System_String | string) {
|
||||||
|
if (!captureStartTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
const poppedRegionName = openRegionsStack.pop();
|
||||||
|
if (!poppedRegionName) {
|
||||||
|
throw new Error(`Profiling mismatch: tried to end profiling for '${readJsString(name)}', but the stack was empty.`);
|
||||||
|
} else if (poppedRegionName !== name) {
|
||||||
|
throw new Error(`Profiling mismatch: tried to end profiling for '${readJsString(name)}', but the top stack item was '${readJsString(poppedRegionName)}'.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
entryLog.push({ name: name, type: 'end', timestamp: endTime });
|
||||||
|
}
|
||||||
|
|
||||||
|
function profileReset() {
|
||||||
|
openRegionsStack.length = 0;
|
||||||
|
entryLog.length = 0;
|
||||||
|
captureStartTime = 0;
|
||||||
|
updateCapturingStateInHost(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function profileExport() {
|
||||||
|
const traceEvents: TraceEvent[] = entryLog.map(entry => ({
|
||||||
|
name: readJsString(entry.name)!,
|
||||||
|
cat: 'PERF',
|
||||||
|
ph: entry.type === 'start' ? 'B': 'E',
|
||||||
|
ts: (entry.timestamp - captureStartTime) * 1000,
|
||||||
|
pid: 0,
|
||||||
|
tid: 0,
|
||||||
|
}));
|
||||||
|
const traceEventsJson = JSON.stringify(traceEvents);
|
||||||
|
const traceEventsBuffer = new TextEncoder().encode(traceEventsJson);
|
||||||
|
const anchorElement = document.createElement('a');
|
||||||
|
anchorElement.href = URL.createObjectURL(new Blob([traceEventsBuffer]));
|
||||||
|
anchorElement.setAttribute('download', 'trace.json');
|
||||||
|
anchorElement.click();
|
||||||
|
URL.revokeObjectURL(anchorElement.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCaptureEnabled() {
|
||||||
|
if (!captureStartTime) {
|
||||||
|
displayOverlayMessage('Started capturing performance profile...');
|
||||||
|
updateCapturingStateInHost(true);
|
||||||
|
captureStartTime = performance.now();
|
||||||
|
} else {
|
||||||
|
displayOverlayMessage('Finished capturing performance profile');
|
||||||
|
profileExport();
|
||||||
|
profileReset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayOverlayMessage(message: string) {
|
||||||
|
const elem = document.createElement('div');
|
||||||
|
elem.textContent = message;
|
||||||
|
elem.setAttribute('style', 'position: absolute; z-index: 99999; font-family: \'Sans Serif\'; top: 0; left: 0; padding: 4px; font-size: 12px; background-color: purple; color: white;');
|
||||||
|
document.body.appendChild(elem);
|
||||||
|
setTimeout(() => document.body.removeChild(elem), 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readJsString(str: string | System_String) {
|
||||||
|
// This is expensive, so don't do it while capturing timings. Only do it as part of the export process.
|
||||||
|
return typeof str === 'string' ? str : BINDING.conv_string(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// These globals deliberately differ from our normal conventions for attaching functions inside Blazor.*
|
||||||
|
// because the intention is to minimize overhead in all reasonable ways. Having any dot-separators in the
|
||||||
|
// name would cause extra string allocations on every invocation.
|
||||||
|
window['_blazorProfileStart'] = profileStart;
|
||||||
|
window['_blazorProfileEnd'] = profileEnd;
|
||||||
|
|
@ -6,6 +6,7 @@ import { applyCaptureIdToElement } from './ElementReferenceCapture';
|
||||||
import { EventFieldInfo } from './EventFieldInfo';
|
import { EventFieldInfo } from './EventFieldInfo';
|
||||||
import { dispatchEvent } from './RendererEventDispatcher';
|
import { dispatchEvent } from './RendererEventDispatcher';
|
||||||
import { attachToEventDelegator as attachNavigationManagerToEventDelegator } from '../Services/NavigationManager';
|
import { attachToEventDelegator as attachNavigationManagerToEventDelegator } from '../Services/NavigationManager';
|
||||||
|
import { profileEnd, profileStart } from '../Platform/Profiling';
|
||||||
const selectValuePropname = '_blazorSelectValue';
|
const selectValuePropname = '_blazorSelectValue';
|
||||||
const sharedTemplateElemForParsing = document.createElement('template');
|
const sharedTemplateElemForParsing = document.createElement('template');
|
||||||
const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
||||||
|
|
@ -40,6 +41,8 @@ export class BrowserRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateComponent(batch: RenderBatch, componentId: number, edits: ArrayBuilderSegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>): void {
|
public updateComponent(batch: RenderBatch, componentId: number, edits: ArrayBuilderSegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>): void {
|
||||||
|
profileStart('updateComponent');
|
||||||
|
|
||||||
const element = this.childComponentLocations[componentId];
|
const element = this.childComponentLocations[componentId];
|
||||||
if (!element) {
|
if (!element) {
|
||||||
throw new Error(`No element is currently associated with component ${componentId}`);
|
throw new Error(`No element is currently associated with component ${componentId}`);
|
||||||
|
|
@ -67,6 +70,8 @@ export class BrowserRenderer {
|
||||||
if ((activeElementBefore instanceof HTMLElement) && ownerDocument && ownerDocument.activeElement !== activeElementBefore) {
|
if ((activeElementBefore instanceof HTMLElement) && ownerDocument && ownerDocument.activeElement !== activeElementBefore) {
|
||||||
activeElementBefore.focus();
|
activeElementBefore.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profileEnd('updateComponent');
|
||||||
}
|
}
|
||||||
|
|
||||||
public disposeComponent(componentId: number) {
|
public disposeComponent(componentId: number) {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="$(ComponentsSharedSourceRoot)src\WebAssemblyJSInteropInternalCalls.cs" />
|
||||||
<Reference Include="Microsoft.JSInterop" />
|
<Reference Include="Microsoft.JSInterop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue