From 9b5169eafa9cf839dd8eb0373859bd53ba5dc51c Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 21 Jul 2026 12:27:14 +0200 Subject: [PATCH 1/8] ci: bump E2E Xcode to 26.6 --- .github/workflows/e2e-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 7b0076bf..4c29d7f4 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -146,10 +146,10 @@ jobs: node-version: '24.10.0' cache: 'pnpm' - - name: Setup Xcode 26.4.1 + - name: Setup Xcode 26.6 uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: - xcode-version: '26.4.1' + xcode-version: '26.6' - name: Install Watchman run: brew install watchman From 01dd8808e8ede25c8f8288901b46d43f881ada28 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 21 Jul 2026 12:43:12 +0200 Subject: [PATCH 2/8] ci: measure iOS simulator startup timings --- .../ios-simulator-startup-timings.yml | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/ios-simulator-startup-timings.yml diff --git a/.github/workflows/ios-simulator-startup-timings.yml b/.github/workflows/ios-simulator-startup-timings.yml new file mode 100644 index 00000000..81f3d8df --- /dev/null +++ b/.github/workflows/ios-simulator-startup-timings.yml @@ -0,0 +1,53 @@ +name: iOS Simulator Startup Timings + +on: + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + measure-ios-simulator-startup: + name: Measure iOS simulator startup + runs-on: macos-latest + timeout-minutes: 20 + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Measure simulator startup timings + shell: bash + run: | + set -euxo pipefail + + sudo xcode-select \ + --switch /Applications/Xcode_26.6.app/Contents/Developer + + echo "=== First launch ===" + time sudo xcodebuild -runFirstLaunch + + echo "=== Cache preparation ===" + time xcrun simctl runtime dyld_shared_cache update --all + + RUNTIME="com.apple.CoreSimulator.SimRuntime.iOS-26-5" + DEVICE_NAME="iPhone 17 Pro" + + UDID="$( + xcrun simctl list devices available -j | + jq -er --arg runtime "$RUNTIME" --arg name "$DEVICE_NAME" ' + .devices[$runtime] + | map(select(.name == $name and .isAvailable == true)) + | .[0].udid + ' + )" + + echo "=== Selected simulator ===" + echo "$DEVICE_NAME ($RUNTIME): $UDID" + + echo "=== Boot request ===" + time xcrun simctl boot "$UDID" || true + + echo "=== Boot completion ===" + time xcrun simctl bootstatus "$UDID" -b From 24e8b2e30b22285121ad42658c88ac220d8d0ea0 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 21 Jul 2026 12:57:35 +0200 Subject: [PATCH 3/8] ci: benchmark simulator boot runtimes --- .../ios-simulator-startup-timings.yml | 130 ++++++++++++++---- 1 file changed, 102 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ios-simulator-startup-timings.yml b/.github/workflows/ios-simulator-startup-timings.yml index 81f3d8df..2f9f39b4 100644 --- a/.github/workflows/ios-simulator-startup-timings.yml +++ b/.github/workflows/ios-simulator-startup-timings.yml @@ -1,53 +1,127 @@ -name: iOS Simulator Startup Timings +name: Simulator boot benchmark on: - pull_request: - branches: - - main + workflow_dispatch: permissions: contents: read jobs: - measure-ios-simulator-startup: - name: Measure iOS simulator startup - runs-on: macos-latest + simulator-boot: + strategy: + fail-fast: false + matrix: + runtime: + - iOS-26-2 + - iOS-26-4 + - iOS-26-5 + + runs-on: macos-26 timeout-minutes: 20 - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Measure simulator startup timings + steps: + - name: Select Xcode 26.6 shell: bash run: | - set -euxo pipefail + set -euo pipefail sudo xcode-select \ --switch /Applications/Xcode_26.6.app/Contents/Developer - echo "=== First launch ===" - time sudo xcodebuild -runFirstLaunch + xcodebuild -version + xcrun simctl list runtimes + xcrun simctl list devices available + + - name: Measure existing simulator boot + shell: bash + env: + RUNTIME_SUFFIX: ${{ matrix.runtime }} + DEVICE_NAME: iPhone 17 Pro + run: | + set -euo pipefail - echo "=== Cache preparation ===" - time xcrun simctl runtime dyld_shared_cache update --all + RUNTIME_ID="com.apple.CoreSimulator.SimRuntime.${RUNTIME_SUFFIX}" + BOOT_LOG="$RUNNER_TEMP/bootstatus-${RUNTIME_SUFFIX}.log" - RUNTIME="com.apple.CoreSimulator.SimRuntime.iOS-26-5" - DEVICE_NAME="iPhone 17 Pro" + echo "Looking for:" + echo " Runtime: $RUNTIME_ID" + echo " Device: $DEVICE_NAME" + # Verify that the requested runtime is installed. + if ! xcrun simctl list runtimes -j | + jq -e --arg runtime "$RUNTIME_ID" ' + .runtimes[] + | select( + .identifier == $runtime + and .isAvailable == true + ) + ' >/dev/null; then + echo "::error::Runtime $RUNTIME_ID is not available" + exit 1 + fi + + # Select an existing available device only. UDID="$( xcrun simctl list devices available -j | - jq -er --arg runtime "$RUNTIME" --arg name "$DEVICE_NAME" ' - .devices[$runtime] - | map(select(.name == $name and .isAvailable == true)) - | .[0].udid - ' - )" + jq -er \ + --arg runtime "$RUNTIME_ID" \ + --arg name "$DEVICE_NAME" ' + .devices[$runtime] + | map( + select( + .name == $name + and .isAvailable == true + ) + ) + | .[0].udid + ' + )" || { + echo "::error::No existing $DEVICE_NAME found for $RUNTIME_ID" + echo "Available devices:" + xcrun simctl list devices available + exit 1 + } - echo "=== Selected simulator ===" - echo "$DEVICE_NAME ($RUNTIME): $UDID" + echo "Selected existing simulator:" + echo " $DEVICE_NAME" + echo " $RUNTIME_ID" + echo " $UDID" + + # Do not erase, recreate, or delete the selected device. + xcrun simctl shutdown "$UDID" 2>/dev/null || true + + START_SECONDS="$(date +%s)" echo "=== Boot request ===" - time xcrun simctl boot "$UDID" || true + time xcrun simctl boot "$UDID" echo "=== Boot completion ===" - time xcrun simctl bootstatus "$UDID" -b + set +e + xcrun simctl bootstatus "$UDID" -b 2>&1 | + tee "$BOOT_LOG" + BOOT_STATUS="${PIPESTATUS[0]}" + set -e + + END_SECONDS="$(date +%s)" + ELAPSED_SECONDS="$((END_SECONDS - START_SECONDS))" + + MIGRATION_SAMPLES="$( + grep -c "Waiting on Data Migration" "$BOOT_LOG" || true + )" + + { + echo "### Existing simulator boot result" + echo + echo "- Runtime: \`$RUNTIME_ID\`" + echo "- Device: \`$DEVICE_NAME\`" + echo "- UDID: \`$UDID\`" + echo "- Duration: **${ELAPSED_SECONDS}s**" + echo "- Migration samples: **${MIGRATION_SAMPLES}**" + echo "- Runner image: \`${ImageOS:-unknown}\`" + echo "- Image version: \`${ImageVersion:-unknown}\`" + } >> "$GITHUB_STEP_SUMMARY" + + if [[ "$BOOT_STATUS" -ne 0 ]]; then + echo "::error::Simulator failed to finish booting" + exit "$BOOT_STATUS" + fi From a999bc21aa8474128324b1edc79d67e4bafb6a2f Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 21 Jul 2026 16:17:59 +0200 Subject: [PATCH 4/8] ci: cache iOS 26.5 simulator state --- .../ios-simulator-startup-timings.yml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/ios-simulator-startup-timings.yml b/.github/workflows/ios-simulator-startup-timings.yml index 2f9f39b4..f4c73052 100644 --- a/.github/workflows/ios-simulator-startup-timings.yml +++ b/.github/workflows/ios-simulator-startup-timings.yml @@ -29,6 +29,22 @@ jobs: --switch /Applications/Xcode_26.6.app/Contents/Developer xcodebuild -version + + echo "SIMULATOR_CACHE_KEY=core-simulator-device-ios-26-5-xcode-26-6-${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV" + + - name: Restore iOS 26.5 simulator cache + id: cache-ios-26-5-restore + if: matrix.runtime == 'iOS-26-5' + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ~/Library/Developer/CoreSimulator/Devices/FD9EC0F9-63AA-4C2F-B0F5-224AAE68866E + key: ${{ env.SIMULATOR_CACHE_KEY }} + + - name: Inspect available simulators + shell: bash + run: | + set -euo pipefail + xcrun simctl list runtimes xcrun simctl list devices available @@ -125,3 +141,10 @@ jobs: echo "::error::Simulator failed to finish booting" exit "$BOOT_STATUS" fi + + - name: Save iOS 26.5 simulator cache + if: always() && matrix.runtime == 'iOS-26-5' && steps.cache-ios-26-5-restore.outputs.cache-hit != 'true' + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ~/Library/Developer/CoreSimulator/Devices/FD9EC0F9-63AA-4C2F-B0F5-224AAE68866E + key: ${{ steps.cache-ios-26-5-restore.outputs.cache-primary-key }} From 368a48bfcd2cdd99780b1d530dfcc09435d61b68 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 21 Jul 2026 16:25:36 +0200 Subject: [PATCH 5/8] fix: shut down simulator before caching --- .../ios-simulator-startup-timings.yml | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ios-simulator-startup-timings.yml b/.github/workflows/ios-simulator-startup-timings.yml index f4c73052..a41f767a 100644 --- a/.github/workflows/ios-simulator-startup-timings.yml +++ b/.github/workflows/ios-simulator-startup-timings.yml @@ -142,8 +142,28 @@ jobs: exit "$BOOT_STATUS" fi + - name: Shut down iOS 26.5 simulator before caching + id: shutdown-ios-26-5 + if: always() && matrix.runtime == 'iOS-26-5' + shell: bash + run: | + set -euo pipefail + + UDID="FD9EC0F9-63AA-4C2F-B0F5-224AAE68866E" + xcrun simctl shutdown "$UDID" || true + + for _ in {1..30}; do + if xcrun simctl list devices | grep -F "$UDID" | grep -q '(Shutdown)'; then + exit 0 + fi + sleep 1 + done + + echo "::error::Simulator $UDID did not shut down before caching" + exit 1 + - name: Save iOS 26.5 simulator cache - if: always() && matrix.runtime == 'iOS-26-5' && steps.cache-ios-26-5-restore.outputs.cache-hit != 'true' + if: always() && matrix.runtime == 'iOS-26-5' && steps.shutdown-ios-26-5.outcome == 'success' && steps.cache-ios-26-5-restore.outputs.cache-hit != 'true' uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/Library/Developer/CoreSimulator/Devices/FD9EC0F9-63AA-4C2F-B0F5-224AAE68866E From 5f59a8444d88c43437d001c17b0853671fdeb9bb Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Thu, 23 Jul 2026 11:41:39 +0200 Subject: [PATCH 6/8] ci: sample runner memory pressure during iOS E2E --- .github/workflows/e2e-tests.yml | 53 +++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 4c29d7f4..8af28ba7 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -200,6 +200,18 @@ jobs: path: ./apps/playground/ios/build/Build/Products/Debug-iphonesimulator/HarnessPlayground.app key: ios-app-playground + # Samples host memory pressure (swap usage, compressor, swap in/out) every + # 2s across the whole harness run so we can tell whether the 7 GB / 3 vCPU + # runner is swapping while simulator + xcodebuild + Metro run together. + # Uses a backgrounded nohup process that persists across later steps. + - name: Start memory-pressure sampler + shell: bash + run: | + SAMPLE_LOG="$RUNNER_TEMP/mem-pressure.log" + nohup bash -c 'while true; do echo "=== $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="; sysctl -n vm.swapusage; vm_stat; sleep 2; done' > "$SAMPLE_LOG" 2>&1 & + echo $! > "$RUNNER_TEMP/mem-sampler.pid" + echo "memory sampler PID $(cat "$RUNNER_TEMP/mem-sampler.pid") -> $SAMPLE_LOG" + - name: Run React Native Harness uses: ./ with: @@ -213,6 +225,47 @@ jobs: echo "HARNESS_RUNNER=$HARNESS_RUNNER" echo "HARNESS_EXIT_CODE=$HARNESS_EXIT_CODE" + - name: Stop sampler and summarize memory pressure + if: always() + shell: bash + run: | + PID="$(cat "$RUNNER_TEMP/mem-sampler.pid" 2>/dev/null || true)" + if [ -n "$PID" ]; then kill "$PID" 2>/dev/null || true; fi + LOG="$RUNNER_TEMP/mem-pressure.log" + if [ ! -s "$LOG" ]; then echo "no sampler log found"; exit 0; fi + + PAGE=16384 + PEAK_SWAP_M="$(grep -o 'used = [0-9.]*M' "$LOG" | sed 's/used = //; s/M//' | sort -n | tail -1)" + PEAK_COMP_PAGES="$(grep 'occupied by compressor' "$LOG" | grep -oE '[0-9]+' | sort -n | tail -1)" + MIN_FREE_PAGES="$(grep 'Pages free' "$LOG" | grep -oE '[0-9]+' | sort -n | head -1)" + FIRST_SWAPOUT="$(grep 'Swapouts' "$LOG" | head -1 | grep -oE '[0-9]+' | head -1)" + LAST_SWAPOUT="$(grep 'Swapouts' "$LOG" | tail -1 | grep -oE '[0-9]+' | head -1)" + PEAK_SWAP_M="${PEAK_SWAP_M:-0}"; PEAK_COMP_PAGES="${PEAK_COMP_PAGES:-0}" + MIN_FREE_PAGES="${MIN_FREE_PAGES:-0}"; FIRST_SWAPOUT="${FIRST_SWAPOUT:-0}"; LAST_SWAPOUT="${LAST_SWAPOUT:-0}" + PEAK_COMP_MB=$(( PEAK_COMP_PAGES * PAGE / 1048576 )) + MIN_FREE_MB=$(( MIN_FREE_PAGES * PAGE / 1048576 )) + SWAPOUT_DELTA=$(( LAST_SWAPOUT - FIRST_SWAPOUT )) + + { + echo "### iOS runner memory pressure" + echo "" + echo "- Peak swap used: **${PEAK_SWAP_M} MB**" + echo "- Peak compressed memory: **${PEAK_COMP_MB} MB** (${PEAK_COMP_PAGES} pages)" + echo "- Min free memory: **${MIN_FREE_MB} MB** (${MIN_FREE_PAGES} pages)" + echo "- Swapouts during run (delta): **${SWAPOUT_DELTA}**" + echo "- Runner: macos-latest (3 vCPU / 7 GB), page size ${PAGE} B" + } >> "$GITHUB_STEP_SUMMARY" + + echo "peak_swap_mb=${PEAK_SWAP_M} peak_compressed_mb=${PEAK_COMP_MB} min_free_mb=${MIN_FREE_MB} swapout_delta=${SWAPOUT_DELTA}" + + - name: Upload memory samples + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: mem-pressure-e2e-ios + path: ${{ runner.temp }}/mem-pressure.log + if-no-files-found: warn + - name: Upload Harness logs if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 From 8aea448ab48ec306cea77433350c4ff62f79c003 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Thu, 23 Jul 2026 12:20:26 +0200 Subject: [PATCH 7/8] ci: capture per-process RSS during iOS E2E Add a second sampler logging full `ps -Ao rss,pid,command` every 10s, plus a peak-snapshot per-process/grouped breakdown in the step summary, to attribute the 7 GB runner pressure to specific processes. --- .github/workflows/e2e-tests.yml | 51 +++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 8af28ba7..06bd26c4 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -200,17 +200,24 @@ jobs: path: ./apps/playground/ios/build/Build/Products/Debug-iphonesimulator/HarnessPlayground.app key: ios-app-playground - # Samples host memory pressure (swap usage, compressor, swap in/out) every - # 2s across the whole harness run so we can tell whether the 7 GB / 3 vCPU - # runner is swapping while simulator + xcodebuild + Metro run together. - # Uses a backgrounded nohup process that persists across later steps. + # Two backgrounded samplers (persist across later steps via nohup): + # 1. mem-pressure.log - system swap/compressor/free every 2s (timeline) + # 2. mem-proc.log - full per-process RSS (`ps -Ao rss,pid,command`) + # every 10s, so we can rank the biggest consumers AND total the ~170 + # simulator daemons to see what a `bootout` would reclaim. + # Goal: attribute the 7 GB / 3 vCPU pressure to specific processes and + # bring them down so Harness fits on the standard macos runner. - name: Start memory-pressure sampler shell: bash run: | SAMPLE_LOG="$RUNNER_TEMP/mem-pressure.log" + PROC_LOG="$RUNNER_TEMP/mem-proc.log" nohup bash -c 'while true; do echo "=== $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="; sysctl -n vm.swapusage; vm_stat; sleep 2; done' > "$SAMPLE_LOG" 2>&1 & echo $! > "$RUNNER_TEMP/mem-sampler.pid" - echo "memory sampler PID $(cat "$RUNNER_TEMP/mem-sampler.pid") -> $SAMPLE_LOG" + nohup bash -c 'while true; do echo "=== $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="; ps -Ao rss,pid,command; sleep 10; done' > "$PROC_LOG" 2>&1 & + echo $! > "$RUNNER_TEMP/mem-proc.pid" + echo "pressure sampler PID $(cat "$RUNNER_TEMP/mem-sampler.pid") -> $SAMPLE_LOG" + echo "process sampler PID $(cat "$RUNNER_TEMP/mem-proc.pid") -> $PROC_LOG" - name: Run React Native Harness uses: ./ @@ -231,6 +238,8 @@ jobs: run: | PID="$(cat "$RUNNER_TEMP/mem-sampler.pid" 2>/dev/null || true)" if [ -n "$PID" ]; then kill "$PID" 2>/dev/null || true; fi + PPID_="$(cat "$RUNNER_TEMP/mem-proc.pid" 2>/dev/null || true)" + if [ -n "$PPID_" ]; then kill "$PPID_" 2>/dev/null || true; fi LOG="$RUNNER_TEMP/mem-pressure.log" if [ ! -s "$LOG" ]; then echo "no sampler log found"; exit 0; fi @@ -258,12 +267,42 @@ jobs: echo "peak_swap_mb=${PEAK_SWAP_M} peak_compressed_mb=${PEAK_COMP_MB} min_free_mb=${MIN_FREE_MB} swapout_delta=${SWAPOUT_DELTA}" + # Per-process breakdown at the peak-usage snapshot (highest total RSS). + PROC_LOG="$RUNNER_TEMP/mem-proc.log" + if [ -s "$PROC_LOG" ]; then + PEAK_TS="$(awk '/^=== /{ if(cur!=""&&sum>best){best=sum;bts=cur} cur=$2; sum=0; next } { sum+=$1 } END{ if(sum>best){best=sum;bts=cur} print bts }' "$PROC_LOG")" + echo "peak-RSS snapshot: $PEAK_TS" + { + echo "" + echo "#### Top processes by RSS at peak ($PEAK_TS)" + echo '```' + awk -v ts="$PEAK_TS" '$0=="=== "ts" ==="{c=1;next} /^=== /{c=0} c' "$PROC_LOG" \ + | awk 'NF>=3 && $1 ~ /^[0-9]+$/ {printf "%6d MB %s\n", $1/1024, substr($0, index($0,$3))}' \ + | sort -rn | head -15 + echo '```' + echo "" + echo "Approx grouped totals at peak (rough; refine from artifact):" + echo '```' + awk -v ts="$PEAK_TS" '$0=="=== "ts" ==="{c=1;next} /^=== /{c=0} c && $1 ~ /^[0-9]+$/ { + cmd=substr($0, index($0,$3)); r=$1; tot+=r; + if (cmd ~ /CoreSimulator|launchd_sim|SpringBoard|backboardd|testmanagerd|HarnessPlayground|XCTest|SimDevice|\/Devices\//) sim+=r; + else if (cmd ~ /node/) node+=r; + else if (cmd ~ /swift|clang|XCBBuildService|xcodebuild|[Xx]code|ld$/) build+=r; + else other+=r; + } + END { printf "simulator=%d MB node(metro+harness)=%d MB xcodebuild/compilers=%d MB other=%d MB sum-RSS=%d MB\n", sim/1024, node/1024, build/1024, other/1024, tot/1024 }' "$PROC_LOG" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + fi + - name: Upload memory samples if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: mem-pressure-e2e-ios - path: ${{ runner.temp }}/mem-pressure.log + path: | + ${{ runner.temp }}/mem-pressure.log + ${{ runner.temp }}/mem-proc.log if-no-files-found: warn - name: Upload Harness logs From 1782a3310841e50782e5ab6e8da64719ae3ed6c0 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Thu, 23 Jul 2026 12:47:00 +0200 Subject: [PATCH 8/8] ci: measure per-process physical footprint via top (not RSS) ps RSS double-counts shared framework memory, inflating simulator totals absurdly. Switch the per-process sampler to `top -l1 -o mem` (phys_footprint, Activity Monitor's metric) plus PhysMem total and process count. --- .github/workflows/e2e-tests.yml | 38 +++++++++++---------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 06bd26c4..b86ee537 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -202,9 +202,9 @@ jobs: # Two backgrounded samplers (persist across later steps via nohup): # 1. mem-pressure.log - system swap/compressor/free every 2s (timeline) - # 2. mem-proc.log - full per-process RSS (`ps -Ao rss,pid,command`) - # every 10s, so we can rank the biggest consumers AND total the ~170 - # simulator daemons to see what a `bootout` would reclaim. + # 2. mem-proc.log - `top -l1 -o mem` every 10s: honest per-process + # physical footprint (phys_footprint, no RSS shared-page double-count) + # + a PhysMem total + process count. Ranks the real consumers. # Goal: attribute the 7 GB / 3 vCPU pressure to specific processes and # bring them down so Harness fits on the standard macos runner. - name: Start memory-pressure sampler @@ -214,7 +214,7 @@ jobs: PROC_LOG="$RUNNER_TEMP/mem-proc.log" nohup bash -c 'while true; do echo "=== $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="; sysctl -n vm.swapusage; vm_stat; sleep 2; done' > "$SAMPLE_LOG" 2>&1 & echo $! > "$RUNNER_TEMP/mem-sampler.pid" - nohup bash -c 'while true; do echo "=== $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="; ps -Ao rss,pid,command; sleep 10; done' > "$PROC_LOG" 2>&1 & + nohup bash -c 'while true; do echo "=== $(date -u +%Y-%m-%dT%H:%M:%SZ) nprocs=$(ps -A | wc -l | tr -d " ") ==="; top -l 1 -o mem -n 45 -stats pid,mem,command; sleep 10; done' > "$PROC_LOG" 2>&1 & echo $! > "$RUNNER_TEMP/mem-proc.pid" echo "pressure sampler PID $(cat "$RUNNER_TEMP/mem-sampler.pid") -> $SAMPLE_LOG" echo "process sampler PID $(cat "$RUNNER_TEMP/mem-proc.pid") -> $PROC_LOG" @@ -267,32 +267,18 @@ jobs: echo "peak_swap_mb=${PEAK_SWAP_M} peak_compressed_mb=${PEAK_COMP_MB} min_free_mb=${MIN_FREE_MB} swapout_delta=${SWAPOUT_DELTA}" - # Per-process breakdown at the peak-usage snapshot (highest total RSS). + # Honest physical footprint: top's PhysMem line (no RSS shared-page + # double-count). Per-process MEM (phys_footprint) is in the artifact. PROC_LOG="$RUNNER_TEMP/mem-proc.log" if [ -s "$PROC_LOG" ]; then - PEAK_TS="$(awk '/^=== /{ if(cur!=""&&sum>best){best=sum;bts=cur} cur=$2; sum=0; next } { sum+=$1 } END{ if(sum>best){best=sum;bts=cur} print bts }' "$PROC_LOG")" - echo "peak-RSS snapshot: $PEAK_TS" + PEAK_PHYS="$(grep -o 'PhysMem: [0-9]*[MG] used' "$PROC_LOG" | grep -oE '[0-9]+[MG]' | awk '/G$/{sub(/G/,"");print $1*1024;next}{sub(/M/,"");print}' | sort -n | tail -1)" + PEAK_NPROCS="$(grep -oE 'nprocs=[0-9]+' "$PROC_LOG" | grep -oE '[0-9]+' | sort -n | tail -1)" { - echo "" - echo "#### Top processes by RSS at peak ($PEAK_TS)" - echo '```' - awk -v ts="$PEAK_TS" '$0=="=== "ts" ==="{c=1;next} /^=== /{c=0} c' "$PROC_LOG" \ - | awk 'NF>=3 && $1 ~ /^[0-9]+$/ {printf "%6d MB %s\n", $1/1024, substr($0, index($0,$3))}' \ - | sort -rn | head -15 - echo '```' - echo "" - echo "Approx grouped totals at peak (rough; refine from artifact):" - echo '```' - awk -v ts="$PEAK_TS" '$0=="=== "ts" ==="{c=1;next} /^=== /{c=0} c && $1 ~ /^[0-9]+$/ { - cmd=substr($0, index($0,$3)); r=$1; tot+=r; - if (cmd ~ /CoreSimulator|launchd_sim|SpringBoard|backboardd|testmanagerd|HarnessPlayground|XCTest|SimDevice|\/Devices\//) sim+=r; - else if (cmd ~ /node/) node+=r; - else if (cmd ~ /swift|clang|XCBBuildService|xcodebuild|[Xx]code|ld$/) build+=r; - else other+=r; - } - END { printf "simulator=%d MB node(metro+harness)=%d MB xcodebuild/compilers=%d MB other=%d MB sum-RSS=%d MB\n", sim/1024, node/1024, build/1024, other/1024, tot/1024 }' "$PROC_LOG" - echo '```' + echo "- Peak physical memory used (top PhysMem): **${PEAK_PHYS:-?} MB**" + echo "- Peak process count: **${PEAK_NPROCS:-?}**" + echo "- Per-process physical footprint (top MEM) in artifact \`mem-proc.log\`" } >> "$GITHUB_STEP_SUMMARY" + echo "peak_phys_mb=${PEAK_PHYS:-0} peak_nprocs=${PEAK_NPROCS:-0}" fi - name: Upload memory samples