Add MAUI iOS Inner Loop measurements for CI#5187
Add MAUI iOS Inner Loop measurements for CI#5187davidnguyen-tech wants to merge 69 commits intodotnet:mainfrom
Conversation
- Create iOSInnerLoopParser.cs: binlog parser for iOS inner loop build timings, extracting iOS-specific tasks (AOTCompile, Codesign, MTouch, etc.) and targets (_AOTCompile, _CodesignAppBundle, _CreateAppBundle, etc.) plus shared tasks (Csc, XamlC, LinkAssembliesNoShrink) - Wire into Startup.cs: add iOSInnerLoop to MetricType enum and map it to iOSInnerLoopParser in the parser switch expression - Fix Reporter.cs: guard against null/empty PERFLAB_BUILDTIMESTAMP to prevent ArgumentNullException on DateTime.Parse(null) when the env var is unset (falls back to DateTime.Now) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- const.py: Add IOSINNERLOOP constant and SCENARIO_NAMES mapping - ioshelper.py: New module with iOSHelper class for simulator and physical device management (boot, install, launch, terminate, uninstall, find bundle) - runner.py: Add iosinnerloop subparser, attribute assignment, and full execution branch (first build+deploy+launch, incremental loop with source toggling, binlog parsing, report aggregation, and Helix upload) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
pre.py: Install maui-ios workload, create MAUI template (no-restore for Helix), strip non-iOS TFMs with flexible regex, inject MSBuild properties (AllowMissingPrunePackageData, UseSharedCompilation), copy merged NuGet.config for Helix-side restore, create modified source files for incremental edit loop, check Xcode compatibility. test.py: Thin entrypoint that builds TestTraits and invokes Runner. post.py: Uninstall app from simulator, shut down dotnet build server, clean directories. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create the Helix machine setup script for MAUI iOS inner loop measurements. This script runs on the macOS Helix machine before test.py and handles: 1. DOTNET_ROOT/PATH configuration from the correlation payload SDK 2. Xcode selection — auto-detects highest versioned Xcode_*.app, matching the pattern used by maui_scenarios_ios.proj PreparePayloadWorkItem 3. iOS simulator runtime validation via xcrun simctl 4. Simulator device boot with graceful already-booted handling 5. maui-ios workload install using rollback file from pre.py, with --ignore-failed-sources for dead NuGet feeds 6. NuGet package restore with --ignore-failed-sources /p:NuGetAudit=false 7. Spotlight indexing disabled via mdutil to prevent file-lock errors Follows the same structure as the Android inner loop setup_helix.py: context dict pattern, step-by-step functions, structured logging to HELIX_WORKITEM_UPLOAD_ROOT for post-mortem debugging. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Define the Helix .proj file for iOS inner loop measurements, modeled after the Android inner loop .proj and existing maui_scenarios_ios.proj patterns. Key design decisions: - Build on Helix machine (not build agent) because deploy requires a connected device/simulator. PreparePayloadWorkItem only creates the template and modified source files via pre.py. - Workload packs stripped from correlation payload (RemoveDotnetFromCorrelation Staging) and reinstalled on Helix machine by setup_helix.py. - Environment variables set via shell 'export' in PreCommands (not in Python) because os.environ changes don't persist across process boundaries. - No XHarness — iOS inner loop uses xcrun simctl directly. - Simulator-only for now; physical device support (ios-arm64, code signing) is structured as a future TODO pending runner.py device support. - 01:30 timeout to accommodate iOS build + workload install + NuGet restore. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- sdk-perf-jobs.yml: Add Mono Debug job entry for maui_scenarios_ios_innerloop on osx-x64-ios-arm64 (Mac.iPhone.17.Perf queue) - run-performance-job.yml: Add maui_scenarios_ios_innerloop to the in() check so --runtime-flavor is forwarded to run_performance_job.py - run_performance_job.py: Add maui_scenarios_ios_innerloop to get_run_configurations() (CodegenType, RuntimeType, BuildConfig) and to the binlog copy block for PreparePayloadWorkItems artifacts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ioshelper.py: Add detect_connected_device() with auto-detection via xcrun devicectl (JSON + fallback text parsing), uninstall_app_physical, terminate_app_physical, close_physical_device, and cleanup() dispatch - runner.py: Add --device-type arg (simulator/device) to iosinnerloop subparser, auto-infer from RuntimeIdentifier, auto-detect device UDID, branch setup/install/startup/cleanup for physical vs simulator - setup_helix.py: Detect device type from IOS_RID env var, skip simulator boot for physical device, add detect_physical_device() for Helix - post.py: Handle physical device uninstall via devicectl with UDID auto-detection fallback - maui_scenarios_ios_innerloop.proj: Add physical device HelixWorkItem (conditioned on iOSRid=ios-arm64), pass IOS_RID to Pre/PostCommands, add --device-type arg to both simulator and device workitems Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e sort Fix 1 (Major): Replace non-existent 'devicectl terminate --bundle-id' with '--terminate-existing' flag on launch command. Make terminate_app_physical() a no-op with documentation explaining why. Fix 2 (Medium): Write devicectl JSON output to temp file instead of /dev/stdout, which mixes human-readable table and JSON. Applied in both ioshelper.py and setup_helix.py with proper temp file cleanup. Fix 3 (Medium): Add standard UUID pattern (8-4-4-4-12) to UDID regex in _detect_device_fallback() for CoreDevice UUID format compatibility. Fix 4 (Medium): Normalize MAUI template to always use Pages/ subdirectory in pre.py. If template puts MainPage files at root, move them to Pages/. Add explanatory comment in .proj documenting the coupling. Fix 5 (Minor): Use tuple-of-ints version sort for Xcode selection instead of string comparison (fixes 16.10 < 16.2 ordering bug). Fix 6 (Minor): Make simulator boot failure fatal with sys.exit(1). Add dynamic fallback to latest available iPhone simulator before failing. Fix 7 (Nit): Add missing trailing newline to runner.py. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace deprecated tempfile.mktemp() with tempfile.mkstemp() in both ioshelper.py and setup_helix.py to avoid TOCTOU race condition. - Fix unreachable fallback in detect_connected_device(): when devicectl exits non-zero (e.g., older Xcode without --json-output), call _detect_device_fallback() instead of returning None immediately. - Guard against missing JSON report in runner.py IOSINNERLOOP branch: Startup.cs only writes reports when PERFLAB_INLAB=1, so local runs would crash with FileNotFoundError. Now degrades gracefully with empty counters and a warning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Temporarily disable all other scenario jobs to speed up CI iteration while validating the new MAUI iOS Inner Loop scenario. This change should be reverted before merging. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Capture dotnet build output instead of crashing on CalledProcessError - Create traces/ directory before first build - Fix setup_helix.py to write output.log (matches .proj expectation) - Improve error handling for build failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The dotnet build stdout/stderr wasn't appearing in Helix console logs, making it impossible to diagnose build failures. Explicitly capture and print build output through Python logging. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Build 2943141 hit the 90-minute timeout. iOS first build with AOT compilation can take 30+ minutes, plus 3 incremental iterations. Increasing to 2.5 hours to allow full completion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Helix machines have Xcode 26.2 but the iOS SDK requires 26.3. The minor version difference shouldn't affect build correctness, so bypass the check with ValidateXcodeVersion=false. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mac.iPhone.17.Perf queue uses Intel x64 machines which need iossimulator-x64, not iossimulator-arm64. Add architecture detection in setup_helix.py and update default RID in .proj. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The traces upload directory already exists from the first build, causing copytree() to fail on subsequent iterations. Clear it before each parsetraces() call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The -v:n flag was added to debug build errors but produces excessive file copy logs. Default verbosity shows errors/warnings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add /p:MtouchLink=None to disable managed linker for Debug inner loop builds, avoiding MT0180 errors on machines without Xcode 26.3 - Add minimum Xcode version check in setup_helix.py for fast failure with clear diagnostics when machine has Xcode < 26.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove temporary ${{ if false }}: wrappers that disabled all jobs
except iOS inner loop during iterative CI debugging.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…loop - Separate install from simulator/device setup in ioshelper.py - Capture install time for first and incremental deploys in runner.py - Add "Install Time" counter to both perf reports - Add CoreCLR Debug job entry in pipeline YAML - Add device (ios-arm64) job entries for both Mono and CoreCLR - Wire iOSRid env var through to MSBuild for device builds - [TEMP] Disable non-iOS-inner-loop jobs for CI validation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When the dynamically-resolved manifest references SDK packs not yet propagated to NuGet feeds, fall back to installing without the rollback file. This avoids CI being blocked by transient feed propagation delays. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When the rollback file references SDK packs not yet propagated to NuGet feeds, retry without the rollback file. Matches the fallback pattern already added to pre.py. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The simulator HelixWorkItem was unconditionally included, even when iOSRid=ios-arm64. This caused the simulator to receive device RID in _MSBuildArgs, producing ARM64 binaries that can't install on a simulator. Add Condition to exclude it from device jobs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t.py Consolidate 12 duplicated simulator/device methods in ioshelper.py into a unified API (setup_device, install_app, measure_cold_startup, cleanup) that dispatches internally based on is_physical_device. Removes all if-is_physical dispatch branches from runner.py. Extract merge_build_deploy_and_startup and _make_counter to module-level helpers. Inline the incremental iteration loop (was a nested function with 10 parameters). Simplify post.py to reuse ioshelper instead of duplicating device detection. Extract inject_csproj_properties in pre.py. Net reduction: -232 lines across 4 files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ling Re-apply run_env_vars (including iOSRid) right before perf_send_to_helix() so the MSBuild .proj ItemGroup conditions can correctly exclude the simulator work item from device jobs. Add '|| exit $?' to setup_helix.py PreCommands so that when setup_helix exits non-zero (e.g., Xcode too old), the Helix shell stops instead of continuing to run test.py which would fail with a less clear error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Env var inheritance through msbuild.sh/tools.sh is unreliable for iOSRid. Add ios_rid field to PerfSendToHelixArgs and pass it as /p:iOSRid=<value> on the MSBuild command line so it reaches .proj evaluation deterministically. Also set it via set_environment_variables as belt-and-suspenders. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mono_InnerLoop → Mono_InnerLoop_Simulator CoreCLR_InnerLoop → CoreCLR_InnerLoop_Simulator Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace simctl (simulator) and devicectl (device) install/launch commands with mlaunch to match the real Visual Studio F5 developer experience: - Simulator: --launchsim combines install + launch (install_app returns 0) - Device: --installdev for install, --launchdev for launch - Device cleanup: --uninstalldevbundleid replaces devicectl uninstall - Simulator cleanup: unchanged (simctl terminate + uninstall) - Added _resolve_mlaunch() to find mlaunch from iOS SDK packs Device detection (devicectl) and simulator management (simctl boot/ terminate/uninstall) remain unchanged. The install_app/measure_cold_startup API is preserved so runner.py requires no changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of making install_app() a no-op for simulator, use mlaunch --installsim to get a separate install measurement. measure_cold_startup() still uses --launchsim for launch timing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…stall After installing the maui-ios workload, read _RecommendedXcodeVersion from the SDK's Versions.props and switch to the matching Xcode_*.app if the currently active Xcode doesn't match. This handles the case where Helix agents have a newer Xcode than the SDK requires. Falls back gracefully to the already-selected Xcode if no matching installation is found. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use --from-rollback-file for workload install to get latest nightly builds. Make Xcode handling diagnostic-only (log version, no switching) since the SDK validates Xcode compatibility at build time. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The dotnet CLI can hang for hours when NuGet feeds are slow or broken (internal download retries). On build 2946062, CoreCLR Simulator's workload install hung for 2h28m on DNCENGMAC044 due to download failures for microsoft.netcore.app.runtime.aot.osx-x64.cross.iossimulator-x64. The fallback retry started 2 min before the Helix work item timeout and was killed. Add _run_workload_cmd() wrapper with a 20-minute timeout per attempt. Total worst case is 40 min for both attempts, leaving 1:50 for actual test execution within the 2:30 Helix work item timeout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pin microsoft.net.sdk.ios to 26.2.11591-net11-p4 (preview.3 band) to bypass the Xcode 26.4 requirement of the latest nightly packs. This allows CI iteration on the Helix machines which have Xcode 26.2. Cross-band note: the Helix SDK is preview.4 but the 26.2.x manifests only exist on the preview.3 band. Using --from-rollback-file to attempt cross-band installation. TODO: Remove this pin when Helix machines have Xcode 26.4. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Helix machines in Mac.iPhone.17.Perf may default to Xcode 15.0 while the iOS SDK packs (26.2.x) require Xcode >= 26.0. The old select_xcode() only logged the version diagnostically, so the mismatch wasn't caught until _ValidateXcodeVersion failed 20+ minutes later during the build. Now select_xcode(): - Finds /Applications/Xcode_*.app dirs, sorted by version number - Activates the highest one via sudo xcode-select -s - Falls back to the system default if no Xcode_*.app found - Parses and validates the version is >= 26.0 - Fails fast with a clear error if the minimum isn't met Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ix.py Refinements from code review: - Check default Xcode first; only search Xcode_*.app if default < 26 - Log xcode-select -p before AND after selection for diagnostics - Add comment explaining why >= 26 is hardcoded (packs not yet installed at this point so we can't read _RecommendedXcodeVersion) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…est — validate post-switch version Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When the iOS workload manifest declares net10.0 cross-targeting packs that don't exist on any NuGet feed, the standard --from-rollback-file install fails. This adds a fallback that: 1. Tries --from-rollback-file first (works when all packs are published) 2. If that fails, downloads the manifest nupkg from the NuGet flat container 3. Patches WorkloadManifest.json to remove net10.0 entries from workloads.ios.packs, workloads.ios.extends, and top-level packs 4. Places the patched manifest on disk under sdk-manifests/ 5. Installs with --skip-manifest-update Also fixes feed variable scoping: when the fallback feed is used during package resolution, the feed variable now reflects the actual feed used (needed for downloading the manifest nupkg in the patching fallback). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Handle workloads.ios.packs as list or dict (manifest format varies) - Add safety check for top-level packs being unexpected type - Add timeout=60 to urlopen for service index fetch - Replace urlretrieve with urlopen+write for nupkg download (timeout=120) - Narrow except clause from Exception to subprocess.CalledProcessError - Log warning when manifest patching removes zero entries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The install_workload() fallback path (--skip-manifest-update) already
works correctly with pre.py's manifest-patching approach:
- pre.py patches the iOS manifest to remove net10.0 entries and places
it in the SDK tree at $DOTNET_ROOT/sdk-manifests/{band}/...
- The SDK tree ships to Helix as the correlation payload
- The --skip-manifest-update retry tells the CLI to use on-disk
manifests, picking up the patched version
No functional changes — updated docstring and inline comments to
document why the fallback works and its dependency on pre.py.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix startup measurement: use SpringBoard watchdog events via log collect instead of timing the devicectl command wall-clock - Fix device UDID detection: read hardwareProperties.udid instead of CoreDevice identifier from devicectl JSON - Use devicectl directly for physical device install/launch (mlaunch tunnel failures on local machines) - Skip post-build signing on local runs (MSBuild handles it) - Prefix binlog filenames with RUNTIME_FLAVOR to prevent overwrites - Log SDK versions (dotnet --info + versions.json from linked DLLs) matching the pattern used by other MAUI scenarios Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ugs, reduce parser boilerplate - Refactor install_latest_maui() to accept workloads and workload_id params, eliminating ~80 lines of duplicated workload resolution logic in pre.py - Fix feed resolution at import-time bug (now resolved at call time) - Fix simulator startup timeout silently returning elapsed time instead of raising - Fix "Time to Main" metric mislabel — device measures total cold startup (time-to-main + time-to-first-draw), renamed to "Cold Startup Time" - Fix hardcoded iossimulator-arm64 RID — now uses IOS_RID env var for version extraction on Intel Helix machines - Fix setup_helix.py device detection to prefer hardware UDID over CoreDevice identifier, matching ioshelper.py behavior required by mlaunch - Reduce iOSInnerLoopParser.cs from 224 to 105 lines using dictionary-driven task/target collection instead of 30+ separate List<double> variables - Add duplication documentation comment on setup_helix.py detect_physical_device Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sign_app_for_device() now warns and skips re-signing when the Helix-specific 'sign' tool is not found, instead of raising FileNotFoundError. For local builds, MSBuild/Xcode already signs the app with the developer's identity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Simplified orchestration script (~226 lines) that delegates to the repo's existing init.sh, pre.py, test.py, and post.py instead of reimplementing their logic. Supports --configs, --device-type, --iterations, --skip-setup, and --dry-run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
select_xcode.py uses a 3-tier strategy to find the correct Xcode: 1. rollback_maui.json (created by pre.py during workload install) 2. SDK pack _RecommendedXcodeVersion (from installed workload) 3. Highest available >= minimum version (fallback) run-local.sh now calls this helper instead of naively picking the highest Xcode version, which could mismatch the SDK. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy .binlog files from traces/ to results/<runtime>/ before cleanup, so they persist alongside the JSON reports. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve SDK/runtime version metadata alongside binlogs and JSON reports in results/<runtime>/. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
setup_device() already uninstalls stale apps on simulator but skipped physical devices. A leftover install could affect first-deploy timing. Uses mlaunch --uninstalldevbundleid (best-effort, matching cleanup() pattern). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a previous run is interrupted, leftover obj/ directories cause XAML source generator duplicate errors on the next build. Clean app/ and traces/ before running pre.py when --skip-setup is not set. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
measure_cold_startup() returns -1 when watchdog event parsing fails on physical devices (fewer than 4 events found). Without validation, this invalid sentinel value was silently appended to results and merged into JSON perf reports, polluting performance data. Raise RuntimeError immediately at both call sites (first deploy and incremental deploy iterations) so failures surface as crashes rather than corrupt data.
Add tasks (ILLink, CompileNativeCode, GetFileHash) and targets (_RunILLink, _SelectR2RAssemblies, _LinkR2RFramework, _CompileNativeExecutable, _GenerateDSym) to capture the full build time breakdown. Binlog analysis showed _SelectR2RAssemblies alone accounts for 55% of the CoreCLR vs Mono build gap. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- pre.py: Strip whitespace from _RecommendedXcodeVersion to avoid false Xcode version mismatch warnings - pre.py: Use XML element check (<name>) instead of substring match for property injection to avoid silent failures from comment matches - setup_helix.py: Fix iPhone simulator regex to match non-numeric models (SE, XR, XS) in fallback selection path - Reporter.cs: Add CultureInfo.InvariantCulture to DateTime.Parse to handle non-US locales correctly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add _measure_app_size() that walks the .app directory to compute total file size. Emits 'App Bundle Size' (bytes) counter in both first-deploy and incremental reports, enabling CoreCLR vs Mono binary size comparison. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…erence Bug 1: Sort watchdog events by timestamp before indexing — log show ndjson output is not guaranteed chronological, which could produce incorrect or negative time deltas. Also validate the expected event sequence (monitor → stop → monitor → stop) and return -1 on mismatch. Bug 2: find_app_bundle always searched iossimulator-* first, returning the simulator build even for physical device runs. Add is_physical parameter to restrict search to ios-arm64 when targeting a physical device. Update the caller in runner.py to pass the existing is_physical flag through. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ix work item budget NuGet restore can hang on dead feeds. Without a timeout, it would consume the entire 2:30 Helix work item timeout. 10 minutes is generous for restore.
There was a problem hiding this comment.
Pull request overview
Adds a new MAUI iOS “inner loop” (build → deploy → cold-startup → incremental repeat) performance scenario and wires it into Helix/CI for both simulator and physical devices.
Changes:
- Added a new binlog parser (
iOSInnerLoopParser) to extract iOS build task/target timings and integrated it intoScenarioMeasurement. - Implemented iOS simulator/device orchestration (workload install, Xcode selection, deploy/startup measurement, reporting merge) via new scenario scripts and a shared
iOSHelper. - Updated Helix/pipeline plumbing to pass iOS RID, route the new run kind, and define the new Helix work items.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tools/ScenarioMeasurement/Util/Parsers/iOSInnerLoopParser.cs | New binlog parser emitting task/target/build duration counters for iOS inner loop. |
| src/tools/ScenarioMeasurement/Startup/Startup.cs | Adds new MetricType and wires it to the new parser. |
| src/tools/Reporting/Reporting/Reporter.cs | Makes build timestamp parsing null-tolerant for lab reporting. |
| src/scenarios/shared/runner.py | Adds iosinnerloop runner flow (build/binlog parse + install/startup timings + report merge + uploads). |
| src/scenarios/shared/mauisharedpython.py | Extends MAUI workload install helper to support selecting workload manifest IDs + workload ID. |
| src/scenarios/shared/ioshelper.py | New helper for simulator/device management, install, launch, and (device) watchdog-based startup measurement. |
| src/scenarios/shared/const.py | Adds IOSINNERLOOP scenario constant and display name mapping. |
| src/scenarios/mauiiosinnerloop/test.py | New scenario entrypoint for the iOS inner loop test. |
| src/scenarios/mauiiosinnerloop/setup_helix.py | Helix machine setup (DOTNET_ROOT, Xcode selection, simulator boot/device detect, workload install, restore). |
| src/scenarios/mauiiosinnerloop/select_xcode.py | Standalone helper for selecting an Xcode matching SDK/workload requirements. |
| src/scenarios/mauiiosinnerloop/run-local.sh | Local convenience runner for end-to-end measurements and report collection. |
| src/scenarios/mauiiosinnerloop/pre.py | Creates/normalizes MAUI template for iOS-only build + prepares incremental edit sources; workload install with fallback manifest patching. |
| src/scenarios/mauiiosinnerloop/post.py | Cleanup script for simulator/device uninstall and workspace cleanup. |
| src/scenarios/mauiiosinnerloop/results/mono/first-debug-e2e-perf-lab-report.json | Added example/generated report output (should likely not be committed). |
| src/scenarios/mauiiosinnerloop/results/mono/incremental-debug-e2e-perf-lab-report.json | Added example/generated report output (should likely not be committed). |
| src/scenarios/mauiiosinnerloop/results/coreclr/first-debug-e2e-perf-lab-report.json | Added example/generated report output (should likely not be committed). |
| src/scenarios/mauiiosinnerloop/results/coreclr/incremental-debug-e2e-perf-lab-report.json | Added example/generated report output (should likely not be committed). |
| scripts/send_to_helix.py | Adds ios_rid support and passes it as an MSBuild property for reliable .proj evaluation. |
| scripts/run_performance_job.py | Propagates run_env_vars into environment for MSBuild evaluation; includes ios inner loop in binlog copying; forwards iOSRid into SendToHelix args. |
| eng/pipelines/templates/run-performance-job.yml | Enables --runtime-flavor for the new maui_scenarios_ios_innerloop run kind. |
| eng/pipelines/sdk-perf-jobs.yml | Adds iOS inner loop jobs, but also temporarily disables many unrelated private jobs via if false. |
| eng/performance/maui_scenarios_ios_innerloop.proj | New Helix work item definition for simulator and device inner loop scenario runs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "tests": [ | ||
| { | ||
| "counters": [ | ||
| { | ||
| "name": "Install Time", | ||
| "topCounter": false, | ||
| "defaultCounter": false, | ||
| "higherIsBetter": false, | ||
| "metricName": "ms", | ||
| "results": [ | ||
| 5950.941801071167 | ||
| ] | ||
| }, | ||
| { | ||
| "name": "Cold Startup Time", | ||
| "topCounter": true, | ||
| "defaultCounter": false, | ||
| "higherIsBetter": false, | ||
| "metricName": "ms", | ||
| "results": [ | ||
| 3416 | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| ] |
There was a problem hiding this comment.
This results/...perf-lab-report.json file appears to be generated output from running the scenario locally. It will likely become stale and add noise to diffs. Consider removing generated results from the repo and keeping only the code/scripts that produce them.
| "tests": [ | |
| { | |
| "counters": [ | |
| { | |
| "name": "Install Time", | |
| "topCounter": false, | |
| "defaultCounter": false, | |
| "higherIsBetter": false, | |
| "metricName": "ms", | |
| "results": [ | |
| 5950.941801071167 | |
| ] | |
| }, | |
| { | |
| "name": "Cold Startup Time", | |
| "topCounter": true, | |
| "defaultCounter": false, | |
| "higherIsBetter": false, | |
| "metricName": "ms", | |
| "results": [ | |
| 3416 | |
| ] | |
| } | |
| ] | |
| } | |
| ] | |
| "tests": [] |
| WinUIBlazor, | ||
| TimeToMain2, | ||
| BuildTime, | ||
| iOSInnerLoop, |
There was a problem hiding this comment.
MetricType enum values elsewhere use PascalCase, but iOSInnerLoop starts with a lowercase i. For consistency (and clearer CLI/help output if DragonFruit uses the enum names), consider renaming this to IOSInnerLoop and updating the corresponding parser type name accordingly.
| iOSInnerLoop, | |
| IOSInnerLoop, |
| /// <summary> | ||
| /// Parses iOS inner loop (build+deploy) target and task durations from a binary log file. | ||
| /// </summary> | ||
| public class iOSInnerLoopParser : IParser |
There was a problem hiding this comment.
The parser type is named iOSInnerLoopParser (lowercase leading i), which is inconsistent with the PascalCase naming used by other parsers in this directory (e.g., BuildTimeParser, InnerLoopParser). Consider renaming to IOSInnerLoopParser and updating references so it matches the established type naming conventions.
| public class iOSInnerLoopParser : IParser | |
| public class IOSInnerLoopParser : IParser |
| # Scenario benchmarks | ||
| - template: /eng/pipelines/templates/build-machine-matrix.yml | ||
| parameters: | ||
| jobTemplate: /eng/pipelines/templates/run-scenarios-job.yml | ||
| buildMachines: | ||
| - win-x64-viper | ||
| - ubuntu-x64-viper | ||
| - win-arm64-ampere | ||
| - ubuntu-arm64-ampere | ||
| isPublic: false | ||
| jobParameters: | ||
| runKind: scenarios | ||
| projectFileName: scenarios.proj | ||
| channels: | ||
| - main | ||
| - 9.0 | ||
| - 8.0 | ||
| ${{ each parameter in parameters.jobParameters }}: | ||
| ${{ parameter.key }}: ${{ parameter.value }} | ||
| - ${{ if false }}: # [TEMP] Disabled for iOS inner loop CI validation | ||
| - template: /eng/pipelines/templates/build-machine-matrix.yml | ||
| parameters: | ||
| jobTemplate: /eng/pipelines/templates/run-scenarios-job.yml | ||
| buildMachines: |
There was a problem hiding this comment.
A large set of existing private jobs is currently wrapped in ${{ if false }} with a [TEMP] note. This effectively disables most of the private perf pipeline (scenarios, affinitized scenarios, Android/iOS release/debug, NativeAOT, etc.) whenever this template is used. If the intent is only to gate iOS inner loop validation, please avoid hard-disabling unrelated jobs here; instead gate the new iOS inner loop jobs behind a parameter/condition, or keep the existing jobs enabled and add the new ones alongside them.
| <!-- No XHarness needed — iOS inner loop uses xcrun simctl directly for | ||
| simulator deploy and does not use the XHarness test infrastructure. --> |
There was a problem hiding this comment.
The comment says the inner loop scenario uses xcrun simctl directly for simulator deploy, but the implementation uses mlaunch --installsim/--launchsim for deploy/launch (with simctl mainly for boot/terminate/uninstall). Please update the comment to match the actual tooling so future maintainers aren’t misled about how deploy is performed.
| <!-- No XHarness needed — iOS inner loop uses xcrun simctl directly for | |
| simulator deploy and does not use the XHarness test infrastructure. --> | |
| <!-- No XHarness needed — iOS inner loop uses mlaunch | |
| (--installsim/--launchsim) for simulator deploy/launch, with xcrun | |
| simctl used for simulator lifecycle tasks such as boot, terminate, | |
| and uninstall. --> |
| { | ||
| "tests": [ | ||
| { | ||
| "categories": [ | ||
| "Startup" | ||
| ], | ||
| "name": "MAUI iOS Inner Loop - Local - First Build and Deploy", | ||
| "additionalData": {}, | ||
| "counters": [ | ||
| { |
There was a problem hiding this comment.
This results/...perf-lab-report.json file appears to be generated output from running the scenario locally. It will likely become stale and add noise to diffs. Consider removing generated results from the repo and keeping only the code/scripts that produce them.
| "tests": [ | ||
| { | ||
| "counters": [ | ||
| { | ||
| "name": "Install Time", | ||
| "topCounter": false, | ||
| "defaultCounter": false, | ||
| "higherIsBetter": false, | ||
| "metricName": "ms", | ||
| "results": [ | ||
| 8044.32487487793 | ||
| ] | ||
| }, | ||
| { | ||
| "name": "Cold Startup Time", | ||
| "topCounter": true, | ||
| "defaultCounter": false, | ||
| "higherIsBetter": false, | ||
| "metricName": "ms", | ||
| "results": [ | ||
| 2668 | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| ] |
There was a problem hiding this comment.
This results/...perf-lab-report.json file appears to be generated output from running the scenario locally. It will likely become stale and add noise to diffs. Consider removing generated results from the repo and keeping only the code/scripts that produce them.
| "tests": [ | |
| { | |
| "counters": [ | |
| { | |
| "name": "Install Time", | |
| "topCounter": false, | |
| "defaultCounter": false, | |
| "higherIsBetter": false, | |
| "metricName": "ms", | |
| "results": [ | |
| 8044.32487487793 | |
| ] | |
| }, | |
| { | |
| "name": "Cold Startup Time", | |
| "topCounter": true, | |
| "defaultCounter": false, | |
| "higherIsBetter": false, | |
| "metricName": "ms", | |
| "results": [ | |
| 2668 | |
| ] | |
| } | |
| ] | |
| } | |
| ] | |
| "tests": [] |
| private static Build ParseBuildInfo(IEnvironment environment) | ||
| { | ||
| var buildTimestampStr = environment.GetEnvironmentVariable("PERFLAB_BUILDTIMESTAMP"); | ||
| var buildTimestamp = !string.IsNullOrEmpty(buildTimestampStr) ? DateTime.Parse(buildTimestampStr, CultureInfo.InvariantCulture) : DateTime.Now; |
There was a problem hiding this comment.
PERFLAB_BUILDTIMESTAMP is now optional, but the fallback uses DateTime.Now and still uses DateTime.Parse(...) which will throw for invalid formats. For more stable reporting (and to avoid local-time ambiguity), consider using DateTimeOffset.TryParse/TryParseExact with DateTimeStyles.RoundtripKind and falling back to DateTimeOffset.UtcNow (or DateTime.UtcNow) when the env var is missing/invalid.
| var buildTimestamp = !string.IsNullOrEmpty(buildTimestampStr) ? DateTime.Parse(buildTimestampStr, CultureInfo.InvariantCulture) : DateTime.Now; | |
| var buildTimestamp = DateTimeOffset.TryParse( | |
| buildTimestampStr, | |
| CultureInfo.InvariantCulture, | |
| DateTimeStyles.RoundtripKind, | |
| out var parsedBuildTimestamp) | |
| ? parsedBuildTimestamp.UtcDateTime | |
| : DateTimeOffset.UtcNow.UtcDateTime; |
| Install and launch use mlaunch (the same tool Visual Studio uses for F5) | ||
| to match the real developer inner-loop experience: | ||
| - Simulator: mlaunch --installsim / --launchsim | ||
| - Device: mlaunch --installdev / --launchdev | ||
| Device detection still uses devicectl; simulator management uses simctl. |
There was a problem hiding this comment.
The class docstring says physical-device install/launch uses mlaunch --installdev/--launchdev, but install_app() uses xcrun devicectl device install app for physical devices. Please align the docstring with the current behavior (or switch the implementation to match the stated approach) to avoid confusion when debugging deployment failures.
| Install and launch use mlaunch (the same tool Visual Studio uses for F5) | |
| to match the real developer inner-loop experience: | |
| - Simulator: mlaunch --installsim / --launchsim | |
| - Device: mlaunch --installdev / --launchdev | |
| Device detection still uses devicectl; simulator management uses simctl. | |
| The helper uses different platform tools depending on the target: | |
| - Simulator: mlaunch for app install/launch; simctl for simulator management | |
| - Device: devicectl for device detection and app install | |
| This mixed-tool approach reflects the current implementation and is useful | |
| to keep in mind when debugging deployment failures. |
| { | ||
| "tests": [ | ||
| { | ||
| "categories": [ | ||
| "Startup" | ||
| ], | ||
| "name": "MAUI iOS Inner Loop - Local - First Build and Deploy", | ||
| "additionalData": {}, | ||
| "counters": [ | ||
| { |
There was a problem hiding this comment.
These checked-in results/...perf-lab-report.json files look like generated output from a local run. They are likely to become stale/noisy and can bloat the repo. Consider removing them from source control and (if needed) documenting how to generate them locally or adding a small sanitized example under a dedicated docs/testdata folder.
Summary
Adds MAUI iOS Inner Loop performance measurements to CI, supporting both iOS simulators and physical devices.
What's included:
Measurements:
Targets:
Based on: