Remove Blazor internal profiling infrastructure (#24468)
* Put back InternalCalls * Removing .NET profiling calls * Remove JS side profiling
This commit is contained in:
parent
a64f3fda00
commit
6f7a3dfd4e
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)src\ArrayBuilder.cs" LinkBase="RenderTree" />
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)src\WebAssemblyJSInteropInternalCalls.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
// 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 = PlatformInfo.IsWebAssembly
|
||||
? new WebAssemblyComponentsProfiling()
|
||||
: (ComponentsProfiling)new NoOpComponentsProfiling();
|
||||
|
||||
public abstract void Start([CallerMemberName] string? name = null);
|
||||
public abstract void End([CallerMemberName] string? name = null);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.AspNetCore.Components.Profiling;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.RenderTree
|
||||
|
|
@ -28,7 +27,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
ArrayRange<RenderTreeFrame> oldTree,
|
||||
ArrayRange<RenderTreeFrame> newTree)
|
||||
{
|
||||
ComponentsProfiling.Instance.Start();
|
||||
var editsBuffer = batchBuilder.EditsBuffer;
|
||||
var editsBufferStartLength = editsBuffer.Count;
|
||||
|
||||
|
|
@ -37,7 +35,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
|
||||
var editsSegment = editsBuffer.ToSegment(editsBufferStartLength, editsBuffer.Count);
|
||||
var result = new RenderTreeDiff(componentId, editsSegment);
|
||||
ComponentsProfiling.Instance.End();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +46,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
int oldStartIndex, int oldEndIndexExcl,
|
||||
int newStartIndex, int newEndIndexExcl)
|
||||
{
|
||||
ProfilingStart();
|
||||
// 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,
|
||||
// because it involves so much parameter passing. You can think of the code here
|
||||
|
|
@ -300,12 +296,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
diffContext.KeyedItemInfoDictionaryPool.Return(keyedItemInfos);
|
||||
}
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
private static Dictionary<object, KeyedItemInfo> BuildKeyToInfoLookup(DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl)
|
||||
{
|
||||
ProfilingStart();
|
||||
var result = diffContext.KeyedItemInfoDictionaryPool.Get();
|
||||
var oldTree = diffContext.OldTree;
|
||||
var newTree = diffContext.NewTree;
|
||||
|
|
@ -351,7 +345,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
newStartIndex = NextSiblingIndex(frame, newStartIndex);
|
||||
}
|
||||
|
||||
ProfilingEnd();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -394,7 +387,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
int oldStartIndex, int oldEndIndexExcl,
|
||||
int newStartIndex, int newEndIndexExcl)
|
||||
{
|
||||
ProfilingStart();
|
||||
// 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
|
||||
// a hash-join if not. We'll do a merge join until we hit a case we can't handle and
|
||||
|
|
@ -443,7 +435,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
ref diffContext,
|
||||
oldStartIndex, oldEndIndexExcl,
|
||||
newStartIndex, newEndIndexExcl);
|
||||
ProfilingEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -469,12 +460,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
ref diffContext,
|
||||
oldStartIndex, oldEndIndexExcl,
|
||||
newStartIndex, newEndIndexExcl);
|
||||
ProfilingEnd();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
private static void AppendAttributeDiffEntriesForRangeSlow(
|
||||
|
|
@ -482,7 +470,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
int oldStartIndex, int oldEndIndexExcl,
|
||||
int newStartIndex, int newEndIndexExcl)
|
||||
{
|
||||
ProfilingStart();
|
||||
var oldTree = diffContext.OldTree;
|
||||
var newTree = diffContext.NewTree;
|
||||
|
||||
|
|
@ -521,7 +508,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
|
||||
// We should have processed any additions at this point. Reset for the next batch.
|
||||
diffContext.AttributeDiffSet.Clear();
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
private static void UpdateRetainedChildComponent(
|
||||
|
|
@ -529,7 +515,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
int oldComponentIndex,
|
||||
int newComponentIndex)
|
||||
{
|
||||
ProfilingStart();
|
||||
var oldTree = diffContext.OldTree;
|
||||
var newTree = diffContext.NewTree;
|
||||
ref var oldComponentFrame = ref oldTree[oldComponentIndex];
|
||||
|
|
@ -556,8 +541,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
{
|
||||
componentState.SetDirectParameters(newParameters);
|
||||
}
|
||||
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
private static int NextSiblingIndex(in RenderTreeFrame frame, int frameIndex)
|
||||
|
|
@ -580,7 +563,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
int oldFrameIndex,
|
||||
int newFrameIndex)
|
||||
{
|
||||
ProfilingStart();
|
||||
var oldTree = diffContext.OldTree;
|
||||
var newTree = diffContext.NewTree;
|
||||
ref var oldFrame = ref oldTree[oldFrameIndex];
|
||||
|
|
@ -593,7 +575,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
{
|
||||
InsertNewFrame(ref diffContext, newFrameIndex);
|
||||
RemoveOldFrame(ref diffContext, oldFrameIndex);
|
||||
ProfilingEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -719,8 +700,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
default:
|
||||
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
|
||||
|
|
@ -730,7 +709,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
int oldFrameIndex,
|
||||
int newFrameIndex)
|
||||
{
|
||||
ProfilingStart();
|
||||
var oldTree = diffContext.OldTree;
|
||||
var newTree = diffContext.NewTree;
|
||||
ref var oldFrame = ref oldTree[oldFrameIndex];
|
||||
|
|
@ -759,13 +737,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
// since it was unchanged.
|
||||
newFrame = oldFrame;
|
||||
}
|
||||
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
private static void InsertNewFrame(ref DiffContext diffContext, int newFrameIndex)
|
||||
{
|
||||
ProfilingStart();
|
||||
var newTree = diffContext.NewTree;
|
||||
ref var newFrame = ref newTree[newFrameIndex];
|
||||
switch (newFrame.FrameType)
|
||||
|
|
@ -818,12 +793,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
default:
|
||||
throw new NotImplementedException($"Unexpected frame type during {nameof(InsertNewFrame)}: {newFrame.FrameType}");
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
private static void RemoveOldFrame(ref DiffContext diffContext, int oldFrameIndex)
|
||||
{
|
||||
ProfilingStart();
|
||||
var oldTree = diffContext.OldTree;
|
||||
ref var oldFrame = ref oldTree[oldFrameIndex];
|
||||
switch (oldFrame.FrameType)
|
||||
|
|
@ -865,7 +838,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
default:
|
||||
throw new NotImplementedException($"Unexpected frame type during {nameof(RemoveOldFrame)}: {oldFrame.FrameType}");
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
private static int GetAttributesEndIndexExclusive(RenderTreeFrame[] tree, int rootIndex)
|
||||
|
|
@ -899,7 +871,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
|
||||
private static void InitializeNewSubtree(ref DiffContext diffContext, int frameIndex)
|
||||
{
|
||||
ProfilingStart();
|
||||
var frames = diffContext.NewTree;
|
||||
var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength;
|
||||
for (var i = frameIndex; i < endIndexExcl; i++)
|
||||
|
|
@ -921,12 +892,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
break;
|
||||
}
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
private static void InitializeNewComponentFrame(ref DiffContext diffContext, int frameIndex)
|
||||
{
|
||||
ProfilingStart();
|
||||
var frames = diffContext.NewTree;
|
||||
ref var frame = ref frames[frameIndex];
|
||||
|
||||
|
|
@ -943,7 +912,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
var initialParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder);
|
||||
var initialParameters = new ParameterView(initialParametersLifetime, frames, frameIndex);
|
||||
childComponentState.SetDirectParameters(initialParameters);
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
private static void InitializeNewAttributeFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
|
||||
|
|
@ -988,7 +956,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
|
||||
private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
|
||||
{
|
||||
ProfilingStart();
|
||||
for (var i = startIndex; i < endIndexExcl; i++)
|
||||
{
|
||||
ref var frame = ref frames[i];
|
||||
|
|
@ -1001,7 +968,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
batchBuilder.DisposedEventHandlerIds.Append(frame.AttributeEventHandlerId);
|
||||
}
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1043,18 +1009,5 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
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,7 +8,6 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Profiling;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -247,7 +246,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
/// </returns>
|
||||
public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs)
|
||||
{
|
||||
ComponentsProfiling.Instance.Start();
|
||||
Dispatcher.AssertAccess();
|
||||
|
||||
if (!_eventBindings.TryGetValue(eventHandlerId, out var callback))
|
||||
|
|
@ -275,7 +273,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
catch (Exception e)
|
||||
{
|
||||
HandleException(e);
|
||||
ComponentsProfiling.Instance.End();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
finally
|
||||
|
|
@ -290,7 +287,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
// 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.
|
||||
var result = GetErrorHandledTask(task);
|
||||
ComponentsProfiling.Instance.End();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -441,7 +437,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
|
||||
private void ProcessRenderQueue()
|
||||
{
|
||||
ComponentsProfiling.Instance.Start();
|
||||
Dispatcher.AssertAccess();
|
||||
|
||||
if (_isBatchInProgress)
|
||||
|
|
@ -456,7 +451,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
{
|
||||
if (_batchBuilder.ComponentRenderQueue.Count == 0)
|
||||
{
|
||||
ComponentsProfiling.Instance.End();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -468,9 +462,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
}
|
||||
|
||||
var batch = _batchBuilder.ToBatch();
|
||||
ComponentsProfiling.Instance.Start(nameof(UpdateDisplayAsync));
|
||||
updateDisplayTask = UpdateDisplayAsync(batch);
|
||||
ComponentsProfiling.Instance.End(nameof(UpdateDisplayAsync));
|
||||
|
||||
// Fire off the execution of OnAfterRenderAsync, but don't wait for it
|
||||
// if there is async work to be done.
|
||||
|
|
@ -480,7 +472,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
{
|
||||
// Ensure we catch errors while running the render functions of the components.
|
||||
HandleException(e);
|
||||
ComponentsProfiling.Instance.End();
|
||||
return;
|
||||
}
|
||||
finally
|
||||
|
|
@ -498,8 +489,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
|
|||
{
|
||||
ProcessRenderQueue();
|
||||
}
|
||||
|
||||
ComponentsProfiling.Instance.End();
|
||||
}
|
||||
|
||||
private Task InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updatedComponents, Task updateDisplayTask)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components.Profiling;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Rendering
|
||||
|
|
@ -57,7 +56,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
|
||||
public void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)
|
||||
{
|
||||
ComponentsProfiling.Instance.Start();
|
||||
// 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.
|
||||
if (_componentWasDisposed)
|
||||
|
|
@ -69,9 +67,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
(CurrentRenderTree, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, CurrentRenderTree);
|
||||
|
||||
CurrentRenderTree.Clear();
|
||||
ComponentsProfiling.Instance.Start("BuildRenderTree");
|
||||
renderFragment(CurrentRenderTree);
|
||||
ComponentsProfiling.Instance.End("BuildRenderTree");
|
||||
|
||||
var diff = RenderTreeDiffBuilder.ComputeDiff(
|
||||
_renderer,
|
||||
|
|
@ -81,7 +77,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
CurrentRenderTree.GetFrames());
|
||||
batchBuilder.UpdatedComponentDiffs.Append(diff);
|
||||
batchBuilder.InvalidateParameterViews();
|
||||
ComponentsProfiling.Instance.End();
|
||||
}
|
||||
|
||||
public bool TryDisposeInBatch(RenderBatchBuilder batchBuilder, [NotNullWhen(false)] out Exception? exception)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.AspNetCore.Components.Profiling;
|
||||
using Microsoft.AspNetCore.Components.RenderTree;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.Rendering
|
||||
|
|
@ -45,7 +44,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="elementName">A value representing the type of the element.</param>
|
||||
public void OpenElement(int sequence, string elementName)
|
||||
{
|
||||
ProfilingStart();
|
||||
// We are entering a new scope, since we track the "duplicate attributes" per
|
||||
// element/component we might need to clean them up now.
|
||||
if (_hasSeenAddMultipleAttributes)
|
||||
|
|
@ -56,7 +54,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
|
||||
_openElementIndices.Push(_entries.Count);
|
||||
Append(RenderTreeFrame.Element(sequence, elementName));
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -65,7 +62,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// </summary>
|
||||
public void CloseElement()
|
||||
{
|
||||
ProfilingStart();
|
||||
var indexOfEntryBeingClosed = _openElementIndices.Pop();
|
||||
|
||||
// We might be closing an element with only attributes, run the duplicate cleanup pass
|
||||
|
|
@ -77,7 +73,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
|
||||
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
|
||||
entry = entry.WithElementSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -87,9 +82,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="markupContent">Content for the new markup frame.</param>
|
||||
public void AddMarkupContent(int sequence, string? markupContent)
|
||||
{
|
||||
ProfilingStart();
|
||||
Append(RenderTreeFrame.Markup(sequence, markupContent ?? string.Empty));
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -99,9 +92,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="textContent">Content for the new text frame.</param>
|
||||
public void AddContent(int sequence, string? textContent)
|
||||
{
|
||||
ProfilingStart();
|
||||
Append(RenderTreeFrame.Text(sequence, textContent ?? string.Empty));
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -111,7 +102,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="fragment">Content to append.</param>
|
||||
public void AddContent(int sequence, RenderFragment? fragment)
|
||||
{
|
||||
ProfilingStart();
|
||||
if (fragment != null)
|
||||
{
|
||||
// We surround the fragment with a region delimiter to indicate that the
|
||||
|
|
@ -122,7 +112,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
fragment(this);
|
||||
CloseRegion();
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -133,12 +122,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="value">The value used by <paramref name="fragment"/>.</param>
|
||||
public void AddContent<TValue>(int sequence, RenderFragment<TValue>? fragment, TValue value)
|
||||
{
|
||||
ProfilingStart();
|
||||
if (fragment != null)
|
||||
{
|
||||
AddContent(sequence, fragment(value));
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -169,15 +156,12 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="name">The name of the attribute.</param>
|
||||
public void AddAttribute(int sequence, string name)
|
||||
{
|
||||
ProfilingStart();
|
||||
|
||||
if (_lastNonAttributeFrameType != RenderTreeFrameType.Element)
|
||||
{
|
||||
throw new InvalidOperationException($"Valueless attributes may only be added immediately after frames of type {RenderTreeFrameType.Element}");
|
||||
}
|
||||
|
||||
Append(RenderTreeFrame.Attribute(sequence, name, BoxedTrue));
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -194,7 +178,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="value">The value of the attribute.</param>
|
||||
public void AddAttribute(int sequence, string name, bool value)
|
||||
{
|
||||
ProfilingStart();
|
||||
AssertCanAddAttribute();
|
||||
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
||||
{
|
||||
|
|
@ -210,7 +193,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
{
|
||||
TrackAttributeName(name);
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -227,7 +209,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="value">The value of the attribute.</param>
|
||||
public void AddAttribute(int sequence, string name, string? value)
|
||||
{
|
||||
ProfilingStart();
|
||||
AssertCanAddAttribute();
|
||||
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
||||
{
|
||||
|
|
@ -237,7 +218,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
{
|
||||
TrackAttributeName(name);
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -254,7 +234,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="value">The value of the attribute.</param>
|
||||
public void AddAttribute(int sequence, string name, MulticastDelegate? value)
|
||||
{
|
||||
ProfilingStart();
|
||||
AssertCanAddAttribute();
|
||||
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
||||
{
|
||||
|
|
@ -264,7 +243,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
{
|
||||
TrackAttributeName(name);
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -285,7 +263,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// </remarks>
|
||||
public void AddAttribute(int sequence, string name, EventCallback value)
|
||||
{
|
||||
ProfilingStart();
|
||||
AssertCanAddAttribute();
|
||||
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
||||
{
|
||||
|
|
@ -310,7 +287,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
// Track the attribute name if needed since we elided the frame.
|
||||
TrackAttributeName(name);
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -331,7 +307,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// </remarks>
|
||||
public void AddAttribute<TArgument>(int sequence, string name, EventCallback<TArgument> value)
|
||||
{
|
||||
ProfilingStart();
|
||||
AssertCanAddAttribute();
|
||||
if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
|
||||
{
|
||||
|
|
@ -356,7 +331,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
// Track the attribute name if needed since we elided the frame.
|
||||
TrackAttributeName(name);
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -370,7 +344,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="value">The value of the attribute.</param>
|
||||
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
|
||||
// types that AddAttribute special cases.
|
||||
if (_lastNonAttributeFrameType == RenderTreeFrameType.Element)
|
||||
|
|
@ -423,7 +396,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
// This is going to throw. Calling it just to get a consistent exception message.
|
||||
AssertCanAddAttribute();
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -438,7 +410,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="frame">A <see cref="RenderTreeFrame"/> holding the name and value of the attribute.</param>
|
||||
public void AddAttribute(int sequence, in RenderTreeFrame frame)
|
||||
{
|
||||
ProfilingStart();
|
||||
if (frame.FrameType != RenderTreeFrameType.Attribute)
|
||||
{
|
||||
throw new ArgumentException($"The {nameof(frame.FrameType)} must be {RenderTreeFrameType.Attribute}.");
|
||||
|
|
@ -446,7 +417,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
|
||||
AssertCanAddAttribute();
|
||||
Append(frame.WithAttributeSequence(sequence));
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -456,7 +426,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="attributes">A collection of key-value pairs representing attributes.</param>
|
||||
public void AddMultipleAttributes(int sequence, IEnumerable<KeyValuePair<string, object>>? attributes)
|
||||
{
|
||||
ProfilingStart();
|
||||
// Calling this up-front just to make sure we validate before mutating anything.
|
||||
AssertCanAddAttribute();
|
||||
|
||||
|
|
@ -473,7 +442,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
AddAttribute(sequence, attribute.Key, attribute.Value);
|
||||
}
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -490,7 +458,6 @@ 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>
|
||||
public void SetUpdatesAttributeName(string updatesAttributeName)
|
||||
{
|
||||
ProfilingStart();
|
||||
if (_entries.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("No preceding attribute frame exists.");
|
||||
|
|
@ -503,7 +470,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
}
|
||||
|
||||
prevFrame = prevFrame.WithAttributeEventUpdatesAttributeName(updatesAttributeName);
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -535,12 +501,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="value">The value for the key.</param>
|
||||
public void SetKey(object? value)
|
||||
{
|
||||
ProfilingStart();
|
||||
if (value == null)
|
||||
{
|
||||
// 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
|
||||
ProfilingEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -563,12 +527,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
default:
|
||||
throw new InvalidOperationException($"Cannot set a key on a frame of type {parentFrame.FrameType}.");
|
||||
}
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
private void OpenComponentUnchecked(int sequence, Type componentType)
|
||||
{
|
||||
ProfilingStart();
|
||||
// We are entering a new scope, since we track the "duplicate attributes" per
|
||||
// element/component we might need to clean them up now.
|
||||
if (_hasSeenAddMultipleAttributes)
|
||||
|
|
@ -579,7 +541,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
|
||||
_openElementIndices.Push(_entries.Count);
|
||||
Append(RenderTreeFrame.ChildComponent(sequence, componentType));
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -588,7 +549,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// </summary>
|
||||
public void CloseComponent()
|
||||
{
|
||||
ProfilingStart();
|
||||
var indexOfEntryBeingClosed = _openElementIndices.Pop();
|
||||
|
||||
// We might be closing a component with only attributes. Run the attribute cleanup pass
|
||||
|
|
@ -600,7 +560,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
|
||||
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
|
||||
entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -610,14 +569,12 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="elementReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
|
||||
public void AddElementReferenceCapture(int sequence, Action<ElementReference> elementReferenceCaptureAction)
|
||||
{
|
||||
ProfilingStart();
|
||||
if (GetCurrentParentFrameType() != 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));
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -627,7 +584,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="componentReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
|
||||
public void AddComponentReferenceCapture(int sequence, Action<object?> componentReferenceCaptureAction)
|
||||
{
|
||||
ProfilingStart();
|
||||
var parentFrameIndex = GetCurrentParentFrameIndex();
|
||||
if (!parentFrameIndex.HasValue)
|
||||
{
|
||||
|
|
@ -641,7 +597,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
}
|
||||
|
||||
Append(RenderTreeFrame.ComponentReferenceCapture(sequence, componentReferenceCaptureAction, parentFrameIndexValue));
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -650,7 +605,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
public void OpenRegion(int sequence)
|
||||
{
|
||||
ProfilingStart();
|
||||
// We are entering a new scope, since we track the "duplicate attributes" per
|
||||
// element/component we might need to clean them up now.
|
||||
if (_hasSeenAddMultipleAttributes)
|
||||
|
|
@ -661,7 +615,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
|
||||
_openElementIndices.Push(_entries.Count);
|
||||
Append(RenderTreeFrame.Region(sequence));
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -670,11 +623,9 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// </summary>
|
||||
public void CloseRegion()
|
||||
{
|
||||
ProfilingStart();
|
||||
var indexOfEntryBeingClosed = _openElementIndices.Pop();
|
||||
ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
|
||||
entry = entry.WithRegionSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
private void AssertCanAddAttribute()
|
||||
|
|
@ -702,29 +653,24 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
ProfilingStart();
|
||||
_entries.Clear();
|
||||
_openElementIndices.Clear();
|
||||
_lastNonAttributeFrameType = null;
|
||||
_hasSeenAddMultipleAttributes = false;
|
||||
_seenAttributeNames?.Clear();
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
// 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
|
||||
internal void InsertAttributeExpensive(int insertAtIndex, int sequence, string attributeName, object? attributeValue)
|
||||
{
|
||||
ProfilingStart();
|
||||
// Replicate the same attribute omission logic as used elsewhere
|
||||
if ((attributeValue == null) || (attributeValue is bool boolValue && !boolValue))
|
||||
{
|
||||
ProfilingEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
_entries.InsertExpensive(insertAtIndex, RenderTreeFrame.Attribute(sequence, attributeName, attributeValue));
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -748,7 +694,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
// Internal for testing
|
||||
internal void ProcessDuplicateAttributes(int first)
|
||||
{
|
||||
ProfilingStart();
|
||||
Debug.Assert(_hasSeenAddMultipleAttributes);
|
||||
|
||||
// When AddMultipleAttributes method has been called, we need to postprocess attributes while closing
|
||||
|
|
@ -830,7 +775,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
|
||||
seenAttributeNames.Clear();
|
||||
_hasSeenAddMultipleAttributes = false;
|
||||
ProfilingEnd();
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
|
|
@ -847,22 +791,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
|||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
ProfilingStart();
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -12,7 +12,6 @@ import { setEventDispatcher } from './Rendering/RendererEventDispatcher';
|
|||
import { resolveOptions, CircuitStartOptions } from './Platform/Circuits/CircuitStartOptions';
|
||||
import { DefaultReconnectionHandler } from './Platform/Circuits/DefaultReconnectionHandler';
|
||||
import { attachRootComponentToLogicalElement } from './Rendering/Renderer';
|
||||
import { initializeProfiling } from './Platform/Profiling';
|
||||
|
||||
let renderingFailed = false;
|
||||
let started = false;
|
||||
|
|
@ -22,7 +21,6 @@ async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> {
|
|||
throw new Error('Blazor has already started.');
|
||||
}
|
||||
started = true;
|
||||
initializeProfiling(null);
|
||||
|
||||
// Establish options to be used
|
||||
const options = resolveOptions(userOptions);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import { WebAssemblyConfigLoader } from './Platform/WebAssemblyConfigLoader';
|
|||
import { BootConfigResult } from './Platform/BootConfig';
|
||||
import { Pointer } from './Platform/Platform';
|
||||
import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
|
||||
import { profileStart, profileEnd } from './Platform/Profiling';
|
||||
|
||||
let started = false;
|
||||
|
||||
|
|
@ -34,8 +33,6 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
|
|||
const platform = Environment.setPlatform(monoPlatform);
|
||||
window['Blazor'].platform = platform;
|
||||
window['Blazor']._internal.renderBatch = (browserRendererId: number, batchAddress: Pointer) => {
|
||||
profileStart('renderBatch');
|
||||
|
||||
// We're going to read directly from the .NET memory heap, so indicate to the platform
|
||||
// that we don't want anything to modify the memory contents during this time. Currently this
|
||||
// is only guaranteed by the fact that .NET code doesn't run during this time, but in the
|
||||
|
|
@ -47,8 +44,6 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
|
|||
} finally {
|
||||
heapLock.release();
|
||||
}
|
||||
|
||||
profileEnd('renderBatch');
|
||||
};
|
||||
|
||||
// Configure navigation via JS Interop
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { navigateTo, internalFunctions as navigationManagerInternalFunctions } from './Services/NavigationManager';
|
||||
import { attachRootComponentToElement } from './Rendering/Renderer';
|
||||
import { domFunctions } from './DomWrapper';
|
||||
import { setProfilingEnabled } from './Platform/Profiling';
|
||||
|
||||
// Make the following APIs available in global scope for invocation from JS
|
||||
window['Blazor'] = {
|
||||
|
|
@ -11,6 +10,5 @@ window['Blazor'] = {
|
|||
attachRootComponentToElement,
|
||||
navigationManager: navigationManagerInternalFunctions,
|
||||
domWrapper: domFunctions,
|
||||
setProfilingEnabled: setProfilingEnabled,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { WebAssemblyResourceLoader, LoadingResource } from '../WebAssemblyResour
|
|||
import { Platform, System_Array, Pointer, System_Object, System_String, HeapLock } from '../Platform';
|
||||
import { loadTimezoneData } from './TimezoneDataFile';
|
||||
import { WebAssemblyBootResourceType } from '../WebAssemblyStartOptions';
|
||||
import { initializeProfiling } from '../Profiling';
|
||||
|
||||
let mono_wasm_add_assembly: (name: string, heapAddress: number, length: number) => void;
|
||||
const appBinDirName = 'appBinDir';
|
||||
|
|
@ -36,10 +35,6 @@ export const monoPlatform: Platform = {
|
|||
start: function start(resourceLoader: WebAssemblyResourceLoader) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
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
|
||||
window['Browser'] = {
|
||||
|
|
|
|||
|
|
@ -1,137 +0,0 @@
|
|||
// Import type definitions to ensure that the global declaration
|
||||
// is of BINDING is included when tests run
|
||||
import './Mono/MonoTypes';
|
||||
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,7 +6,6 @@ import { applyCaptureIdToElement } from './ElementReferenceCapture';
|
|||
import { EventFieldInfo } from './EventFieldInfo';
|
||||
import { dispatchEvent } from './RendererEventDispatcher';
|
||||
import { attachToEventDelegator as attachNavigationManagerToEventDelegator } from '../Services/NavigationManager';
|
||||
import { profileEnd, profileStart } from '../Platform/Profiling';
|
||||
const selectValuePropname = '_blazorSelectValue';
|
||||
const sharedTemplateElemForParsing = document.createElement('template');
|
||||
const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
||||
|
|
@ -41,8 +40,6 @@ export class BrowserRenderer {
|
|||
}
|
||||
|
||||
public updateComponent(batch: RenderBatch, componentId: number, edits: ArrayBuilderSegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>): void {
|
||||
profileStart('updateComponent');
|
||||
|
||||
const element = this.childComponentLocations[componentId];
|
||||
if (!element) {
|
||||
throw new Error(`No element is currently associated with component ${componentId}`);
|
||||
|
|
@ -70,8 +67,6 @@ export class BrowserRenderer {
|
|||
if ((activeElementBefore instanceof HTMLElement) && ownerDocument && ownerDocument.activeElement !== activeElementBefore) {
|
||||
activeElementBefore.focus();
|
||||
}
|
||||
|
||||
profileEnd('updateComponent');
|
||||
}
|
||||
|
||||
public disposeComponent(componentId: number) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(ComponentsSharedSourceRoot)src\WebAssemblyJSInteropInternalCalls.cs" />
|
||||
<Reference Include="Microsoft.JSInterop" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue