Browse Source
* 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 wellpull/18401/head
committed by
GitHub
26 changed files with 291 additions and 24 deletions
@ -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> |
|||
@ -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(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue