diff --git a/src/Core/src/Diagnostics/DiagnosticsManager.cs b/src/Core/src/Diagnostics/DiagnosticsManager.cs index 2d32495ff55a..183c01e41d7b 100644 --- a/src/Core/src/Diagnostics/DiagnosticsManager.cs +++ b/src/Core/src/Diagnostics/DiagnosticsManager.cs @@ -35,8 +35,6 @@ public DiagnosticsManager(IEnumerable metrics, IEnumerable ActivitySource.HasListeners(); - public Meter? Meter { get; } public void GetTags(object source, out TagList tagList) diff --git a/src/Core/src/Diagnostics/IDiagnosticsManager.cs b/src/Core/src/Diagnostics/IDiagnosticsManager.cs index 9166c60d43c4..a46dd82b7bd5 100644 --- a/src/Core/src/Diagnostics/IDiagnosticsManager.cs +++ b/src/Core/src/Diagnostics/IDiagnosticsManager.cs @@ -7,8 +7,6 @@ internal interface IDiagnosticsManager { ActivitySource ActivitySource { get; } - bool HasActivityListeners { get; } - Meter? Meter { get; } void GetTags(object source, out TagList tagList); diff --git a/src/Core/src/Diagnostics/Instrumentation/DiagnosticInstrumentation.cs b/src/Core/src/Diagnostics/Instrumentation/DiagnosticInstrumentation.cs index 3189d46296a3..cd1e74f1c528 100644 --- a/src/Core/src/Diagnostics/Instrumentation/DiagnosticInstrumentation.cs +++ b/src/Core/src/Diagnostics/Instrumentation/DiagnosticInstrumentation.cs @@ -10,52 +10,18 @@ internal static class DiagnosticInstrumentation /// /// The view to instrument. /// Returns an instance of if instrumentation is supported; otherwise, null. - public static LayoutMeasureInstrumentation? StartLayoutMeasure(IView view) - { - if (!RuntimeFeature.IsMeterSupported) - { - return null; - } - - var diagnostics = view.GetMauiDiagnostics(); - if (diagnostics is null) - { - return null; - } - - var metrics = diagnostics.GetMetrics(); - if (!diagnostics.HasActivityListeners && metrics?.IsMeasureEnabled != true) - { - return null; - } - - return new LayoutMeasureInstrumentation(view, diagnostics, metrics); - } + public static LayoutMeasureInstrumentation? StartLayoutMeasure(IView view) => + RuntimeFeature.IsMeterSupported + ? new LayoutMeasureInstrumentation(view) + : null; /// /// Starts layout arrange instrumentation for the specified view. /// /// The view to instrument. /// Returns an instance of if instrumentation is supported; otherwise, null. - public static LayoutArrangeInstrumentation? StartLayoutArrange(IView view) - { - if (!RuntimeFeature.IsMeterSupported) - { - return null; - } - - var diagnostics = view.GetMauiDiagnostics(); - if (diagnostics is null) - { - return null; - } - - var metrics = diagnostics.GetMetrics(); - if (!diagnostics.HasActivityListeners && metrics?.IsArrangeEnabled != true) - { - return null; - } - - return new LayoutArrangeInstrumentation(view, diagnostics, metrics); - } + public static LayoutArrangeInstrumentation? StartLayoutArrange(IView view) => + RuntimeFeature.IsMeterSupported + ? new LayoutArrangeInstrumentation(view) + : null; } diff --git a/src/Core/src/Diagnostics/Instrumentation/LayoutArrangeInstrumentation.cs b/src/Core/src/Diagnostics/Instrumentation/LayoutArrangeInstrumentation.cs index 8c24898840e0..895e7dd72dfd 100644 --- a/src/Core/src/Diagnostics/Instrumentation/LayoutArrangeInstrumentation.cs +++ b/src/Core/src/Diagnostics/Instrumentation/LayoutArrangeInstrumentation.cs @@ -5,59 +5,21 @@ namespace Microsoft.Maui.Diagnostics; /// /// Instrumentation for the layout arrange phase of a view. /// -readonly struct LayoutArrangeInstrumentation : System.IDisposable +readonly struct LayoutArrangeInstrumentation(IView view) : IDiagnosticInstrumentation { - readonly IView _view; - readonly IDiagnosticsManager _diagnostics; - readonly LayoutDiagnosticMetrics? _metrics; - readonly Activity? _activity; - readonly bool _metricsDurationStarted; - readonly long _metricsStartTimestamp; - - public LayoutArrangeInstrumentation(IView view, IDiagnosticsManager diagnostics, LayoutDiagnosticMetrics? metrics) - { - _view = view; - _diagnostics = diagnostics; - _metrics = metrics; - - if (diagnostics.HasActivityListeners) - { - diagnostics.GetTags(view, out var tagList); - _activity = diagnostics.ActivitySource.StartActivity( - ActivityKind.Internal, - name: $"Arrange {view.GetType().Name}", - tags: tagList); - } - else - { - _activity = null; - } - - _metricsDurationStarted = metrics?.IsArrangeDurationEnabled == true; - _metricsStartTimestamp = _metricsDurationStarted - ? Stopwatch.GetTimestamp() - : 0; - } + readonly Activity? _activity = view.StartDiagnosticActivity("Arrange"); /// /// Disposes the instrumentation and stops the diagnostic activity. /// - public void Dispose() - { - var metrics = _metrics; - var recordDuration = _metricsDurationStarted && metrics?.IsArrangeDurationEnabled == true; - var duration = recordDuration - ? LayoutDiagnosticMetrics.GetElapsedNanoseconds(_metricsStartTimestamp) - : 0; - - _activity?.Stop(); + public void Dispose() => + view.StopDiagnostics(_activity, this); - if (metrics?.IsArrangeEnabled == true) - { - _diagnostics.GetTags(_view, out var tagList); - metrics.RecordArrange(duration, recordDuration, in tagList); - } - - _activity?.Dispose(); - } + /// + /// Records the stopping of the instrumentation and publishes various metrics. + /// + /// The instance. + /// The tags associated with the instrumentation. + public void Stopped(IDiagnosticsManager diagnostics, in TagList tagList) => + diagnostics.GetMetrics()?.RecordArrange(_activity?.Duration, in tagList); } diff --git a/src/Core/src/Diagnostics/Instrumentation/LayoutDiagnosticMetrics.cs b/src/Core/src/Diagnostics/Instrumentation/LayoutDiagnosticMetrics.cs index 881d51346932..51531ed5584b 100644 --- a/src/Core/src/Diagnostics/Instrumentation/LayoutDiagnosticMetrics.cs +++ b/src/Core/src/Diagnostics/Instrumentation/LayoutDiagnosticMetrics.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics; using System.Diagnostics.Metrics; @@ -28,18 +29,6 @@ internal class LayoutDiagnosticMetrics : IDiagnosticMetrics /// internal Histogram? ArrangeHistogram { get; private set; } - internal bool IsMeasureEnabled => - MeasureCounter?.Enabled == true || - MeasureHistogram?.Enabled == true; - - internal bool IsMeasureDurationEnabled => MeasureHistogram?.Enabled == true; - - internal bool IsArrangeEnabled => - ArrangeCounter?.Enabled == true || - ArrangeHistogram?.Enabled == true; - - internal bool IsArrangeDurationEnabled => ArrangeHistogram?.Enabled == true; - /// public void Create(Meter meter) { @@ -53,52 +42,38 @@ public void Create(Meter meter) /// /// Records a measure operation with an optional duration and associated tags. /// - /// The duration of the measure operation in nanoseconds. - /// Whether a duration should be recorded. + /// The duration of the measure operation. /// The tags associated with the measure operation. - public void RecordMeasure(int duration, bool recordDuration, in TagList tagList) + public void RecordMeasure(TimeSpan? duration, in TagList tagList) { - if (MeasureCounter?.Enabled == true) - { - MeasureCounter.Add(1, tagList); - } + MeasureCounter?.Add(1, tagList); - if (recordDuration && MeasureHistogram?.Enabled == true) + if (duration is not null) { - MeasureHistogram.Record(duration, tagList); +#if NET9_0_OR_GREATER + MeasureHistogram?.Record((int)duration.Value.TotalNanoseconds, tagList); +#else + MeasureHistogram?.Record((int)(duration.Value.TotalMilliseconds * 1_000_000), tagList); +#endif } } /// /// Records an arrange operation with an optional duration and associated tags. /// - /// The duration of the arrange operation in nanoseconds. - /// Whether a duration should be recorded. + /// The duration of the arrange operation. /// The tags associated with the arrange operation. - public void RecordArrange(int duration, bool recordDuration, in TagList tagList) + public void RecordArrange(TimeSpan? duration, in TagList tagList) { - if (ArrangeCounter?.Enabled == true) - { - ArrangeCounter.Add(1, tagList); - } + ArrangeCounter?.Add(1, tagList); - if (recordDuration && ArrangeHistogram?.Enabled == true) + if (duration is not null) { - ArrangeHistogram.Record(duration, tagList); +#if NET9_0_OR_GREATER + ArrangeHistogram?.Record((int)duration.Value.TotalNanoseconds, tagList); +#else + ArrangeHistogram?.Record((int)(duration.Value.TotalMilliseconds * 1_000_000), tagList); +#endif } } - - internal static int GetElapsedNanoseconds(long startTimestamp) - { - var elapsedTimestamp = Stopwatch.GetTimestamp() - startTimestamp; - if (elapsedTimestamp <= 0) - { - return 0; - } - - var elapsedNanoseconds = elapsedTimestamp * (1_000_000_000.0 / Stopwatch.Frequency); - return elapsedNanoseconds >= int.MaxValue - ? int.MaxValue - : (int)elapsedNanoseconds; - } } diff --git a/src/Core/src/Diagnostics/Instrumentation/LayoutMeasureInstrumentation.cs b/src/Core/src/Diagnostics/Instrumentation/LayoutMeasureInstrumentation.cs index 8bd5fd7400bf..66c76480eb99 100644 --- a/src/Core/src/Diagnostics/Instrumentation/LayoutMeasureInstrumentation.cs +++ b/src/Core/src/Diagnostics/Instrumentation/LayoutMeasureInstrumentation.cs @@ -5,59 +5,21 @@ namespace Microsoft.Maui.Diagnostics; /// /// Instrumentation for measuring layout operations in a view. /// -readonly struct LayoutMeasureInstrumentation : System.IDisposable +readonly struct LayoutMeasureInstrumentation(IView view) : IDiagnosticInstrumentation { - readonly IView _view; - readonly IDiagnosticsManager _diagnostics; - readonly LayoutDiagnosticMetrics? _metrics; - readonly Activity? _activity; - readonly bool _metricsDurationStarted; - readonly long _metricsStartTimestamp; - - public LayoutMeasureInstrumentation(IView view, IDiagnosticsManager diagnostics, LayoutDiagnosticMetrics? metrics) - { - _view = view; - _diagnostics = diagnostics; - _metrics = metrics; - - if (diagnostics.HasActivityListeners) - { - diagnostics.GetTags(view, out var tagList); - _activity = diagnostics.ActivitySource.StartActivity( - ActivityKind.Internal, - name: $"Measure {view.GetType().Name}", - tags: tagList); - } - else - { - _activity = null; - } - - _metricsDurationStarted = metrics?.IsMeasureDurationEnabled == true; - _metricsStartTimestamp = _metricsDurationStarted - ? Stopwatch.GetTimestamp() - : 0; - } + readonly Activity? _activity = view.StartDiagnosticActivity("Measure"); /// /// Disposes the instrumentation and stops the diagnostic activity. /// - public void Dispose() - { - var metrics = _metrics; - var recordDuration = _metricsDurationStarted && metrics?.IsMeasureDurationEnabled == true; - var duration = recordDuration - ? LayoutDiagnosticMetrics.GetElapsedNanoseconds(_metricsStartTimestamp) - : 0; - - _activity?.Stop(); + public void Dispose() => + view.StopDiagnostics(_activity, this); - if (metrics?.IsMeasureEnabled == true) - { - _diagnostics.GetTags(_view, out var tagList); - metrics.RecordMeasure(duration, recordDuration, in tagList); - } - - _activity?.Dispose(); - } + /// + /// Records the stopping of the instrumentation and publishes various metrics. + /// + /// The instance. + /// The tags associated with the instrumentation. + public void Stopped(IDiagnosticsManager diagnostics, in TagList tagList) => + diagnostics.GetMetrics()?.RecordMeasure(_activity?.Duration, in tagList); } diff --git a/src/Core/tests/Benchmarks/Benchmarks/LayoutDiagnosticsBenchmarker.cs b/src/Core/tests/Benchmarks/Benchmarks/LayoutDiagnosticsBenchmarker.cs deleted file mode 100644 index 6d4fc1019ec0..000000000000 --- a/src/Core/tests/Benchmarks/Benchmarks/LayoutDiagnosticsBenchmarker.cs +++ /dev/null @@ -1,148 +0,0 @@ -using BenchmarkDotNet.Attributes; -using Microsoft.Maui.Controls; -using Microsoft.Maui.Controls.Hosting; -using Microsoft.Maui.Graphics; -using Microsoft.Maui.Hosting; - -namespace Microsoft.Maui.Benchmarks -{ - [MemoryDiagnoser] - public class LayoutDiagnosticsBenchmarker - { - const int SingleIterations = 1024; - const int SingleOperationsPerInvoke = SingleIterations * 2; - const int TreeChildCount = 100; - const int TreeIterations = 16; - const int TreeOperationsPerInvoke = TreeChildCount * TreeIterations * 2; - - static readonly Rect ArrangeBounds = new(0, 0, 100, 100); - static readonly Rect TreeBounds = new(0, 0, 100, 2400); - - MauiApp _app; - MauiContext _context; - IView _view; - ILayout _layout; - - [GlobalSetup] - public void Setup() - { - var builder = MauiApp.CreateBuilder(); - builder.UseMauiApp(); - - _app = builder.Build(); - _context = new MauiContext(_app.Services); - _view = CreateProbeView(_context); - - var layout = new VerticalStackLayout - { - Spacing = 0, - }; - - for (var i = 0; i < TreeChildCount; i++) - { - layout.Add(CreateProbeView(_context)); - } - - _layout = layout; - } - - [GlobalCleanup] - public void Cleanup() - { - _app?.Dispose(); - } - - [Benchmark(OperationsPerInvoke = SingleOperationsPerInvoke)] - public double MeasureAndArrangeViewNoListener() - { - double checksum = 0; - - for (var i = 0; i < SingleIterations; i++) - { - var measured = _view.Measure(100, 100); - var arranged = _view.Arrange(ArrangeBounds); - checksum += measured.Width + arranged.Height; - } - - return checksum; - } - - [Benchmark(OperationsPerInvoke = TreeOperationsPerInvoke)] - public double MeasureAndArrangeTreeNoListener() - { - double checksum = 0; - - for (var i = 0; i < TreeIterations; i++) - { - var measured = _layout.CrossPlatformMeasure(100, 2400); - var arranged = _layout.CrossPlatformArrange(TreeBounds); - checksum += measured.Width + arranged.Height; - } - - return checksum; - } - - static ProbeView CreateProbeView(IMauiContext context) - { - var view = new ProbeView - { - Handler = new ProbeHandler(context), - }; - - return view; - } - - sealed class ProbeView : View - { - protected override Size MeasureOverride(double widthConstraint, double heightConstraint) => - new(42, 24); - - protected override Size ArrangeOverride(Rect bounds) - { - Frame = bounds; - return bounds.Size; - } - } - - sealed class ProbeHandler(IMauiContext context) : IViewHandler - { - public bool HasContainer { get; set; } - - public object ContainerView => null; - - public object PlatformView => null; - - public IView VirtualView { get; private set; } - - IElement IElementHandler.VirtualView => VirtualView; - - public IMauiContext MauiContext { get; private set; } = context; - - public void SetMauiContext(IMauiContext mauiContext) => MauiContext = mauiContext; - - public void SetVirtualView(IElement view) - { - VirtualView = (IView)view; - } - - public void UpdateValue(string property) - { - } - - public void Invoke(string command, object args = null) - { - } - - public void DisconnectHandler() - { - } - - public Size GetDesiredSize(double widthConstraint, double heightConstraint) => - new(42, 24); - - public void PlatformArrange(Rect frame) - { - } - } - } -} diff --git a/src/Core/tests/UnitTests/Diagnostics/LayoutDiagnosticsTests.cs b/src/Core/tests/UnitTests/Diagnostics/LayoutDiagnosticsTests.cs deleted file mode 100644 index 3623157e922f..000000000000 --- a/src/Core/tests/UnitTests/Diagnostics/LayoutDiagnosticsTests.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.Metrics; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Maui.Controls; -using Microsoft.Maui.Diagnostics; -using Microsoft.Maui.Graphics; -using Microsoft.Maui.Hosting; -using Xunit; - -namespace Microsoft.Maui.UnitTests.Diagnostics -{ - [Category(TestCategory.Core)] - public class LayoutDiagnosticsTests - { - const int AllocationIterations = 1024; - const string MeasureDurationInstrumentName = "maui.layout.measure_duration"; - const string ArrangeDurationInstrumentName = "maui.layout.arrange_duration"; - - static readonly Rect ArrangeBounds = new Rect(0, 0, 100, 100); - - [Fact] - public void MeasureAndArrangeDoNotAllocateWithoutListeners() - { - using var app = CreateMauiApp(); - var view = CreateProbeView(new MauiContext(app.Services)); - var iView = (IView)view; - - for (var i = 0; i < 64; i++) - { - iView.Measure(100, 100); - iView.Arrange(ArrangeBounds); - } - - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - - var allocatedBefore = GC.GetAllocatedBytesForCurrentThread(); - for (var i = 0; i < AllocationIterations; i++) - { - iView.Measure(100, 100); - iView.Arrange(ArrangeBounds); - } - var allocated = GC.GetAllocatedBytesForCurrentThread() - allocatedBefore; - - Assert.Equal(0, allocated); - } - - [Theory] - [InlineData(MeasureDurationInstrumentName)] - [InlineData(ArrangeDurationInstrumentName)] - public void DurationMetricsRecordWithoutActivityListeners(string instrumentName) - { - var measurements = new List(); - - using var listener = new MeterListener(); - listener.InstrumentPublished = (instrument, meterListener) => - { - if (instrument.Meter.Name == "Microsoft.Maui" && instrument.Name == instrumentName) - { - meterListener.EnableMeasurementEvents(instrument); - } - }; - listener.SetMeasurementEventCallback((instrument, measurement, tags, state) => - { - if (instrument.Meter.Name == "Microsoft.Maui" && instrument.Name == instrumentName) - { - measurements.Add(measurement); - } - }); - listener.Start(); - - using var app = CreateMauiApp(); - var view = CreateProbeView(new MauiContext(app.Services)); - var iView = (IView)view; - - if (instrumentName == MeasureDurationInstrumentName) - { - iView.Measure(100, 100); - } - else - { - iView.Arrange(ArrangeBounds); - } - - var measurement = Assert.Single(measurements); - Assert.True(measurement >= 0); - } - - [Fact] - public void ElapsedNanosecondsClampsToIntMaxValue() - { - var startTimestamp = Stopwatch.GetTimestamp() - (Stopwatch.Frequency * 3); - - var elapsedNanoseconds = LayoutDiagnosticMetrics.GetElapsedNanoseconds(startTimestamp); - - Assert.Equal(int.MaxValue, elapsedNanoseconds); - } - - [Fact] - public void ElapsedNanosecondsReturnsZeroForNonPositiveElapsedTime() - { - var startTimestamp = Stopwatch.GetTimestamp() + Stopwatch.Frequency; - - var elapsedNanoseconds = LayoutDiagnosticMetrics.GetElapsedNanoseconds(startTimestamp); - - Assert.Equal(0, elapsedNanoseconds); - } - - static MauiApp CreateMauiApp() - { - var builder = MauiApp.CreateBuilder(); - builder.Services.AddSingleton(); - - return builder.Build(); - } - - static ProbeView CreateProbeView(IMauiContext context) - { - var view = new ProbeView - { - Handler = new ProbeHandler(context), - }; - - return view; - } - - sealed class ProbeView : View - { - protected override Size MeasureOverride(double widthConstraint, double heightConstraint) => - new Size(42, 24); - - protected override Size ArrangeOverride(Rect bounds) => - bounds.Size; - } - - sealed class ProbeHandler : IViewHandler - { - public ProbeHandler(IMauiContext context) - { - MauiContext = context; - } - - public bool HasContainer { get; set; } - - public object ContainerView => null; - - public object PlatformView => null; - - public IView VirtualView { get; private set; } - - IElement IElementHandler.VirtualView => VirtualView; - - public IMauiContext MauiContext { get; private set; } - - public void SetMauiContext(IMauiContext mauiContext) => - MauiContext = mauiContext; - - public void SetVirtualView(IElement view) => - VirtualView = (IView)view; - - public void UpdateValue(string property) - { - } - - public void Invoke(string command, object args = null) - { - } - - public void DisconnectHandler() - { - } - - public Size GetDesiredSize(double widthConstraint, double heightConstraint) => - new Size(42, 24); - - public void PlatformArrange(Rect frame) - { - } - } - - sealed class TestMeterFactory : IMeterFactory - { - readonly List _meters = new List(); - - public Meter Create(MeterOptions options) - { - var meter = new Meter(options); - _meters.Add(meter); - - return meter; - } - - public void Dispose() - { - foreach (var meter in _meters) - { - meter.Dispose(); - } - } - } - } -}