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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
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)
|
||||
using (Diagnostic.BeginCompositorUpdatePass())
|
||||
{
|
||||
var adorner = _adornerUpdateQueue.Dequeue();
|
||||
adorner.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);
|
||||
}
|
||||
|
||||
_updateRequested = false;
|
||||
Readback.CompleteWrite(Revision);
|
||||
|
||||
_overlays.MarkUpdateCallEnd();
|
||||
}
|
||||
|
||||
_updateRequested = false;
|
||||
Readback.CompleteWrite(Revision);
|
||||
|
||||
_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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user