| 1 | name: App memory screening |
| 2 | |
| 3 | on: |
| 4 | pull_request: |
| 5 | branches: [main-v2] |
| 6 | push: |
| 7 | branches: [main-v2] |
| 8 | schedule: |
| 9 | - cron: "17 3 * * *" |
| 10 | workflow_dispatch: |
| 11 | |
| 12 | permissions: |
| 13 | actions: read |
| 14 | contents: read |
| 15 | |
| 16 | concurrency: |
| 17 | group: app-memory-${{ github.event.pull_request.number || github.ref }} |
| 18 | cancel-in-progress: true |
| 19 | |
| 20 | env: |
| 21 | EXPECTED_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} |
| 22 | |
| 23 | jobs: |
| 24 | changes: |
| 25 | runs-on: ubuntu-22.04 |
| 26 | outputs: |
| 27 | memory: ${{ steps.paths.outputs.memory }} |
| 28 | memory_profile: ${{ steps.profile.outputs.profile }} |
| 29 | memory_shards: ${{ steps.profile.outputs.shards }} |
| 30 | steps: |
| 31 | - uses: actions/checkout@v7 |
| 32 | with: |
| 33 | ref: ${{ env.EXPECTED_SOURCE_SHA }} |
| 34 | fetch-depth: 0 |
| 35 | - uses: actions/setup-node@v7 |
| 36 | with: |
| 37 | node-version: "24" |
| 38 | - id: paths |
| 39 | env: |
| 40 | BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} |
| 41 | EVENT_NAME: ${{ github.event_name }} |
| 42 | run: | |
| 43 | args=(--head "$EXPECTED_SOURCE_SHA" --github-output "$GITHUB_OUTPUT" --summary "$GITHUB_STEP_SUMMARY") |
| 44 | if [ "$EVENT_NAME" = workflow_dispatch ] || [ "$EVENT_NAME" = schedule ]; then |
| 45 | args+=(--full) |
| 46 | elif [ "$EVENT_NAME" = pull_request ]; then |
| 47 | args+=(--base "$BASE_SHA" --mode pull_request) |
| 48 | else |
| 49 | args+=(--base "$BASE_SHA" --mode push) |
| 50 | fi |
| 51 | node scripts/ci-paths.mjs "${args[@]}" |
| 52 | - name: Select memory profile |
| 53 | id: profile |
| 54 | env: |
| 55 | EVENT_NAME: ${{ github.event_name }} |
| 56 | MEMORY: ${{ steps.paths.outputs.memory }} |
| 57 | MEMORY_FULL: ${{ steps.paths.outputs.memory_full }} |
| 58 | run: | |
| 59 | if [ "$MEMORY" != true ]; then |
| 60 | profile=off |
| 61 | shards='[1]' |
| 62 | elif [ "$EVENT_NAME" = pull_request ] && [ "$MEMORY_FULL" != true ]; then |
| 63 | profile=short |
| 64 | shards='[1]' |
| 65 | else |
| 66 | profile=full |
| 67 | shards='[1,2,3]' |
| 68 | fi |
| 69 | echo "profile=$profile" >> "$GITHUB_OUTPUT" |
| 70 | echo "shards=$shards" >> "$GITHUB_OUTPUT" |
| 71 | printf 'App memory profile: **%s**; shards: `%s`\n' "$profile" "$shards" >> "$GITHUB_STEP_SUMMARY" |
| 72 | |
| 73 | prepare: |
| 74 | needs: changes |
| 75 | if: needs.changes.outputs.memory == 'true' |
| 76 | runs-on: ubuntu-22.04 |
| 77 | timeout-minutes: 20 |
| 78 | env: |
| 79 | REASONIX_APP_MEMORY_PROFILE: ${{ needs.changes.outputs.memory_profile }} |
| 80 | defaults: |
| 81 | run: |
| 82 | working-directory: desktop/frontend |
| 83 | steps: |
| 84 | - uses: actions/checkout@v7 |
| 85 | with: |
| 86 | ref: ${{ env.EXPECTED_SOURCE_SHA }} |
| 87 | - uses: pnpm/action-setup@v6.1.0 |
| 88 | with: |
| 89 | version: 10 |
| 90 | run_install: false |
| 91 | - uses: actions/setup-node@v7 |
| 92 | with: |
| 93 | node-version: "24" |
| 94 | cache: pnpm |
| 95 | cache-dependency-path: desktop/pnpm-lock.yaml |
| 96 | - name: Install memory dependencies |
| 97 | run: pnpm install --frozen-lockfile |
| 98 | - run: node --test bench/app-memory-shards.test.mjs bench/app-memory-paths.test.mjs bench/app-memory-timing.test.mjs |
| 99 | - name: Build memory frontend |
| 100 | run: pnpm build |
| 101 | - run: node bench/app-memory-prepare.mjs "$RUNNER_TEMP/app-memory-build" |
| 102 | - uses: actions/upload-artifact@v7 |
| 103 | with: |
| 104 | name: app-memory-build-${{ github.run_id }}-${{ github.run_attempt }} |
| 105 | path: ${{ runner.temp }}/app-memory-build |
| 106 | include-hidden-files: true |
| 107 | if-no-files-found: error |
| 108 | retention-days: 7 |
| 109 | |
| 110 | shard: |
| 111 | needs: [changes, prepare] |
| 112 | runs-on: ubuntu-22.04 |
| 113 | timeout-minutes: 90 |
| 114 | strategy: |
| 115 | fail-fast: false |
| 116 | matrix: |
| 117 | shard: ${{ fromJSON(needs.changes.outputs.memory_shards) }} |
| 118 | env: |
| 119 | REASONIX_APP_MEMORY_PROFILE: ${{ needs.changes.outputs.memory_profile }} |
| 120 | defaults: |
| 121 | run: |
| 122 | working-directory: desktop/frontend |
| 123 | steps: |
| 124 | - uses: actions/checkout@v7 |
| 125 | with: |
| 126 | ref: ${{ env.EXPECTED_SOURCE_SHA }} |
| 127 | - uses: pnpm/action-setup@v6.1.0 |
| 128 | with: |
| 129 | version: 10 |
| 130 | run_install: false |
| 131 | - uses: actions/setup-node@v7 |
| 132 | with: |
| 133 | node-version: "24" |
| 134 | cache: pnpm |
| 135 | cache-dependency-path: desktop/pnpm-lock.yaml |
| 136 | - name: Install memory dependencies |
| 137 | run: pnpm install --frozen-lockfile |
| 138 | - name: Install browser runtimes |
| 139 | run: pnpm exec playwright install --with-deps chromium |
| 140 | - uses: actions/download-artifact@v8 |
| 141 | with: |
| 142 | name: app-memory-build-${{ github.run_id }}-${{ github.run_attempt }} |
| 143 | path: ${{ runner.temp }}/app-memory-build |
| 144 | - run: cp -a "$RUNNER_TEMP/app-memory-build/dist/." dist/ |
| 145 | - name: Run complete independent memory process |
| 146 | env: |
| 147 | REASONIX_APP_MEMORY_SHARD: ${{ matrix.shard }} |
| 148 | REASONIX_APP_MEMORY_PREPARED: ${{ runner.temp }}/app-memory-build/identity.json |
| 149 | REASONIX_APP_MEMORY_ARTIFACTS: ${{ runner.temp }}/app-memory-results |
| 150 | PLAYWRIGHT_BROWSERS_PATH: /home/runner/.cache/ms-playwright |
| 151 | run: node bench/app-memory.mjs |
| 152 | - uses: actions/upload-artifact@v7 |
| 153 | if: always() |
| 154 | with: |
| 155 | name: app-memory-shard-${{ matrix.shard }}-${{ github.run_id }}-${{ github.run_attempt }} |
| 156 | path: ${{ runner.temp }}/app-memory-results |
| 157 | if-no-files-found: error |
| 158 | retention-days: 7 |
| 159 | |
| 160 | app-memory: |
| 161 | if: always() |
| 162 | needs: [changes, prepare, shard] |
| 163 | runs-on: ubuntu-22.04 |
| 164 | steps: |
| 165 | - name: Verify all prerequisite jobs |
| 166 | env: |
| 167 | CHANGES_RESULT: ${{ needs.changes.result }} |
| 168 | SHOULD_RUN: ${{ needs.changes.outputs.memory }} |
| 169 | PREPARE_RESULT: ${{ needs.prepare.result }} |
| 170 | SHARD_RESULT: ${{ needs.shard.result }} |
| 171 | MEMORY_PROFILE: ${{ needs.changes.outputs.memory_profile }} |
| 172 | run: | |
| 173 | test "$CHANGES_RESULT" = success |
| 174 | if [ "$SHOULD_RUN" = true ]; then |
| 175 | test "$MEMORY_PROFILE" = short || test "$MEMORY_PROFILE" = full |
| 176 | test "$PREPARE_RESULT" = success |
| 177 | test "$SHARD_RESULT" = success |
| 178 | else |
| 179 | test "$SHOULD_RUN" = false |
| 180 | test "$MEMORY_PROFILE" = off |
| 181 | test "$PREPARE_RESULT" = skipped |
| 182 | test "$SHARD_RESULT" = skipped |
| 183 | fi |
| 184 | - uses: actions/checkout@v7 |
| 185 | if: needs.changes.outputs.memory == 'true' |
| 186 | with: |
| 187 | ref: ${{ env.EXPECTED_SOURCE_SHA }} |
| 188 | - uses: actions/setup-node@v7 |
| 189 | if: needs.changes.outputs.memory == 'true' |
| 190 | with: |
| 191 | node-version: "24" |
| 192 | - uses: actions/download-artifact@v8 |
| 193 | if: needs.changes.outputs.memory == 'true' |
| 194 | with: |
| 195 | pattern: app-memory-shard-*-${{ github.run_id }}-${{ github.run_attempt }} |
| 196 | path: ${{ runner.temp }}/app-memory-reports |
| 197 | - uses: actions/download-artifact@v8 |
| 198 | if: needs.changes.outputs.memory == 'true' |
| 199 | with: |
| 200 | name: app-memory-build-${{ github.run_id }}-${{ github.run_attempt }} |
| 201 | path: ${{ runner.temp }}/app-memory-build |
| 202 | - name: Verify same-build complete protocol |
| 203 | if: needs.changes.outputs.memory == 'true' |
| 204 | run: | |
| 205 | node desktop/frontend/bench/app-memory-aggregate.mjs \ |
| 206 | "$RUNNER_TEMP/app-memory-reports" "$RUNNER_TEMP/app-memory-build/identity.json" \ |
| 207 | "$RUNNER_TEMP/app-memory-report.json" "$EXPECTED_SOURCE_SHA" |
| 208 | - uses: actions/upload-artifact@v7 |
| 209 | if: success() && needs.changes.outputs.memory == 'true' |
| 210 | with: |
| 211 | name: app-memory-aggregate-${{ github.run_id }}-${{ github.run_attempt }} |
| 212 | path: ${{ runner.temp }}/app-memory-report.json |
| 213 | if-no-files-found: error |
| 214 | retention-days: 7 |
| 215 | - uses: actions/checkout@v7 |
| 216 | if: always() && needs.changes.outputs.memory == 'true' |
| 217 | with: |
| 218 | ref: ${{ env.EXPECTED_SOURCE_SHA }} |
| 219 | path: .ci-metrics-source |
| 220 | - name: Summarize memory timing |
| 221 | if: always() && needs.changes.outputs.memory == 'true' |
| 222 | env: |
| 223 | GH_TOKEN: ${{ github.token }} |
| 224 | run: | |
| 225 | gh api "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" > "$RUNNER_TEMP/memory-run.json" |
| 226 | gh api "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/jobs?filter=latest&per_page=100" > "$RUNNER_TEMP/memory-jobs.json" |
| 227 | node .ci-metrics-source/scripts/ci-timings.mjs \ |
| 228 | --run "$RUNNER_TEMP/memory-run.json" \ |
| 229 | --jobs "$RUNNER_TEMP/memory-jobs.json" \ |
| 230 | --summary "$GITHUB_STEP_SUMMARY" \ |
| 231 | --title "App memory timing" |
| 232 |