AnalyticsClient.<init>: 6 ms -> 188 ms, MobileBoost records sampled CPU
profiles while your app runs and needs two things from your CI to read them:
Both are a one-time change to your build setup plus one upload step. Most teams
already produce the symbol files for Google Play or a crash reporter.
What every performance build needs
- Release-like. Build with the same compiler optimisation, minification and SDKs as production. A debug build is several times slower in places your users never see, so its numbers would mislead you.
- The same setup for both sides. The base build (your
mainbranch) and the head build (the pull request) have to come from the same build type and flags, or the report measures your build configuration instead of your change. Buildmainwith the same job on every merge so a base build always exists. - Its own symbol files. Symbols only match the build they were produced with. Upload the mapping file or dSYMs from the same CI job as the build, never from an earlier one.
- The backend your tests already use. Performance runs use the same environment as your functional tests. Recorded responses are replayed for the mocked network runs, and the live runs talk to that backend.
Android
1
Add a perf build type
Copy your release build type, so the performance build is optimised and
minified exactly like production, but sign it with your debug key so CI does
not need the upload key.If your release build type sets
app/build.gradle.kts
isDebuggable = true, leave it out of
perf. A debuggable build runs slower than what your users get, so its
numbers would not be representative.2
Make only the perf build profileable
Create a manifest that only the
perf build type merges in. Your production
release stays unchanged.app/src/perf/AndroidManifest.xml
profileable lets profiling tools sample a release build without the
overhead of a debug build. It takes effect on Android 10 (API 29) and later,
which covers every MobileBoost device.3
Keep the R8 mapping file
R8 renames your classes and methods in release builds, so a profile shows
names like Keep obfuscation on in
a.b.c() until it is mapped back. The mapping is written next to
the build:perf, as in production. Turning it off with
-dontobfuscate saves the upload step but also changes the code you are
measuring.4
Ship your Baseline Profile with the build
If your app has a Baseline Profile, add the Profile Installer library so the
profile is applied when MobileBoost installs the APK directly. Google Play
applies it for your users; a direct install does not, and startup would
look slower than it is for them.
app/build.gradle.kts
5
Build an APK
MobileBoost installs APKs. Build the perf variant:If your pipeline only produces an App Bundle, turn it into one universal APK
with
bundletool:6
Check the build before uploading
apkanalyzer ships with the Android SDK command-line tools:GitHub Actions for Android
Run the same job on pull requests and on every merge tomain, so each pull
request has a base build to compare against.
.github/workflows/mobileboost-perf-android.yml
iOS
1
Build the Release configuration for a device
Profile the Release configuration, or a
Perf configuration duplicated from
it (Project > Info > Configurations > + > Duplicate
“Release” Configuration). Use a separate configuration only if you need to
switch something off for performance runs, see
What changes after re-signing.The build must be for a real device (iphoneos). Simulator builds run on the
Mac’s CPU and say nothing about phone performance.2
Produce dSYMs for that configuration
In the target’s Build Settings, set Debug Information Format to
DWARF with dSYM File for the configuration you upload. New projects
already do this for Release; check targets that were added later, and
frameworks you build from source.Stripping the app binary is fine, and it is what production does: the dSYM
keeps the symbols. For binary frameworks from vendors, symbols exist only if
the vendor ships dSYMs. Their code then shows as an address with the
framework’s name, which still tells you whose code it is.
3
Archive and export an IPA
Use the export method your CI already uses for internal testers (Ad Hoc,
called Release Testing in Xcode 15 and later, or Development). MobileBoost
re-signs the app for its own devices, so the export method does not change
what is measured.
4
Collect the dSYMs from the archive
The archive holds the dSYMs of the app and of every embedded framework built
with it:
5
Check that the dSYM matches the app
Each build has its own UUID, and a dSYM only works for the binary with the
same one:Both commands must print the same UUID.
What changes after re-signing
MobileBoost re-signs your IPA with its own team so it installs on MobileBoost devices and can be profiled. Re-signing keeps the app identifier, the team identifier,get-task-allow and your keychain access groups, and drops every
other entitlement. Plan your performance build around that:
- Push notifications, App Groups, associated domains and other capabilities are not available. Flows that depend on them will not complete on a performance run.
-
Integrity checks that verify your own signature must be off. Jailbreak
detection, code-signature checks or a check of the team ID make a re-signed
build quit or behave differently. Switch them off in a
Perfconfiguration with a compilation condition: - Keep your other SDKs as in production. Analytics, crash reporting and feature flag SDKs are part of what your users wait for, and measuring them is the point.
GitHub Actions for iOS
.github/workflows/mobileboost-perf-ios.yml
How MobileBoost handles your symbol files
Mapping files and dSYMs describe the structure of your code. MobileBoost uses them only to put function names on the profiles of the build they came with, and keeps them with that build.Checklist
Exact timings for your own milestones
With the setup above, flows end when the screen stops changing and profiles are sampled. To measure exactly from and to the points you choose, mark them in your code:reportFullyDrawn() or trace("name") { ... } on Android, and
os_signpost on iOS. The report then shows those spans by name.
