Skip to content

Fix MAUI Android Inner Loop startup measurement to use logcat Displayed time#5184

Closed
davidnguyen-tech wants to merge 15 commits intonguyendav/maui-android-innerloopfrom
nguyendav/fix-startup-measurement
Closed

Fix MAUI Android Inner Loop startup measurement to use logcat Displayed time#5184
davidnguyen-tech wants to merge 15 commits intonguyendav/maui-android-innerloopfrom
nguyendav/fix-startup-measurement

Conversation

@davidnguyen-tech
Copy link
Copy Markdown
Member

@davidnguyen-tech davidnguyen-tech commented Mar 30, 2026

Problem

Mono and CoreCLR startup times were nearly identical

Root Cause

The previous implementation used am start -W stdout TotalTime/WaitTime which captures only the initial window draw time. This is runtime-agnostic and doesn't reflect the full app rendering, which is where runtime differences manifest. Additionally, there was no cold start guarantee.

davidnguyen-tech and others added 15 commits March 30, 2026 13:10
TargetFrameworks and SupportedOSPlatformVersion are now defined in
the .proj PropertyGroup where all build configuration is visible.
setup_helix.py just passes through PERFLAB_MSBUILD_ARGS unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove PERFLAB_MSBUILD_ARGS env var from setup_helix.py. The .proj now
passes _MSBuildArgs directly to both setup_helix.py (for workload/restore)
and test.py (for build) through their own argument contracts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Environment variables set via os.environ in Python don't persist
across && shell chains. Move DOTNET_ROOT, PATH, ANDROID_HOME,
JAVA_HOME, and other env vars to the .proj PreCommands where
set/export makes them available to all chained commands.

Uses ;-separated PreCommands format consistent with the Helix SDK
convention (DotNetCli.targets, XHarnessRunner.targets). Windows PATH
separators escaped as %3B to avoid being split as command separators.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The --edit-src and --edit-dest values contain semicolons that bash
interprets as command separators on Linux, truncating the test.py
command line. Quoting these values fixes the emulator job failures.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Helix SDK decodes %3B in $(HelixPreCommands) but not in
per-work-item <PreCommands> metadata. On Windows, this caused PATH
to contain literal %3B instead of semicolons, breaking tool resolution.
Follow the same pattern as DotNetCli.targets and XHarnessRunner.targets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
!$(IsPosixShell) fails with MSB4100 when the property is empty.
Use quoted string comparison instead: '$(IsPosixShell)' == 'true'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extract setup_helix.py invocation from the chained Command (setup_helix.py
&& test.py) into a dedicated <PreCommands> element for each HelixWorkItem.
This leverages the Helix SDK's execution order — PreCommands runs before
Command in the same shell session — giving clearer separation of setup
vs. test execution.

The setup_helix.py call contains no semicolons, so there is no %3B
encoding concern for per-item PreCommands.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Helix SDK's AddDotNetSdk target (triggered by IncludeXHarnessCli) appends
DOTNET_ROOT and PATH pointing to .NET 8.0 during target execution. When our
env var overrides were in a PropertyGroup, they were evaluated during property
evaluation (earlier), so the SDK's values won — last set/export wins.

Moving the env var exports into a Target with AfterTargets="AddXHarnessCli"
ensures our .NET 11 SDK paths are appended after the SDK's .NET 8.0 paths,
so our values take precedence.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Targets

Having both BeforeTargets="CoreTest" and AfterTargets="AddXHarnessCli" causes
MSBuild to schedule the target through BeforeTargets import order, which places
it BEFORE AddDotNetSdk. The result is that AddDotNetSdk's .NET 8.0 env vars
come after ours and win.

Removing BeforeTargets="CoreTest" forces MSBuild to schedule purely through
the AfterTargets chain, guaranteeing it runs after both AddDotNetSdk and
AddXHarnessCli. It still runs before CoreTest because AddXHarnessCli itself
has BeforeTargets="CoreTest".

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…caping

Move env var exports (DOTNET_ROOT, PATH, ANDROID_HOME, etc.) from the
OverrideDotnetCliRoot MSBuild Target into per-item <PreCommands> metadata
on each HelixWorkItem. Per-item PreCommands run AFTER $(HelixPreCommands)
in the same shell session, so they naturally override the Helix SDK's
AddDotNetSdk values without needing Target ordering (AfterTargets).

For Windows PATH values, use ;; (double semicolons) which the Helix SDK's
SplitCommands() method treats as literal semicolons (single ; is a command
separator). Linux PATH uses : as separator so no escaping is needed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…adability

Move the long inline env var strings from per-item <PreCommands> into
_WindowsEnvVars and _LinuxEnvVars MSBuild properties. This improves
readability and fixes an ordering bug: env vars now come BEFORE
setup_helix.py so that PATH (including adb) is available when
setup_helix.py runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…guarantee

Switch measure_startup() from am-start stdout TotalTime/WaitTime to logcat
Displayed time as the primary timing source, matching the proven DEVICESTARTUP
pattern. The old approach captured time to initial window draw (runtime-agnostic,
~3s), while logcat Displayed time captures full app rendering including framework
initialization (runtime-dependent, Mono ~6s vs CoreCLR ~9s).

Also fix cold start guarantee: remove -S flag from am start-activity, add
separate force-stop + 3s delay before each start, and add LaunchState: COLD
verification.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add --startup-iterations CLI argument (default=3) for configurable multi-
iteration startup measurement. Each first-deploy now captures 3 startup
data points for statistical reliability. Incremental iterations keep 1
measurement per cycle (the incremental loop itself provides multiple points).

Add post-startup runtime validation that checks logcat for Mono/CoreCLR
markers and logs whether the expected runtime loaded (diagnostic only).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make logcat grep commands tolerant of non-zero exit codes (grep exits 1
when no matches found, which would crash RunCommand before fallback logic
could execute). Add WaitTime as secondary fallback after TotalTime for
Android 14+ compatibility where TotalTime may be absent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidnguyen-tech davidnguyen-tech deleted the nguyendav/fix-startup-measurement branch March 31, 2026 09:05
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.

1 participant