Browse Source

Add diagnostic metrics/activities (#18314)

* Add System.Diagnostics.DiagnosticSource nuget package for pre-.NET 8 targets

* Move System.Memory reference to Base.props

* Add CompositorRenderPass and CompositorUpdatePass metrics

* Layout measure/arrange pass metrics

* Add UI render pass and input pass meters

* Add observable metrics

* Add RaisingRoutedEvent activity

* Add FindingResourceActivity activity

* Add AttachingStyleActivity and EvaluatingStyleActivator activities

* Add PerformingHitTest activity

* Add MeasuingLayoutable/ArrangingLayoutable activities

* Missed RaisingRoutedEvent definition

* Missed tag definitions

* Start FindingResourceActivity on static resources too

* Fix compilation

* Naming

* Add Avalonia.Diagnostics.Diagnostic.IsEnabled runtime switch

* Maybe make it more trimmable as well
pull/18401/head
Maxwell Katz 11 months ago
committed by GitHub
parent
commit
acd4653411
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      Avalonia.sln
  2. 6
      build/Base.props
  3. 6
      build/System.Memory.props
  4. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  5. 8
      src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
  6. 25
      src/Avalonia.Base/Diagnostics/Diagnostic.Activities.cs
  7. 51
      src/Avalonia.Base/Diagnostics/Diagnostic.Consts.cs
  8. 94
      src/Avalonia.Base/Diagnostics/Diagnostic.Metrics.cs
  9. 22
      src/Avalonia.Base/Diagnostics/Diagnostic.cs
  10. 5
      src/Avalonia.Base/Interactivity/EventRoute.cs
  11. 3
      src/Avalonia.Base/Interactivity/Interactive.cs
  12. 5
      src/Avalonia.Base/Interactivity/RoutedEvent.cs
  13. 3
      src/Avalonia.Base/Layout/LayoutManager.cs
  14. 7
      src/Avalonia.Base/Layout/Layoutable.cs
  15. 6
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  16. 28
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  17. 7
      src/Avalonia.Base/Styling/ControlTheme.cs
  18. 6
      src/Avalonia.Base/Styling/Style.cs
  19. 8
      src/Avalonia.Base/Styling/StyleInstance.cs
  20. 4
      src/Avalonia.Base/Threading/DispatcherTimer.cs
  21. 4
      src/Avalonia.Base/Utilities/StopwatchHelper.cs
  22. 5
      src/Avalonia.Base/Visual.cs
  23. 3
      src/Avalonia.Controls/TopLevel.cs
  24. 5
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  25. 1
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  26. 1
      tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj

1
Avalonia.sln

@ -115,7 +115,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\SkiaSharp.props = build\SkiaSharp.props
build\SourceGenerators.props = build\SourceGenerators.props
build\SourceLink.props = build\SourceLink.props
build\System.Memory.props = build\System.Memory.props
build\TargetFrameworks.props = build\TargetFrameworks.props
build\TrimmingEnable.props = build\TrimmingEnable.props
build\UnitTests.NetFX.props = build\UnitTests.NetFX.props

6
build/Base.props

@ -1,6 +1,12 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- '!NET6_0_OR_GREATER' equivalent -->
<ItemGroup Condition="!('$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '6.0')))">
<PackageReference Include="System.Memory" Version="4.5.5" />
</ItemGroup>
<ItemGroup Condition="!('$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '6.0')))">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
</ItemGroup>
<ItemGroup Condition="!('$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '8.0')))">
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
</ItemGroup>
</Project>

6
build/System.Memory.props

@ -1,6 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- '!NET6_0_OR_GREATER' equivalent -->
<ItemGroup Condition="!('$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '6.0')))">
<PackageReference Include="System.Memory" Version="4.5.3" />
</ItemGroup>
</Project>

1
src/Avalonia.Base/Avalonia.Base.csproj

@ -12,7 +12,6 @@
</ItemGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Binding.props" />
<Import Project="..\..\build\System.Memory.props" />
<Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\TrimmingEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" />

8
src/Avalonia.Base/Controls/ResourceNodeExtensions.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Diagnostics;
using Avalonia.Reactive;
using Avalonia.Styling;
@ -75,12 +76,17 @@ namespace Avalonia.Controls
control = control ?? throw new ArgumentNullException(nameof(control));
key = key ?? throw new ArgumentNullException(nameof(key));
IResourceHost? current = control;
using var activity = Diagnostic.FindingResource()?
.AddTag(Diagnostic.Tags.Key, key)
.AddTag(Diagnostic.Tags.ThemeVariant, theme);
var current = control;
while (current != null)
{
if (current.TryGetResource(key, theme, out value))
{
activity?.AddTag(Diagnostic.Tags.Result, true);
return true;
}

25
src/Avalonia.Base/Diagnostics/Diagnostic.Activities.cs

@ -0,0 +1,25 @@
using System.Diagnostics;
// ReSharper disable ExplicitCallerInfoArgument
namespace Avalonia.Diagnostics;
internal static partial class Diagnostic
{
private static ActivitySource? s_activitySource;
public static void InitActivitySource()
{
s_activitySource = new("Avalonia.Diagnostic.Source");
}
private static Activity? StartActivity(string name) => s_activitySource?.StartActivity(name);
public static Activity? AttachingStyle() => StartActivity("Avalonia.AttachingStyle");
public static Activity? FindingResource() => StartActivity("Avalonia.FindingResource");
public static Activity? EvaluatingStyle() => StartActivity("Avalonia.EvaluatingStyle");
public static Activity? MeasuringLayoutable() => StartActivity("Avalonia.MeasuringLayoutable");
public static Activity? ArrangingLayoutable() => StartActivity("Avalonia.ArrangingLayoutable");
public static Activity? PerformingHitTest() => StartActivity("Avalonia.PerformingHitTest");
public static Activity? RaisingRoutedEvent() => StartActivity("Avalonia.RaisingRoutedEvent");
}

51
src/Avalonia.Base/Diagnostics/Diagnostic.Consts.cs

@ -0,0 +1,51 @@
namespace Avalonia.Diagnostics;
internal static partial class Diagnostic
{
public static class Meters
{
public const string SecondsUnit = "s";
public const string MillisecondsUnit = "ms";
public const string CompositorRenderPassName = "avalonia.comp.render.time";
public const string CompositorRenderPassDescription = "Duration of the compositor render pass on render thread";
public const string CompositorUpdatePassName = "avalonia.comp.update.time";
public const string CompositorUpdatePassDescription = "Duration of the compositor update pass on render thread";
public const string LayoutMeasurePassName = "avalonia.ui.measure.time";
public const string LayoutMeasurePassDescription = "Duration of layout measurement pass on UI thread";
public const string LayoutArrangePassName = "avalonia.ui.arrange.time";
public const string LayoutArrangePassDescription = "Duration of layout arrangement pass on UI thread";
public const string LayoutRenderPassName = "avalonia.ui.render.time";
public const string LayoutRenderPassDescription = "Duration of render recording pass on UI thread";
public const string LayoutInputPassName = "avalonia.ui.input.time";
public const string LayoutInputPassDescription = "Duration of input processing on UI thread";
public const string TotalEventHandleCountName = "avalonia.ui.event.handler.count";
public const string TotalEventHandleCountDescription = "Number of event handlers currently registered in the application";
public const string TotalEventHandleCountUnit = "{handler}";
public const string TotalVisualCountName = "avalonia.ui.visual.count";
public const string TotalVisualCountDescription = "Number of visual elements currently present in the visual tree";
public const string TotalVisualCountUnit = "{visual}";
public const string TotalDispatcherTimerCountName = "avalonia.ui.dispatcher.timer.count";
public const string TotalDispatcherTimerCountDescription = "Number of active dispatcher timers in the application";
public const string TotalDispatcherTimerCountUnit = "{timer}";
}
public static class Tags
{
public const string Style = nameof(Style);
public const string SelectorResult = nameof(SelectorResult);
public const string Key = nameof(Key);
public const string ThemeVariant = nameof(ThemeVariant);
public const string Result = nameof(Result);
public const string Activator = nameof(Activator);
public const string IsActive = nameof(IsActive);
public const string Selector = nameof(Selector);
public const string Control = nameof(Control);
public const string RoutedEvent = nameof(RoutedEvent);
}
}

94
src/Avalonia.Base/Diagnostics/Diagnostic.Metrics.cs

@ -0,0 +1,94 @@
using System.Diagnostics;
using System.Diagnostics.Metrics;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Diagnostics;
internal static partial class Diagnostic
{
private static Histogram<double>? s_compositorRender;
private static Histogram<double>? s_compositorUpdate;
private static Histogram<double>? s_layoutMeasure;
private static Histogram<double>? s_layoutArrange;
private static Histogram<double>? s_layoutRender;
private static Histogram<double>? s_layoutInput;
public static void InitMetrics()
{
// Metrics
var meter = new Meter("Avalonia.Diagnostic.Meter");
s_compositorRender = meter.CreateHistogram<double>(
Meters.CompositorRenderPassName,
Meters.MillisecondsUnit,
Meters.CompositorRenderPassDescription);
s_compositorUpdate = meter.CreateHistogram<double>(
Meters.CompositorUpdatePassName,
Meters.MillisecondsUnit,
Meters.CompositorUpdatePassDescription);
s_layoutMeasure = meter.CreateHistogram<double>(
Meters.LayoutMeasurePassName,
Meters.MillisecondsUnit,
Meters.LayoutMeasurePassDescription);
s_layoutArrange = meter.CreateHistogram<double>(
Meters.LayoutArrangePassName,
Meters.MillisecondsUnit,
Meters.LayoutArrangePassDescription);
s_layoutRender = meter.CreateHistogram<double>(
Meters.LayoutRenderPassName,
Meters.MillisecondsUnit,
Meters.LayoutRenderPassDescription);
s_layoutInput = meter.CreateHistogram<double>(
Meters.LayoutInputPassName,
Meters.MillisecondsUnit,
Meters.LayoutInputPassDescription);
meter.CreateObservableUpDownCounter(
Meters.TotalEventHandleCountName,
() => Interactive.TotalHandlersCount,
Meters.TotalEventHandleCountUnit,
Meters.TotalEventHandleCountDescription);
meter.CreateObservableUpDownCounter(
Meters.TotalVisualCountName,
() => Visual.RootedVisualChildrenCount,
Meters.TotalVisualCountUnit,
Meters.TotalVisualCountDescription);
meter.CreateObservableUpDownCounter(
Meters.TotalDispatcherTimerCountName,
() => DispatcherTimer.ActiveTimersCount,
Meters.TotalDispatcherTimerCountUnit,
Meters.TotalDispatcherTimerCountDescription);
}
public static HistogramReportDisposable BeginCompositorRenderPass() => Begin(s_compositorRender);
public static HistogramReportDisposable BeginCompositorUpdatePass() => Begin(s_compositorUpdate);
public static HistogramReportDisposable BeginLayoutMeasurePass() => Begin(s_layoutMeasure);
public static HistogramReportDisposable BeginLayoutArrangePass() => Begin(s_layoutArrange);
public static HistogramReportDisposable BeginLayoutInputPass() => Begin(s_layoutInput);
public static HistogramReportDisposable BeginLayoutRenderPass() => Begin(s_layoutRender);
private static HistogramReportDisposable Begin(Histogram<double>? histogram) => histogram is not null ? new(histogram) : default;
internal readonly ref struct HistogramReportDisposable
{
private readonly Histogram<double> _histogram;
private readonly long _timestamp;
public HistogramReportDisposable(Histogram<double> histogram)
{
_histogram = histogram;
if (histogram.Enabled)
{
_timestamp = Stopwatch.GetTimestamp();
}
}
public void Dispose()
{
if (_timestamp > 0)
{
_histogram.Record(StopwatchHelper.GetElapsedTimeMs(_timestamp));
}
}
}
}

22
src/Avalonia.Base/Diagnostics/Diagnostic.cs

@ -0,0 +1,22 @@
using System;
namespace Avalonia.Diagnostics;
internal static partial class Diagnostic
{
public static bool IsEnabled { get; }
private static bool InitializeIsEnabled() => AppContext.TryGetSwitch("Avalonia.Diagnostics.Diagnostic.IsEnabled", out var isEnabled) && isEnabled;
static Diagnostic()
{
IsEnabled = InitializeIsEnabled();
if (!IsEnabled)
{
return;
}
InitActivitySource();
InitMetrics();
}
}

5
src/Avalonia.Base/Interactivity/EventRoute.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Collections.Pooled;
using Avalonia.Diagnostics;
namespace Avalonia.Interactivity
{
@ -80,6 +81,10 @@ namespace Avalonia.Interactivity
e.Source = source;
using var _ = Diagnostic.RaisingRoutedEvent()?
.AddTag(Diagnostic.Tags.Control, e.Source)
.AddTag(Diagnostic.Tags.RoutedEvent, e.RoutedEvent);
if (_event.RoutingStrategies == RoutingStrategies.Direct)
{
e.Route = RoutingStrategies.Direct;

3
src/Avalonia.Base/Interactivity/Interactive.cs

@ -12,6 +12,7 @@ namespace Avalonia.Interactivity
/// </summary>
public class Interactive : Layoutable
{
internal static int TotalHandlersCount { get; private set; }
private Dictionary<RoutedEvent, List<EventSubscription>>? _eventHandlers;
/// <summary>
@ -90,6 +91,7 @@ namespace Avalonia.Interactivity
if (subscriptions[i].Handler == handler)
{
subscriptions.RemoveAt(i);
TotalHandlersCount--;
}
}
}
@ -185,6 +187,7 @@ namespace Avalonia.Interactivity
}
subscriptions.Add(subscription);
TotalHandlersCount++;
}
private void AddToEventRoute(RoutedEvent routedEvent, EventRoute route)

5
src/Avalonia.Base/Interactivity/RoutedEvent.cs

@ -103,6 +103,11 @@ namespace Avalonia.Interactivity
{
_routeFinished.OnNext(e);
}
public override string ToString()
{
return FormattableString.Invariant($"{OwnerType.Name}.{Name}");
}
}
public class RoutedEvent<TEventArgs> : RoutedEvent

3
src/Avalonia.Base/Layout/LayoutManager.cs

@ -2,6 +2,7 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Metadata;
@ -246,6 +247,7 @@ namespace Avalonia.Layout
private void ExecuteMeasurePass()
{
using var _ = Diagnostic.BeginLayoutMeasurePass();
while (_toMeasure.Count > 0)
{
var control = _toMeasure.Dequeue();
@ -261,6 +263,7 @@ namespace Avalonia.Layout
private void ExecuteArrangePass()
{
using var _ = Diagnostic.BeginLayoutArrangePass();
while (_toArrange.Count > 0)
{
var control = _toArrange.Dequeue();

7
src/Avalonia.Base/Layout/Layoutable.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.Reactive;
using Avalonia.VisualTree;
@ -367,6 +368,9 @@ namespace Avalonia.Layout
if (!IsMeasureValid || _previousMeasure != availableSize)
{
using var activity = Diagnostic.MeasuringLayoutable()?
.AddTag(Diagnostic.Tags.Control, this);
var previousDesiredSize = DesiredSize;
var desiredSize = default(Size);
@ -417,6 +421,9 @@ namespace Avalonia.Layout
if (!IsArrangeValid || _previousArrange != rect)
{
using var activity = Diagnostic.ArrangingLayoutable()?
.AddTag(Diagnostic.Tags.Control, this);
Logger.TryGet(LogEventLevel.Verbose, LogArea.Layout)?.Log(this, "Arrange to {Rect} ", rect);
IsArrangeValid = true;

6
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -6,6 +6,7 @@ using System.Numerics;
using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Collections.Pooled;
using Avalonia.Diagnostics;
using Avalonia.Media;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Threading;
@ -96,6 +97,8 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
/// <inheritdoc/>
public IEnumerable<Visual> HitTest(Point p, Visual? root, Func<Visual, bool>? filter)
{
using var _ = Diagnostic.PerformingHitTest();
CompositionVisual? rootVisual = null;
if (root != null)
{
@ -198,7 +201,8 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester
_updating = true;
try
{
UpdateCore();
using (Diagnostic.BeginLayoutRenderPass())
UpdateCore();
}
finally
{

28
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Avalonia.Collections.Pooled;
using Avalonia.Diagnostics;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Media.Immutable;
@ -122,22 +123,24 @@ namespace Avalonia.Rendering.Composition.Server
Revision++;
_overlays.MarkUpdateCallStart();
using (Diagnostic.BeginCompositorUpdatePass())
{
var transform = Matrix.CreateScale(Scaling, Scaling);
// Update happens in a separate phase to extend dirty rect if needed
Root.Update(this, transform);
var transform = Matrix.CreateScale(Scaling, Scaling);
// Update happens in a separate phase to extend dirty rect if needed
Root.Update(this, transform);
while (_adornerUpdateQueue.Count > 0)
{
var adorner = _adornerUpdateQueue.Dequeue();
adorner.Update(this, transform);
}
while (_adornerUpdateQueue.Count > 0)
{
var adorner = _adornerUpdateQueue.Dequeue();
adorner.Update(this, transform);
}
_updateRequested = false;
Readback.CompleteWrite(Revision);
_updateRequested = false;
Readback.CompleteWrite(Revision);
_overlays.MarkUpdateCallEnd();
}
_overlays.MarkUpdateCallEnd();
if (!_redrawRequested)
return;
@ -151,6 +154,7 @@ namespace Avalonia.Rendering.Composition.Server
using (var renderTargetContext = _renderTarget.CreateDrawingContextWithProperties(
this.PixelSize, out var properties))
using (var renderTiming = Diagnostic.BeginCompositorRenderPass())
{
if(needLayer && (PixelSize != _layerSize || _layer == null || _layer.IsCorrupted))
{

7
src/Avalonia.Base/Styling/ControlTheme.cs

@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using Avalonia.Diagnostics;
using Avalonia.PropertyStore;
namespace Avalonia.Styling
@ -46,12 +47,18 @@ namespace Avalonia.Styling
if (TargetType is null)
throw new InvalidOperationException("ControlTheme has no TargetType.");
using var activity = Diagnostic.AttachingStyle()?
.AddTag(Diagnostic.Tags.Style, this);
if (HasSettersOrAnimations && TargetType.IsAssignableFrom(StyledElement.GetStyleKey(target)))
{
Attach(target, null, type, true);
activity?.AddTag(Diagnostic.Tags.SelectorResult, SelectorMatchResult.AlwaysThisType);
return SelectorMatchResult.AlwaysThisType;
}
activity?.AddTag(Diagnostic.Tags.SelectorResult, SelectorMatchResult.NeverThisType);
return SelectorMatchResult.NeverThisType;
}
}

6
src/Avalonia.Base/Styling/Style.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Diagnostics;
using Avalonia.PropertyStore;
namespace Avalonia.Styling
@ -67,11 +68,16 @@ namespace Avalonia.Styling
if (HasSettersOrAnimations)
{
using var activity = Diagnostic.AttachingStyle()?
.AddTag(Diagnostic.Tags.Style, this);
var match = Selector?.Match(target, Parent, true) ??
(target == host ?
SelectorMatch.AlwaysThisInstance :
SelectorMatch.NeverThisInstance);
activity?.AddTag(Diagnostic.Tags.SelectorResult, match.Result);
if (match.IsMatch)
{
Attach(target, match.Activator, type, Selector is not OrSelector);

8
src/Avalonia.Base/Styling/StyleInstance.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using Avalonia.Animation;
using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.PropertyStore;
using Avalonia.Reactive;
using Avalonia.Styling.Activators;
@ -100,8 +101,15 @@ namespace Avalonia.Styling
_animationTrigger?.OnNext(_activator.GetIsActive());
}
using var activity = _activator is null ? null : Diagnostic.EvaluatingStyle()?
.AddTag(Diagnostic.Tags.Activator, _activator)
.AddTag(Diagnostic.Tags.Selector, (Source as Style)?.Selector)
.AddTag(Diagnostic.Tags.Style, Source as StyleBase);
_isActive = _activator?.GetIsActive() ?? true;
hasChanged = _isActive != previous;
activity?.AddTag(Diagnostic.Tags.IsActive, _isActive);
return _isActive;
}

4
src/Avalonia.Base/Threading/DispatcherTimer.cs

@ -9,6 +9,8 @@ namespace Avalonia.Threading;
/// </summary>
public partial class DispatcherTimer
{
internal static int ActiveTimersCount { get; private set; }
/// <summary>
/// Creates a timer that uses theUI thread's Dispatcher2 to
/// process the timer event at background priority.
@ -147,6 +149,7 @@ public partial class DispatcherTimer
if (!_isEnabled)
{
_isEnabled = true;
ActiveTimersCount++;
Restart();
}
@ -165,6 +168,7 @@ public partial class DispatcherTimer
if (_isEnabled)
{
_isEnabled = false;
ActiveTimersCount--;
updateOSTimer = true;
// If the operation is in the queue, abort it.

4
src/Avalonia.Base/Utilities/StopwatchHelper.cs

@ -10,10 +10,14 @@ namespace Avalonia.Utilities;
internal static class StopwatchHelper
{
private static readonly double s_timestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
private static readonly double s_timestampToMs = s_timestampToTicks / TimeSpan.TicksPerMillisecond;
public static TimeSpan GetElapsedTime(long startingTimestamp)
=> GetElapsedTime(startingTimestamp, Stopwatch.GetTimestamp());
public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp)
=> new((long)((endingTimestamp - startingTimestamp) * s_timestampToTicks));
public static double GetElapsedTimeMs(long startingTimestamp)
=> (Stopwatch.GetTimestamp() - startingTimestamp) * s_timestampToMs;
}

5
src/Avalonia.Base/Visual.cs

@ -7,6 +7,7 @@ using System.Collections;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Media;
@ -31,6 +32,8 @@ namespace Avalonia
[UsableDuringInitialization]
public partial class Visual : StyledElement, IAvaloniaListItemValidator<Visual>
{
internal static int RootedVisualChildrenCount { get; private set; }
/// <summary>
/// Defines the <see cref="Bounds"/> property.
/// </summary>
@ -493,6 +496,7 @@ namespace Avalonia
Logger.TryGet(LogEventLevel.Verbose, LogArea.Visual)?.Log(this, "Attached to visual tree");
_visualRoot = e.Root;
RootedVisualChildrenCount++;
if (_visualParent is null)
{
throw new InvalidOperationException("Visual was attached to the root without being added to the visual parent first.");
@ -541,6 +545,7 @@ namespace Avalonia
Logger.TryGet(LogEventLevel.Verbose, LogArea.Visual)?.Log(this, "Detached from visual tree");
_visualRoot = null;
RootedVisualChildrenCount--;
if (RenderTransform is IMutableTransform mutableTransform)
{

3
src/Avalonia.Controls/TopLevel.cs

@ -23,6 +23,7 @@ using Avalonia.Utilities;
using Avalonia.Input.Platform;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Diagnostics;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
@ -839,6 +840,8 @@ namespace Avalonia.Controls
{
Dispatcher.UIThread.Send(static state =>
{
using var _ = Diagnostic.BeginLayoutInputPass();
var (topLevel, e) = (ValueTuple<TopLevel, RawInputEventArgs>)state!;
if (e is RawPointerEventArgs pointerArgs)
{

5
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Diagnostics;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
@ -64,6 +65,10 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
// which might be able to give us the resource.
if (stack is not null)
{
using var activity = Diagnostic.FindingResource()?
.AddTag(Diagnostic.Tags.Key, resourceKey)
.AddTag(Diagnostic.Tags.ThemeVariant, themeVariant);
// avoid allocations iterating the parents when possible
if (stack is IAvaloniaXamlIlEagerParentStackProvider eagerStack)
{

1
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -10,7 +10,6 @@
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\System.Memory.props" />
<Import Project="..\..\..\build\NullableEnable.props" />
<Import Project="..\..\..\build\DevAnalyzers.props" />
<Import Project="..\..\..\build\TrimmingEnable.props" />

1
tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj

@ -11,7 +11,6 @@
<Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\SharedVersion.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />

Loading…
Cancel
Save