Add MAUI iOS Inner Loop measurements for CI#5187
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>
AzureCliCredential defaults to a 10s subprocess timeout. On the loaded hosted agent, cold-starting 'az account get-access-token' for the storage resource exceeds 10s and fails with CredentialUnavailableError/TimeoutExpired. Allow more time so the service-connection token can be acquired at upload time. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Now that the Helix Mac queue (Mac.iPhone.13.Perf) is updated to Xcode 26.5, run the iOS inner-loop jobs on Helix instead of the AzDO agent: - Simulator jobs: drop runOnAgent: true so they dispatch to the Helix Mac queue again. This avoids the AzDO on-agent PerfLab upload blocker (the service-connection SP lacks a Storage Queue Data role on pvscmdupload, so the resultsqueue enqueue 403s); on Helix the cert-based upload path works. - Device jobs: re-enable (remove the if-false wrapper). They run on the same queue with iOSRid=ios-arm64. - Regular maui_scenarios_ios jobs (SOD/build-time/startup): disable during the iOS inner-loop WIP for faster iteration; re-enable before merge. The on-agent path (run-ios-innerloop-on-agent.sh, the runOnAgent wiring in run-performance-job.yml, and upload.py's AzureCliCredential branch) is kept dormant as a fallback until the Helix path is confirmed working. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Build 2992869 showed Mac.iPhone.13.Perf is a stopgap queue missing the infra
these jobs need:
- Simulator scenario succeeded but PerfLab upload failed: the cert
/Users/helix-runner/certs/LabCert1.pfx is not provisioned there.
- Device jobs failed fast in signing-artifact discovery: embedded.mobileprovision
and the 'sign' tool are absent ('DEVICE INFRA UNAVAILABLE').
ioshelper.py documents that the device code-signing infra lives on
Mac.iPhone.17.Perf, and main's regular iOS perf jobs run/upload from .17 (so the
upload cert is there too). .13 was only a temporary home while .17 had unhealthy
hosts in 2026-05. Point the inner-loop queue back to Mac.iPhone.17.Perf so both
the simulator upload and the device signing path have what they need.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…code 26.5) Build 2992885 on Mac.iPhone.17.Perf aborted immediately for both sim and device: 'No Xcode matching 26.5 found. Available: [Xcode_26.3.app]'. The Xcode 26.5 update landed on .13, not .17. So neither queue is complete for the net11.0_26.5 scenario: - Mac.iPhone.13.Perf: Xcode 26.5 yes; upload cert + device signing NO. - Mac.iPhone.17.Perf: upload cert + device signing yes; Xcode 26.3 (too old). Keep the queue on .13 since it's the only one that actually runs the scenario; provisioning the upload cert + device-signing infra onto .13 (or installing Xcode 26.5 on .17) is an infra task tracked separately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
NOTE: The scenario basically works, there are just some infra issues - MI & Xcode. Postponing this PR until we agree on a yes/no. |
|
Update - We've come to conclusion that we would like to move this PR forward. |
The .NET 11 SDK blocks UseMonoRuntime for mobile TFMs (NETSDK1242). Pass /p:_DisableCheckForUnsupportedMonoMobileRuntime=true (the SDK's supported opt-out, dotnet/sdk#54713) alongside UseMonoRuntime=true for the Mono inner-loop variant so restore + build succeed on net11.0-ios. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| ###################################################### | ||
|
|
||
| - ${{ if parameters.runPublicJobs }}: | ||
| - ${{ if false }}: # parameters.runPublicJobs — disabled for iOS Inner Loop WIP |
| displayName: Run tooling tests | ||
|
|
||
| - ${{ if parameters.runPublicJobs }}: | ||
| - ${{ if false }}: # parameters.runPublicJobs — disabled for iOS Inner Loop WIP |
|
|
||
| # Scheduled runs will run all of the jobs on the PerfTigers, as well as the Arm64 job | ||
| - ${{ if parameters.runScheduledPrivateJobs }}: | ||
| - ${{ if false }}: # parameters.runScheduledPrivateJobs — disabled for iOS Inner Loop WIP |
| - 8.0 | ||
| ${{ each parameter in parameters.jobParameters }}: | ||
| ${{ parameter.key }}: ${{ parameter.value }} | ||
| - ${{ if false }}: # disabled: iOS-only WIP test run |
| self.traits.add_traits(overwrite=True, apptorun="app", startupmetric=const.IOSINNERLOOP, | ||
| tracename=f'{binlog_prefix}first-build-and-deploy.binlog', | ||
| scenarioname=scenarioprefix + " - First Build and Deploy", | ||
| upload_to_perflab_container=False) | ||
| startup.parsetraces(self.traits) |
| # Clear stale traces upload dir so copytree in parsetraces doesn't collide | ||
| helix_upload_dir = helixuploaddir() | ||
| if helix_upload_dir is not None: | ||
| traces_upload = os.path.join(helix_upload_dir, 'traces') | ||
| if os.path.exists(traces_upload): | ||
| rmtree(traces_upload) | ||
|
|
||
| startup.parsetraces(self.traits) |
The net11 iOS SDK pack (26.5.x-net11-p7) requires Xcode 26.6, but the Mac.iPhone.13.Perf queue currently has Xcode 26.5. The 26.5 and 26.6 iOS SDKs are identical (dotnet/macios#25658), so pass /p:ValidateXcodeVersion=false to bypass the strict check. Temporary unblock for both Mono and CoreCLR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| if len(events) < 4: | ||
| getLogger().warning("Expected 4 watchdog events, got %d — falling back to wall-clock", len(events)) | ||
| # Couldn't parse watchdog events; return -1 to signal invalid measurement | ||
| return -1 |
| iosinnerloopparser.add_argument('--csproj-path', help='Path to .csproj file to build', dest='csprojpath') | ||
| iosinnerloopparser.add_argument('--edit-src', help='Modified source file paths, semicolon-separated', dest='editsrc') | ||
| iosinnerloopparser.add_argument('--edit-dest', help='Destination paths for modified files, semicolon-separated', dest='editdest') | ||
| iosinnerloopparser.add_argument('--framework', '-f', help='Target framework (e.g., net11.0-ios)', dest='framework') | ||
| iosinnerloopparser.add_argument('--configuration', '-c', help='Build configuration', dest='configuration', default='Debug') |
| iosinnerloopparser.add_argument('--bundle-id', help='iOS bundle identifier', dest='bundleid') | ||
| iosinnerloopparser.add_argument('--device-id', help='iOS device ID (UDID for physical device, simulator ID or "booted" for simulator)', dest='deviceid', default='booted') | ||
| iosinnerloopparser.add_argument('--device-type', choices=['simulator', 'device'], help='Target device type: simulator (default) or physical device. Auto-detected from RuntimeIdentifier if not set.', dest='devicetype', default=None) | ||
| iosinnerloopparser.add_argument('--inner-loop-iterations', help='Number of incremental build+deploy+startup iterations (1+)', type=int, default=10, dest='innerloopiterations') |
| - ${{ if false }}: # parameters.runPublicJobs — disabled for iOS Inner Loop WIP | ||
| - job: Tooling_Tests_Windows |
| # 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 }}: # disabled: iOS-only WIP test run | ||
| - template: /eng/pipelines/templates/build-machine-matrix.yml | ||
| parameters: |
Bring the iOS Inner Loop branch up to date with dotnet/performance main. Conflicts resolved: - eng/performance/maui_scenarios_ios.proj: took main's addition of /p:_DisableCheckForUnsupportedMonoMobileRuntime=true to the regular (non-inner-loop) Mono iOS jobs; kept our inner-loop block (which already carries that opt-out plus /p:ValidateXcodeVersion=false). - eng/pipelines/sdk-perf-jobs.yml: kept our WIP job gating (only the 4 iOS inner-loop jobs enabled) for focused CI iteration; main's smoke-coverage job reorg deferred until PR finalization. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5e41b4e8-fd2f-4ff7-830a-3f983c86beb3
| ###################################################### | ||
|
|
||
| - ${{ if parameters.runPublicJobs }}: | ||
| - ${{ if false }}: # parameters.runPublicJobs — disabled for iOS Inner Loop WIP |
| displayName: Run tooling tests | ||
|
|
||
| - ${{ if parameters.runPublicJobs }}: | ||
| - ${{ if false }}: # parameters.runPublicJobs — disabled for iOS Inner Loop WIP |
| - 8.0 | ||
| ${{ each parameter in parameters.jobParameters }}: | ||
| ${{ parameter.key }}: ${{ parameter.value }} | ||
| - ${{ if false }}: # disabled: iOS-only WIP test run |
|
|
||
| # Scheduled runs will run all of the jobs on the PerfTigers, as well as the Arm64 job | ||
| - ${{ if parameters.runScheduledPrivateJobs }}: | ||
| - ${{ if false }}: # parameters.runScheduledPrivateJobs — disabled for iOS Inner Loop WIP |
The signing-artifact search walked /Users/helix-runner with a 30s timeout and discarded partial results on expiry. That home dir is dominated by the huge CoreSimulator device store, so the walk reliably timed out mid-scan and reported embedded.mobileprovision / sign as "not found" — a false negative that would mask artifacts even once the queue is provisioned. - Prune known-huge/irrelevant subtrees (CoreSimulator, Caches, Logs, DerivedData, iOS DeviceSupport, .Trash, .git, .nuget, .dotnet, node_modules) so the walk completes. - Raise the per-root timeout 30s -> 90s and use partial results on timeout instead of discarding them. - Check `sign` on PATH first (machine prep usually stages it there). - Log per-root elapsed time for diagnostics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| tracename=f'{binlog_prefix}first-build-and-deploy.binlog', | ||
| scenarioname=scenarioprefix + " - First Build and Deploy", | ||
| upload_to_perflab_container=False) | ||
| startup.parsetraces(self.traits) |
| if os.path.exists(traces_upload): | ||
| rmtree(traces_upload) | ||
|
|
||
| startup.parsetraces(self.traits) |
| iosinnerloopparser.add_argument('--csproj-path', help='Path to .csproj file to build', dest='csprojpath') | ||
| iosinnerloopparser.add_argument('--edit-src', help='Modified source file paths, semicolon-separated', dest='editsrc') | ||
| iosinnerloopparser.add_argument('--edit-dest', help='Destination paths for modified files, semicolon-separated', dest='editdest') | ||
| iosinnerloopparser.add_argument('--framework', '-f', help='Target framework (e.g., net11.0-ios)', dest='framework') |
| iosinnerloopparser.add_argument('--bundle-id', help='iOS bundle identifier', dest='bundleid') | ||
| iosinnerloopparser.add_argument('--device-id', help='iOS device ID (UDID for physical device, simulator ID or "booted" for simulator)', dest='deviceid', default='booted') | ||
| iosinnerloopparser.add_argument('--device-type', choices=['simulator', 'device'], help='Target device type: simulator (default) or physical device. Auto-detected from RuntimeIdentifier if not set.', dest='devicetype', default=None) | ||
| iosinnerloopparser.add_argument('--inner-loop-iterations', help='Number of incremental build+deploy+startup iterations (1+)', type=int, default=10, dest='innerloopiterations') |
| if len(events) < 4: | ||
| getLogger().warning("Expected 4 watchdog events, got %d — falling back to wall-clock", len(events)) | ||
| # Couldn't parse watchdog events; return -1 to signal invalid measurement | ||
| return -1 |
| ###################################################### | ||
|
|
||
| - ${{ if parameters.runPublicJobs }}: | ||
| - ${{ if false }}: # parameters.runPublicJobs — disabled for iOS Inner Loop WIP |
| displayName: Run tooling tests | ||
|
|
||
| - ${{ if parameters.runPublicJobs }}: | ||
| - ${{ if false }}: # parameters.runPublicJobs — disabled for iOS Inner Loop WIP |
|
|
||
| # Scheduled runs will run all of the jobs on the PerfTigers, as well as the Arm64 job | ||
| - ${{ if parameters.runScheduledPrivateJobs }}: | ||
| - ${{ if false }}: # parameters.runScheduledPrivateJobs — disabled for iOS Inner Loop WIP |
| - 8.0 | ||
| ${{ each parameter in parameters.jobParameters }}: | ||
| ${{ parameter.key }}: ${{ parameter.value }} | ||
| - ${{ if false }}: # disabled: iOS-only WIP test run |
| │ device │ mlaunch --installdev │ mlaunch --launchdev │ | ||
| │ │ (handles devicectl tunnel│ (returns PID immediately, │ | ||
| │ │ + devicectl signing │ watchdog log captures the │ | ||
| │ │ metadata) │ startup phases) │ |
…vicectl The device path was built on two wrong assumptions (confirmed by dnceng): it searched the machine for a file named embedded.mobileprovision and a 'sign' tool. embedded.mobileprovision only exists INSIDE an already-signed .app bundle (Xcode embeds the profile at sign time), and no 'sign' tool exists on the perf Macs — so the broad /Users/helix-runner search timed out and reported a false "DEVICE INFRA UNAVAILABLE". setup_helix.py: replace find_and_stage_signing_artifacts with a fast verify_signing_prerequisites — check a raw provisioning profile at the standard Xcode path (~/Library/MobileDevice/Provisioning Profiles, Xcode 16+ fallback) and a codesigning identity via `security find-identity`. No broad find, no timeout. ioshelper.py sign_app_for_device: sign with /usr/bin/codesign --force --deep using the NET_Apple_Development identity from the Helix signing keychain and the entitlements extracted from the raw profile (security cms -D). Copy the raw profile into the bundle as embedded.mobileprovision. Verify the signature and fail loudly on error. ioshelper.py install_app: install to device via `xcrun devicectl device install app` (the bundle is codesigned first), replacing mlaunch --installdev. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (8)
src/scenarios/shared/runner.py:295
- The RuntimeIdentifier rewrite regex can accidentally eat subsequent MSBuild properties when
--msbuild-argsuses;separators (e.g....RuntimeIdentifier=foo;/p:MtouchLink=None) because\S+will consume the;/p:...suffix. Limit the match to stop at;or whitespace so only the RID value is replaced.
if ios_rid_env and 'RuntimeIdentifier=' in self.msbuildargs:
self.msbuildargs = re.sub(
r'RuntimeIdentifier=\S+',
f'RuntimeIdentifier={ios_rid_env}',
self.msbuildargs)
src/scenarios/shared/runner.py:243
--inner-loop-iterationsis documented as "(1+)" but the argparse definition currently allows 0/negative values, which would silently skip the incremental loop and produce incomplete/empty counters. Enforce a valid range (consistent with the Android inner loop parser).
iosinnerloopparser.add_argument('--device-type', choices=['simulator', 'device'], help='Target device type: simulator (default) or physical device. Auto-detected from RuntimeIdentifier if not set.', dest='devicetype', default=None)
iosinnerloopparser.add_argument('--inner-loop-iterations', help='Number of incremental build+deploy+startup iterations (1+)', type=int, default=10, dest='innerloopiterations')
self.add_common_arguments(iosinnerloopparser)
src/scenarios/shared/runner.py:1466
- The regex that re-syncs
IOS_RIDfrom/p:RuntimeIdentifier=...has the same\S+-style issue as earlier: if--msbuild-argsever uses;separators, it can capture too much. Make the capture stop at;or whitespace soIOS_RIDis set correctly.
# Keep IOS_RID env var aligned so versionmanager uses the same
# RID for the linked-DLL lookup below.
m = re.search(r'/p:RuntimeIdentifier=(\S+)', self.msbuildargs)
if m:
os.environ['IOS_RID'] = m.group(1)
src/scenarios/shared/ioshelper.py:249
- When falling back to
simctl list devices booted -j, this returns the first any booted device across all runtimes (iOS, watchOS, tvOS). If a non‑iOS simulator is booted during a local run, this can target the wrong UDID and make install/launch fail in confusing ways. Filter to iOS runtimes when selecting a booted simulator UDID.
data = json.loads(result.stdout)
for runtime_devices in data.get('devices', {}).values():
for dev in runtime_devices:
if dev.get('state', '').lower() == 'booted':
return dev['udid']
eng/pipelines/sdk-perf-jobs.yml:20
- Hard-coding this block to
if falsedisables all public correctness jobs for the pipeline, regardless of therunPublicJobsparameter. This will mask regressions and break expected CI behavior if merged. Restore the parameter-gated condition (or add a new dedicated WIP flag if you need temporary gating).
- ${{ if false }}: # parameters.runPublicJobs — disabled for iOS Inner Loop WIP
eng/pipelines/sdk-perf-jobs.yml:57
- This second
if falsealso disables the public job group unconditionally. If both public blocks are meant to remain, they should still be guarded byparameters.runPublicJobsrather than a constant false so CI can be turned on/off via the pipeline parameter.
- ${{ if false }}: # parameters.runPublicJobs — disabled for iOS Inner Loop WIP
eng/pipelines/sdk-perf-jobs.yml:826
- Hard-coding scheduled private jobs to
if falsedisables all scheduled performance coverage regardless of therunScheduledPrivateJobsparameter. This is a major CI/ops change and should be controlled by the existing parameter (or a separate explicit flag), not a constant.
- ${{ if false }}: # parameters.runScheduledPrivateJobs — disabled for iOS Inner Loop WIP
eng/pipelines/sdk-perf-jobs.yml:370
- This private-jobs block is also hard-disabled with
if false, which removes most private CI coverage whenrunPrivateJobsis true. If the intent is to run only iOS inner loop temporarily, consider introducing a dedicated WIP flag that defaults to false, rather than checking in a constant false that will be easy to forget and will silently reduce coverage.
# Scenario benchmarks
- ${{ if false }}: # disabled: iOS-only WIP test run
- template: /eng/pipelines/templates/build-machine-matrix.yml
parameters:
jobTemplate: /eng/pipelines/templates/run-scenarios-job.yml
| try: | ||
| result = subprocess.run( | ||
| ['xcrun', 'simctl', 'list', 'devices', '-j'], | ||
| capture_output=True, text=True, timeout=15, | ||
| ) | ||
| except subprocess.TimeoutExpired: | ||
| raise RuntimeError("simctl list devices timed out") | ||
| try: | ||
| data = json.loads(result.stdout or '{}') | ||
| except Exception: | ||
| data = {} | ||
| for runtime_devices in (data.get('devices') or {}).values(): |
| # Maui iOS Inner Loop (Mono) - Debug - Device | ||
| # Device variants need the Helix Mac device queue's physical iPhone + code-signing infra | ||
| # (embedded.mobileprovision + sign tool). Those live on Mac.iPhone.17.Perf, but the queue | ||
| # config currently points at Mac.iPhone.13.Perf (the only queue with Xcode 26.5); device | ||
| # signing therefore needs provisioning on .13 (or Xcode 26.5 on .17). See build-machine-matrix.yml. |
Follow-up to the codesign rewrite, from build 3026802 diagnostics on Mac.iPhone.13.Perf: - The keychain identity is "Apple Development: <person> (<TEAMID>)", not the hardcoded NET_Apple_Development, so codesign --sign would have failed on the name. _resolve_signing_identity now auto-detects the identity's SHA-1 from `security find-identity` (unambiguous), honoring IOS_SIGNING_IDENTITY. - Also search the system-wide /Library/MobileDevice/Provisioning Profiles in addition to the two per-user paths (the profile was absent from both user paths in that run). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (5)
src/scenarios/shared/runner.py:1541
StartupWrapper.parsetraces()defaults to copyingTRACEDIRinto the Helix upload dir (and optionally uploading) on every call. In this inner-loop flow you parse multiple binlogs and later do a single finalcopytree(const.TRACEDIR, ...)+ upload, so this first parse should passcopy_traces=Falseto avoid redundant I/O.
scenarioname=scenarioprefix + " - First Build and Deploy",
upload_to_perflab_container=False)
startup.parsetraces(self.traits)
src/scenarios/shared/runner.py:1616
- This per-iteration parse clears the Helix upload
traces/dir and then callsStartupWrapper.parsetraces(), which (by default) recopies the entire accumulatedTRACEDIReach iteration. That creates O(N²) copy cost as binlogs accumulate. Usecopy_traces=Falsefor each iteration parse (and rely on the single finalcopytree(const.TRACEDIR, ...)at the end), which also removes the need to deletetraces/here.
# Clear stale traces upload dir so copytree in parsetraces doesn't collide
helix_upload_dir = helixuploaddir()
if helix_upload_dir is not None:
traces_upload = os.path.join(helix_upload_dir, 'traces')
if os.path.exists(traces_upload):
eng/pipelines/sdk-perf-jobs.yml:20
- This hard-disables the public correctness job block regardless of the
runPublicJobsparameter, which will stop CI from running the normal public validation jobs. If this PR is meant to add iOS inner-loop jobs, it should not disable unrelated job groups; keep the original parameter gating so existing CI coverage remains intact.
- ${{ if false }}: # parameters.runPublicJobs — disabled for iOS Inner Loop WIP
eng/pipelines/sdk-perf-jobs.yml:826
- Scheduled private jobs are currently unreachable because this condition is hard-coded to
false, regardless of therunScheduledPrivateJobsparameter. This will disable scheduled perf coverage for the repo. Restore the parameter condition so schedules can still run when enabled.
- ${{ if false }}: # parameters.runScheduledPrivateJobs — disabled for iOS Inner Loop WIP
eng/pipelines/sdk-perf-jobs.yml:368
- Large parts of the private job matrix are now wrapped in
- ${{ if false }}blocks (e.g. the main scenario benchmark matrix). That effectively removes the normal private perf coverage and makes this pipeline definition iOS-only. If this is intended as a temporary WIP, it should be isolated (separate branch/pipeline) rather than landed into the main job template.
# Scenario benchmarks
- ${{ if false }}: # disabled: iOS-only WIP test run
- template: /eng/pipelines/templates/build-machine-matrix.yml
Summary
Adds MAUI iOS Inner Loop performance measurements to CI, supporting both iOS simulators and physical devices.
What's included:
Measurements:
Targets:
Based on: