Startup profiling
Skill almasumdev/awesome-mobile-performance-agent-skills/.github/skills/startup/startup-profiling
Agent skills for profiling and optimizing mobile app performance (startup, memory, frame-rate, network).
npx -y skills add almasumdev/awesome-mobile-performance-agent-skills --skill startup-profilingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Profile cold, warm, and hot start with Android Macrobenchmark, Xcode Instruments App Launch template, and Flutter DevTools. Use when you need to attribute startup time to specific phases.
SKILL.md
5.3 KB, as published. Nobody here has run it
Startup Profiling
Instructions
Startup time without a trace is a guess. This skill shows how to capture reproducible startup traces on every platform and how to read them.
1. Android — Macrobenchmark + Perfetto
Macrobenchmark runs your app in release mode on a physical device and emits a startup-compilation-*.trace file plus metrics.
module-benchmark/build.gradle.kts:
plugins { id("androidx.benchmark") ; id("com.android.test") }
android {
defaultConfig { testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner" }
buildTypes {
create("benchmark") { initWith(getByName("release")); signingConfig = signingConfigs.getByName("debug") }
}
}
dependencies {
implementation("androidx.benchmark:benchmark-macro-junit4:1.3.0")
}
Test:
@RunWith(AndroidJUnit4::class)
class ColdStartupBenchmark {
@get:Rule val rule = MacrobenchmarkRule()
@Test fun startup() = rule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(
StartupTimingMetric(),
TraceSectionMetric("databaseInit"), // custom trace section
),
iterations = 10,
startupMode = StartupMode.COLD,
compilationMode = CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require),
) {
pressHome(); startActivityAndWait()
device.wait(Until.hasObject(By.res("feed")), 5_000)
device.findObject(By.res("feed")).click() // TTFD signal
}
}
Reading the output: open the generated .perfetto-trace in ui.perfetto.dev. Look at:
MainActivity.onCreateslice width.bindApplication(ART process bootstrap).Choreographer#doFramegaps — long gaps before firstdoFrame= main-thread block.android.os.AsyncTask/ your own trace sections for hot spots.
Add custom sections with androidx.tracing:
androidx.tracing.Trace.beginSection("databaseInit")
try { AppDatabase.getInstance(context) } finally { Trace.endSection() }
2. iOS — Instruments "App Launch" Template
- Xcode → Product → Profile → choose App Launch template.
- Run on a physical device with the Release config (Profile builds use Release by default).
- Instruments prints four phases in the track view:
- System Interface Initialization (dyld, ObjC runtime).
- Static Runtime Initialization (
+[load], static initializers). - UIKit Initialization /
application(_:didFinishLaunchingWithOptions:). - Initial Frame Render.
- Any slice > 200 ms on a modern device is suspicious. Drill in to see the heaviest symbols.
Signpost your own TTFD:
import os.signpost
let log = OSLog(subsystem: "app", category: .pointsOfInterest)
os_signpost(.event, log: log, name: "TTFD")
Dyld/linker cost: check Time Profiler's "System Trace" → "Main Thread" for dyld symbols. If high, reduce dynamic framework count (switch to static linking for first-party frameworks).
3. Flutter — flutter run --trace-startup
flutter run --profile --trace-startup --verbose
Produces build/start_up_info.json:
{
"engineEnterTimestampMicros": 1234,
"timeToFirstFrameMicros": 890000,
"timeToFrameworkInitMicros": 210000,
"timeAfterFrameworkInitMicros": 680000
}
Instrument TTFD:
void main() {
runApp(const MyApp());
WidgetsBinding.instance.addPostFrameCallback((_) {
developer.Timeline.instantSync('TTFD');
});
}
Open DevTools → Performance → Enhance Tracing → Trace shader compilation. Shader compilation jank on first frames is common on Android; use --cache-sksl + Impeller.
4. React Native — Systrace + Hermes
Capture a systrace that includes JS:
adb shell atrace --async_start -t 10 gfx view wm am sched hal
# launch app, wait for interactive
adb shell atrace --async_stop > startup.trace
Open in Perfetto. Sections to look for:
JSBundleLoader.loadScriptReactContext.initializeUIManagerModule.createViewpile-up- Slow native module startup — rename legacy modules to TurboModules.
Hermes sampling profiler for the JS thread:
import * as Hermes from 'react-native/Libraries/SamplingProfiler';
Hermes.startSamplingProfiler();
// trigger launch-like work
Hermes.stopSamplingProfiler('/sdcard/hermes-sample.cpuprofile');
Open the .cpuprofile in Chrome DevTools → Performance → Load Profile.
5. Treat Startup Traces as Regression Gates
Commit a baseline JSON to the repo and fail CI on > 5% regression. Record:
- device model,
- OS version,
- build variant,
- p50 and p95 of 10 runs.
Checklist
- Android Macrobenchmark runs in CI on a known device or Firebase Test Lab matrix.
- iOS
XCTApplicationLaunchMetrictest runs on a real device lane. - Flutter:
start_up_info.jsoncaptured per PR for critical flows. - RN: Hermes sampling profile archived for slow PRs.
- Baseline JSON committed and regression threshold enforced.
- TTFD signpost /
reportFullyDrawn()/Timeline.instantSync('TTFD')implemented. - Traces are stored as CI artifacts and linked from PR descriptions.