Remove Blazor internal profiling infrastructure (#24468)

* Put back InternalCalls

* Removing .NET profiling calls

* Remove JS side profiling
This commit is contained in:
Steve Sanderson 2020-07-31 22:04:48 +01:00 committed by GitHub
parent a64f3fda00
commit 6f7a3dfd4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 5 additions and 377 deletions

View File

@ -10,7 +10,6 @@
<ItemGroup>
<Compile Include="$(ComponentsSharedSourceRoot)src\ArrayBuilder.cs" LinkBase="RenderTree" />
<Compile Include="$(ComponentsSharedSourceRoot)src\WebAssemblyJSInteropInternalCalls.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -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);
}
}

View File

@ -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)
{
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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,
},
};

View File

@ -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'] = {

View File

@ -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;

View File

@ -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) {

View File

@ -9,7 +9,6 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="$(ComponentsSharedSourceRoot)src\WebAssemblyJSInteropInternalCalls.cs" />
<Reference Include="Microsoft.JSInterop" />
</ItemGroup>