Skip to content

[LayoutDiagnostics] Added runtime layout diagnostics API for profiling measure/arrange counts per element type. Includes a floating overlay visible over modals and bottom sheets, automatic snapshot capture during page and bottom sheet lifecycle, per-instance thrashing detection, and JSON export.#849

Open
Vetle444 wants to merge 9 commits intomainfrom
vefi/diagnostics

Conversation

@Vetle444
Copy link
Copy Markdown
Contributor

Description of Change

Add a runtime layout diagnostics API (LayoutDiagnosticsService) for profiling .NET MAUI layout performance on iOS and Android.

What it does:

  • Hooks into MAUI's System.Diagnostics.Metrics instruments (maui.layout.measure_count, maui.layout.arrange_count) via MeterListener to count every measure and arrange call per element type and instance
  • Automatically captures snapshots during ContentPage and BottomSheet lifecycle (OnNavigatedTo/OnAppearing → OnDisappearing)
  • Flags types exceeding an average of 3 layout passes per instance as thrashing warnings
  • Provides JSON export for offline analysis

Floating overlay:

  • Draggable pill that stays visible over all content including modals and bottom sheets
  • iOS: Separate UIWindow at UIWindowLevel.Alert - 1 with touch passthrough via HitTest override, pinned full-screen so MAUI handles internal layout
  • Android: MatchParent layout with MAUI-managed positioning + automatic re-parenting to dialog DecorView via centralized FragmentLifeCycleCallback with dialog stack tracking for nested dialogs

Background — Measure & Arrange:
MAUI uses a two-phase layout process (Microsoft Learn):

  1. MeasureIView.Measure() calculates desired size given constraints. May be called multiple times speculatively.
  2. ArrangeIView.Arrange() assigns final position and bounds.

Deeply nested layouts with complex constraints can cause exponential re-measurement (layout thrashing). This tool makes that visible at runtime.

Public API:

  • LayoutDiagnosticsService — Static service: Initialize(), Teardown(), Enable(), Disable(), Toggle(), ClearSnapshots(), ExportAllToJson(), IsEnabled, IsInitialized, CompletedSnapshots, CurrentSnapshot, SnapshotCompleted event
  • LayoutDiagnosticsSnapshot — Per-navigation snapshot: SourceName, TotalMeasureCount, TotalArrangeCount, MeasureCountsByType, MeasureInstancesByType, Warnings, ToJson(), ToCompactString(), ToDetailedString()

Files added (library):

  • API/Diagnostics/LayoutDiagnosticsService.cs — Shared service with MeterListener
  • API/Diagnostics/LayoutDiagnosticsSnapshot.cs — Snapshot data model
  • API/Diagnostics/LayoutDiagnosticsOverlay.cs — Overlay UI using Border + RoundRectangle, design tokens
  • API/Diagnostics/iOS/LayoutDiagnosticsService.cs — iOS UIWindow approach (full-screen pinning, HitTest passthrough)
  • API/Diagnostics/Android/LayoutDiagnosticsService.cs — Android MatchParent + dialog elevation
  • API/Diagnostics/dotnet/LayoutDiagnosticsService.cs — Desktop stub

Files modified (library):

  • FragmentLifeCycleCallback.cs — Centralized dialog fragment lifecycle handling for overlay z-ordering with dialog stack
  • ContentPage.cs — Auto BeginSnapshot/EndSnapshot on navigation
  • BottomSheet.cs — Auto BeginSnapshot/EndSnapshot on open/close

Prerequisites: builder.Services.AddMetrics() in MauiProgram.cs

Copilot AI review requested due to automatic review settings April 24, 2026 11:54
@Vetle444 Vetle444 enabled auto-merge (squash) April 24, 2026 12:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new runtime “Layout Diagnostics” feature to DIPS.Mobile.UI to profile .NET MAUI layout behavior (measure/arrange counts) at runtime, including an always-on-top overlay and automatic snapshot capture tied to page/bottom sheet lifecycles.

Changes:

  • Introduces LayoutDiagnosticsService + LayoutDiagnosticsSnapshot for capturing and exporting layout metric snapshots.
  • Adds platform-specific overlay hosting (iOS UIWindow, Android re-parenting above dialogs) and hooks overlay z-ordering into Android fragment lifecycle.
  • Updates apps (Components/Playground) to enable MAUI metrics and adds sample “Diagnostics” pages + changelog/wiki/prompt updates.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
wiki/Layout-Diagnostics.md New wiki page documenting the layout diagnostics feature and usage.
src/library/DIPS.Mobile.UI/Components/Pages/ContentPage.cs Auto begin/end snapshot hooks added to page lifecycle.
src/library/DIPS.Mobile.UI/Components/BottomSheets/BottomSheet.cs Auto begin/end snapshot hooks added to bottom sheet open/close.
src/library/DIPS.Mobile.UI/API/Library/Android/FragmentLifeCycleCallback.cs Tracks dialog stack and elevates/restores overlay above dialogs.
src/library/DIPS.Mobile.UI/API/Diagnostics/LayoutDiagnosticsService.cs New shared diagnostics service using MeterListener + snapshot management.
src/library/DIPS.Mobile.UI/API/Diagnostics/LayoutDiagnosticsSnapshot.cs New snapshot model, warnings analysis, and JSON export helpers.
src/library/DIPS.Mobile.UI/API/Diagnostics/LayoutDiagnosticsOverlay.cs New MAUI overlay UI (pill + expanded panel) for controlling/viewing diagnostics.
src/library/DIPS.Mobile.UI/API/Diagnostics/iOS/LayoutDiagnosticsService.cs iOS overlay implementation hosted in a separate passthrough UIWindow.
src/library/DIPS.Mobile.UI/API/Diagnostics/Android/LayoutDiagnosticsService.cs Android overlay implementation added to activity content / dialog decor view.
src/library/DIPS.Mobile.UI/API/Diagnostics/dotnet/LayoutDiagnosticsService.cs Desktop stub partial implementation.
src/app/Playground/Playground.csproj Adds diagnostics package dependency needed for AddMetrics().
src/app/Playground/MauiProgram.cs Registers metrics (AddMetrics) for MAUI diagnostics instruments.
src/app/Playground/DiagnosticsSamples/LayoutDiagnosticsPage.xaml New Playground diagnostics UI page (XAML).
src/app/Playground/DiagnosticsSamples/LayoutDiagnosticsPage.xaml.cs New Playground diagnostics UI logic and snapshot rendering.
src/app/Playground/App.xaml.cs Adds a Diagnostics tab to the Playground Shell.
src/app/Components/Components.csproj Adds diagnostics package dependency needed for AddMetrics().
src/app/Components/MauiProgram.cs Registers metrics (AddMetrics) for MAUI diagnostics instruments.
src/app/Components/DiagnosticsSamples/LayoutDiagnosticsPage.xaml New Components-app diagnostics UI page (XAML).
src/app/Components/DiagnosticsSamples/LayoutDiagnosticsPage.xaml.cs New Components-app diagnostics UI logic and snapshot rendering.
src/app/Components/App.xaml.cs Adds a Diagnostics tab to the Components Shell.
src/Directory.Packages.props Adds central package version for Microsoft.Extensions.Diagnostics.
global.json Bumps pinned .NET SDK version.
CHANGELOG.md Adds a minor version entry for the new diagnostics feature.
.github/skills/design-system-usage/SKILL.md Adds guidance for design token/style usage in C# UI.
.github/prompts/enable-layout-diagnostics.prompt.md Adds a how-to prompt for enabling layout diagnostics in an app.
Comments suppressed due to low confidence (1)

src/library/DIPS.Mobile.UI/Components/Pages/ContentPage.cs:88

  • Snapshots are started in OnAppearing() but ended in OnNavigatingFrom(). This doesn't match the PR description/wiki (which reference OnNavigatedTo/OnDisappearing), and it can leave a snapshot running across non-navigation disappear scenarios (e.g., a modal covering the page) until another snapshot begins or diagnostics are disabled. Consider ending the page snapshot in OnDisappearing() (and updating docs accordingly) to ensure snapshots are always closed when the page is no longer visible.
        protected override void OnAppearing()
        {
            base.OnAppearing();

            HasAppeared = true;

            // Complete previous snapshot (captures outgoing page + transition layout)
            // and start new snapshot for this page's ongoing layout
            LayoutDiagnosticsService.BeginSnapshot($"Page: {GetType().Name}");

            HideOrShowFloatingNavigationMenu();
            
            
#if __ANDROID__
            // Update status bar color for this page (works for both modal and non-modal)
            StatusBarHandler.TrySetStatusBarColor(this, StatusBarColor);
#endif
        }
        

Comment thread wiki/Layout-Diagnostics.md Outdated
Comment thread wiki/Guides/Layout-Diagnostics.md
Comment thread .github/prompts/enable-layout-diagnostics.prompt.md Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
auto-merge was automatically disabled April 27, 2026 06:46

Head branch was pushed to by a user without write access

@Vetle444
Copy link
Copy Markdown
Contributor Author

@copilot Fix all remaining comments

- LayoutDiagnosticsOverlay: remove unused Icons using, replace FontSize=12
  with Sizes.GetSize(SizeName.size_3), remove MinimumWidthRequest=220 magic
  number, replace hardcoded top margin 54 with size_14 token
- Move wiki/Layout-Diagnostics.md → wiki/Guides/Layout-Diagnostics.md
- Playground/App.xaml.cs: remove unused DIPS.Mobile.UI.API.Diagnostics using
- enable-layout-diagnostics.prompt.md: fix reference file paths to actual
  DiagnosticsSamples/LayoutDiagnosticsPage.xaml locations
- FragmentLifeCycleCallback: call base.OnFragmentDestroyed unconditionally

Agent-Logs-Url: https://github.com/DIPSAS/DIPS.Mobile.UI/sessions/4e81c711-23bd-4e18-9d04-932d3010ea73

Co-authored-by: Vetle444 <35739538+Vetle444@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 27, 2026

@copilot Fix all remaining comments

All remaining comments addressed in 9632452:

  • LayoutDiagnosticsOverlay.cs: removed unused Icons using; replaced hardcoded FontSize = 12 with Sizes.GetSize(SizeName.size_3); removed magic MinimumWidthRequest = 220; replaced top margin 54 with Sizes.GetSize(SizeName.size_14)
  • Wiki: moved wiki/Layout-Diagnostics.mdwiki/Guides/Layout-Diagnostics.md (alongside Performance.md and Memory-Leaks.md)
  • Playground/App.xaml.cs: removed unused DIPS.Mobile.UI.API.Diagnostics using
  • enable-layout-diagnostics.prompt.md: fixed reference paths to DiagnosticsSamples/LayoutDiagnosticsPage.xaml
  • FragmentLifeCycleCallback.cs: moved base.OnFragmentDestroyed outside the if (f is DialogFragment) block so it's called unconditionally

@Vetle444 Vetle444 enabled auto-merge (squash) April 27, 2026 06:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants