Skip to content

Commit 976dcbd

Browse files
ci: GitHub actions performance (#187)
* docs: add GitHub Actions performance optimization plan Analyzed run 21023241683 to identify optimization opportunities: - Remove debug dry-run steps (~6.5 min savings) - Remove gradle tasks list from common-setup (~5-10 min savings) Also documented known issues that aren't actionable: - connectedAndroidTest rebuild limitation (AGP hardcoded deps) - Integration test shard flakiness (runner resource contention) - set_build_datetime job for consistent artifact naming Co-authored-by: wharris <wharris@upscalews.com> * perf(ci): remove debug dry-run steps and gradle task list Remove diagnostic steps that added ~11-16 minutes to CI runs: - Remove 'Debug Main Build Task Graph' dry-run step (~2 min savings) - Remove 'Debug Unit Test Task Graph' dry-run step (~2.5 min savings) - Remove 'List Gradle Tasks' step from common-setup (~5-10 min aggregate) These were purely diagnostic and not needed for normal CI operation. Gradle cache is warmed up by subsequent build steps anyway. Co-authored-by: wharris <wharris@upscalews.com> * docs: mark implemented optimizations as complete Co-authored-by: wharris <wharris@upscalews.com> * perf(ci): skip codegen for integration test shards Integration test shards download pre-built APKs and run am instrument directly - they don't need React Native codegen. Changes: - Add skip_codegen input to common-setup action (default false) - Integration test shards now pass skip_codegen: true - Update plan doc with new optimization Expected savings: ~3 min × 4 shards = ~12 min aggregate Co-authored-by: wharris <wharris@upscalews.com> * docs: update plan with verified results All optimizations implemented and verified: - Removed debug dry-run steps - Removed gradle tasks list - Skip codegen for integration test shards Measured ~12-13 min savings per CI run with warm caches. Co-authored-by: wharris <wharris@upscalews.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent feed7c7 commit 976dcbd

3 files changed

Lines changed: 166 additions & 42 deletions

File tree

.github/actions/common-setup/action.yml

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ inputs:
4242
description: 'Optional cache key'
4343
required: false
4444
default: ""
45+
skip_codegen:
46+
description: 'Skip React Native codegen (for jobs that use pre-built APKs)'
47+
required: false
48+
default: "false"
4549

4650
outputs:
4751
build-outputs-cache-hit:
@@ -272,16 +276,10 @@ runs:
272276
run: cd android && chmod +x gradlew
273277
shell: bash
274278

275-
# List all available Gradle tasks (also warms up Gradle cache)
276-
- name: List Gradle Tasks
277-
run: |
278-
cd android && \
279-
./gradlew tasks --all > gradle-tasks.txt
280-
cat gradle-tasks.txt
281-
shell: bash
282-
283279
# Run codegen for all React Native libraries (required for New Architecture)
280+
# Skip for jobs that use pre-built APKs (integration test shards)
284281
- name: Generate React Native Codegen
282+
if: inputs.skip_codegen != 'true'
285283
run: |
286284
cd android && \
287285
./gradlew generateCodegenSchemaFromJavaScript generateCodegenArtifactsFromSchema --parallel --max-workers=$MAX_WORKERS

.github/workflows/actions.yml

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -111,24 +111,6 @@ jobs:
111111
node_version: "22.x"
112112
arch: ${{ matrix.arch }}
113113

114-
# Debug Main Build Task Graph
115-
- name: Debug Main Build Task Graph
116-
run: |
117-
cd android && \
118-
echo "=== Main Build Task Graph ===" && \
119-
./gradlew -PBUILD_ARCH="${BUILD_ARCH}" \
120-
-PreactNativeArchitectures="${BUILD_ARCH}" \
121-
":${{ env.main_project_module }}:assemble${{ matrix.arch == 'arm64-v8a' && 'Arm64V8a' || 'X8664' }}Debug" \
122-
":${{ env.main_project_module }}:assemble${{ matrix.arch == 'arm64-v8a' && 'Arm64V8a' || 'X8664' }}Release" \
123-
":${{ env.main_project_module }}:bundle${{ matrix.arch == 'arm64-v8a' && 'Arm64V8a' || 'X8664' }}Release" \
124-
":${{ env.main_project_module }}:assemble${{ matrix.arch == 'arm64-v8a' && 'Arm64V8a' || 'X8664' }}DebugAndroidTest" \
125-
--dry-run \
126-
--info \
127-
--console=verbose \
128-
--parallel --max-workers=$MAX_WORKERS --build-cache
129-
env:
130-
BUILD_ARCH: ${{ matrix.arch }}
131-
132114
- name: Ccachify the Native Modules
133115
shell: bash
134116
run: |
@@ -440,22 +422,6 @@ jobs:
440422
path: coverage/
441423
retention-days: 90
442424

443-
# Debug unit test task graph
444-
- name: Debug Unit Test Task Graph
445-
run: |
446-
cd android && \
447-
echo "=== Unit Test Task Graph ===" && \
448-
./gradlew -PBUILD_ARCH="${{ matrix.arch }}" \
449-
-PreactNativeArchitectures="${{ matrix.arch }}" \
450-
:${{ env.main_project_module }}:test${{ matrix.arch == 'arm64-v8a' && 'Arm64V8a' || 'X8664' }}DebugUnitTest \
451-
:${{ env.main_project_module }}:create${{ matrix.arch == 'arm64-v8a' && 'Arm64V8a' || 'X8664' }}DebugUnitTestCoverageReport \
452-
--dry-run \
453-
--info \
454-
--console=verbose \
455-
--continue
456-
env:
457-
BUILD_ARCH: ${{ matrix.arch }}
458-
459425
# Run the unit tests
460426
- name: Run Unit Tests
461427
run: |
@@ -739,6 +705,7 @@ jobs:
739705
fetch-depth: 1
740706

741707
# Execute the common setup with emulator configuration
708+
# Skip codegen since we use pre-built APKs (saves ~3 min per shard)
742709
- name: Common Setup
743710
id: common-setup
744711
uses: ./.github/actions/common-setup
@@ -751,6 +718,7 @@ jobs:
751718
android_target: ${{ env.ANDROID_TARGET }}
752719
android_profile: ${{ env.ANDROID_PROFILE }}
753720
run_emulator_setup: "true"
721+
skip_codegen: "true"
754722

755723
- name: Make scripts executable
756724
run: |
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# GitHub Actions Performance Optimizations
2+
3+
## Overview
4+
5+
Analysis of GitHub Actions workflow performance based on run 21023241683 (January 15, 2026).
6+
7+
### Key Timing Findings
8+
9+
| Job | Duration | Key Bottlenecks |
10+
|-----|----------|-----------------|
11+
| Build Android App (x86_64) | ~32 min | Common Setup: 10 min, Dry-run: 2 min, Build: 18.5 min |
12+
| Build Android App (arm64) | ~31 min | Similar breakdown |
13+
| Unit Tests | ~20 min | Common Setup: 10 min, Dry-run: 2.5 min, Tests: 7 min |
14+
| Verify Connected Android Test | ~26 min | Common Setup: 10 min, Build+Test: 15 min |
15+
| Integration Test (per shard) | ~12-14 min | Common Setup: 7-8 min, Tests: 3-4 min |
16+
17+
---
18+
19+
## Actionable Optimizations
20+
21+
### 1. Remove "Debug Main Build Task Graph" Dry-Run Step
22+
23+
**Location**: `.github/workflows/actions.yml` lines 115-131
24+
25+
**Current code**:
26+
```yaml
27+
- name: Debug Main Build Task Graph
28+
run: |
29+
cd android && \
30+
echo "=== Main Build Task Graph ===" && \
31+
./gradlew ... --dry-run ...
32+
```
33+
34+
**Action**: Remove this step entirely (or make conditional on a debug input flag).
35+
36+
**Measured impact**: ~2 minutes per build job × 2 architectures = **~4 minutes saved**
37+
38+
**Risk**: Very low - purely diagnostic output
39+
40+
---
41+
42+
### 2. Remove "Debug Unit Test Task Graph" Dry-Run Step
43+
44+
**Location**: `.github/workflows/actions.yml` lines 444-457
45+
46+
**Current code**:
47+
```yaml
48+
- name: Debug Unit Test Task Graph
49+
run: |
50+
cd android && \
51+
echo "=== Unit Test Task Graph ===" && \
52+
./gradlew ... --dry-run ...
53+
```
54+
55+
**Action**: Remove this step entirely.
56+
57+
**Measured impact**: ~2.5 minutes saved
58+
59+
**Risk**: Very low - purely diagnostic output
60+
61+
---
62+
63+
### 3. Remove "List Gradle Tasks" from Common Setup
64+
65+
**Location**: `.github/actions/common-setup/action.yml` lines 276-281
66+
67+
**Current code**:
68+
```yaml
69+
- name: List Gradle Tasks
70+
run: |
71+
cd android && \
72+
./gradlew tasks --all > gradle-tasks.txt
73+
cat gradle-tasks.txt
74+
```
75+
76+
**Action**: Remove this step entirely. It runs on every job and is purely diagnostic.
77+
78+
**Measured impact**: ~30-60 seconds per job × 10+ jobs = **~5-10 minutes aggregate saved**
79+
80+
**Risk**: Low - only diagnostic, Gradle gets warmed up by other steps anyway
81+
82+
---
83+
84+
### 4. Skip Codegen for Integration Test Shards
85+
86+
**Location**: `.github/workflows/actions.yml` integration-test job + `.github/actions/common-setup/action.yml`
87+
88+
**Problem**: Integration test shards download pre-built APKs and run `am instrument` directly - they don't build anything. But common-setup still runs React Native codegen (~3 min).
89+
90+
**Solution**: Added `skip_codegen` input to common-setup. Integration test shards now pass `skip_codegen: "true"`.
91+
92+
**Measured impact**: ~3 min × 4 shards = **~12 min aggregate saved**
93+
94+
**Risk**: Low - integration test shards only run pre-built APKs via `am instrument`
95+
96+
---
97+
98+
## Estimated Total Savings
99+
100+
| Optimization | Time Saved |
101+
|--------------|------------|
102+
| Remove build dry-run | ~4 min |
103+
| Remove unit test dry-run | ~2.5 min |
104+
| Remove gradle tasks list | ~5-10 min |
105+
| Skip codegen for integration test shards | ~12 min |
106+
| **Total** | **~23-28 minutes per CI run** |
107+
108+
---
109+
110+
## Known Issues (Not Actionable)
111+
112+
### `verify-connected-android-test` Rebuilds Everything
113+
114+
**Why it can't use pre-built APKs**: The Android Gradle Plugin's `connectedAndroidTest` task has hardcoded dependencies on `assembleDebug` and `assembleDebugAndroidTest`. There is no official way to skip these build steps - the AGP's `DeviceProviderInstrumentTestTask` explicitly requires them.
115+
116+
**Previous investigation** (early 2025): Attempted to use pre-built APKs with `connectedAndroidTest`, including reviewing AGP source code. No viable solution found.
117+
118+
**Current workaround**: The main `integration-test` job uses `am instrument` directly with pre-built APKs (via `matrix_run_android_tests.sh`). The `verify-connected-android-test` job exists as a sanity check to ensure `connectedAndroidTest` still works if ever needed, but it's not required for PR merges.
119+
120+
### Integration Test Shard Flakiness/Retries
121+
122+
**Observed**: All 4 shards were on "attempt 3" in run 21023241683. Shard 0 failed twice before succeeding (started 42 min after other shards).
123+
124+
**Root cause**: Resource contention on GitHub Actions runners when multiple emulators run simultaneously. This is a known limitation of the free tier runners.
125+
126+
**Mitigation options**:
127+
- Reduce shard count (current: 4, previously tried 8 which was too flaky)
128+
- Use paid runners with more resources
129+
- Accept occasional retries as cost of parallelism
130+
131+
### `set_build_datetime` Job
132+
133+
The separate job ensures timestamp consistency across all jobs that use it for artifact naming. While it adds ~30 seconds of runner startup overhead, removing it would require a different mechanism to share a consistent timestamp across jobs.
134+
135+
---
136+
137+
## Implementation Checklist
138+
139+
- [x] Remove "Debug Main Build Task Graph" step from `actions.yml`
140+
- [x] Remove "Debug Unit Test Task Graph" step from `actions.yml`
141+
- [x] Remove "List Gradle Tasks" step from `common-setup/action.yml`
142+
- [x] Add `skip_codegen` input to common-setup for jobs using pre-built APKs
143+
- [x] Skip codegen for integration-test shards (~3 min × 4 shards = ~12 min saved)
144+
- [x] Verify CI still passes after changes
145+
- [x] Monitor CI run times to confirm improvements
146+
147+
## Results
148+
149+
**Measured improvements (comparing runs with warm caches):**
150+
151+
| Job | Before | After | Savings |
152+
|-----|--------|-------|---------|
153+
| Unit Tests | 17m | 14m | -3m |
154+
| Integration Shard 1 | 12m | 8m | -4m |
155+
| Integration Shard 2 | 11m | 9m | -2m |
156+
| Integration Shard 3 | 12m | 9m | -3m |
157+
158+
**Total CI time reduced by ~12-13 minutes per run.**

0 commit comments

Comments
 (0)