Hotfix/jrc 8103.bump.app.start.runnable.delay#8195
Conversation
…se#8103) The API-34 fix in 22.0.2 (PR firebase#7281) used a 50 ms MAX_BACKGROUND_RUNNABLE_DELAY to tell cold foreground starts from genuine background starts. That threshold was calibrated against a near-empty test app on API 35, where the gap between StartFromBackgroundRunnable firing and the first onActivityCreated was ~10 ms. On API 34+ physical devices, the gap is dominated by Android's main-thread vs Binder scheduling, not by app work: - 316 ms on a minimal ContentProvider repro (issue firebase#5920, PhilGlass). - 204 ms on a large production app (issue firebase#8103, manavchandrakar29011994). 50 ms is well below both, so _app_start is unconditionally suppressed for typical real-world apps on the very devices the fix was meant to support. Bump MAX_BACKGROUND_RUNNABLE_DELAY to 1000 ms. The mechanism (timing window) and the warm-start guard introduced in PR firebase#7281 (mainThreadRunnableTime is only set when onCreateTime == null) are unchanged. Genuine warm starts where the process was forked for background work seconds-to-minutes before any activity launch are still suppressed, since their gap far exceeds 1000 ms and MAX_LATENCY_BEFORE_UI_INIT (60 s) remains the upper backstop. Tests: - Rename the existing 50 ms tests to _within1000ms / _moreThan1000ms. - Add testStartFromBackground_largeAppGap_isForegroundStart: ~300 ms gap on API 34 now logs the trace (regression-locks firebase#8103). - Add testStartFromBackground_warmStart_stillSuppressed: 10 s gap is still suppressed.
…art detection The timing-window heuristic in resolveIsStartedFromBackground() infers "was this process forked to launch an Activity?" from the ordering of a posted runnable vs the first onActivityCreated callback. That ordering already shifted once on API 34+ (caused the original bug fixed in PR firebase#7281, and the threshold-too-tight follow-up in firebase#8103) and could shift again on future OS versions. Add a TODO documenting two cleaner alternatives so the next maintainer doesn't have to re-derive them: - API 35+: ApplicationStartInfo / getHistoricalProcessStartReasons gives the OS's authoritative start reason (LAUNCHER, SERVICE, CONTENT_PROVIDER, ...). Removes the heuristic entirely on supported devices. - API 34: RunningAppProcessInfo.importanceReasonCode + .importance captured early in FirebasePerfEarly (before our own ContentProvider work mutates the cause), combined with the timing window as a fallback. Comment-only change; no behavior or test surface affected.
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. |
firebase-perf: shadow-capture process start cause (#8103 follow-up, PR 1)
AppStartTrace's current "was this process forked to satisfy a foreground activitylaunch?" heuristic relies on the relative ordering of a posted main-thread runnable vs
the first
Activity.onCreate()Binder transaction. That ordering is undocumented,already shifted once on API 34+ (originally fixed in PR #7281 / #5920, threshold-tightened
into a regression that #8103 reports, and shipping-patched to 1000 ms on the parent
commit of this PR), and could shift again.
This PR adds an OS-provided causal signal alongside the existing decision and emits both
as custom attributes on the existing
_experiment_app_start_ttidtrace, so we cancompare them in production telemetry before deciding (in a later PR) whether to flip the
authoritative classification.
Background
We ran a standalone experiment app
(
api34-appstart-cause/) (TODO: UPLOAD TEST APP) on emulators at API 33, 34, 35, and 36to validate the causal-signal approach before introducing it to the SDK. The full
results are out of tree but the headline findings are:
ApplicationStartInfoavailable?importance == FOREGROUNDon launcher tap?b/339891952)So on API 35+,
ApplicationStartInfo.getReason()is authoritative and available at thefirst ContentProvider checkpoint. On API 34,
RunningAppProcessInfo.importanceat firstcapture is a reliable foreground signal even when the timing-race itself doesn't
reproduce. Pre-API-34, the existing logic is correct and we don't try.
importanceReasonCodewas tested and is not useful — alwaysUNKNOWN (0)acrossall runs.
Implementation
New file:
ProcessStartCause.javaOS-version-conditioned helper. One static factory
capture(@Nullable Context), immutablefields, no held references:
ActivityManager.getHistoricalProcessStartReasons(1)via reflection. Therepo's
compileSdkVersionis 34, soApplicationStartInfocan't be imported directly.Constants (
START_REASON_*/START_TYPE_*) are redeclared as localintliteralsmatching the public AOSP values; the helper logs the raw int as
UNKNOWN_<n>if thevalues ever drift.
ActivityManager.getMyMemoryState(RunningAppProcessInfo).IMPORTANCE_FOREGROUND⇒FOREGROUND; anything else ⇒UNKNOWN(we don't classifybackground here — caller falls back to the existing heuristic).
Cause.UNKNOWN. The pre-API-34 timing logic is correct on thoseversions; the helper still captures
importancefor telemetry continuity.AppStartTrace.javaregisterActivityLifecycleCallbacks(called fromFirebasePerfEarlyduring the ContentProvider init chain — the experiment confirmedthis is early enough that OS-set values reflect the original fork cause).
addProcessStartCauseExperimentAttributes(...)that puts sixcustom attributes on the existing
_experiment_app_start_ttidtrace, alongside theexisting
systemDeterminedForegroundattribute:processStartCauseforeground/background/unknownprocessStartReasonApi35PlusLAUNCHER,BROADCAST,CONTENT_PROVIDER, or""processStartTypeApi35PlusCOLD/WARM/HOTor""processImportanceApi34""timingWindowDecisionforeground/background(mirrors the existing decision)decisionAgreedtrue/false/unknown@VisibleForTestingaccessors for the cause field.MAX_BACKGROUND_RUNNABLE_DELAY,mainThreadRunnableTime,StartFromBackgroundRunnable,resolveIsStartedFromBackground(), and everyisStartedFromBackground-gated early return in the lifecycle callbacks.Tests
ProcessStartCauseTest(7 cases) — covers null context, pre-API-34 path,API 34 with
FOREGROUND/SERVICE/CACHEDimportance, constructor preservation,and the holder view. The API 35+ reflective branch is exercised end-to-end in the
out-of-tree experiment app rather than under Robolectric 4.12, which doesn't support
@Config(sdk = 35).AppStartTraceTestgainsexperimentTrace_includesProcessStartCauseAttributeswhich injects a known
ProcessStartCauseand asserts all six attributes appear on theexperiment trace with the expected values.
Rollout intent (post-merge)
_experiment_app_start_ttiddata to accumulate from the field. CompareprocessStartCausevstimingWindowDecision, grouped byprocessStartReasonApi35Plusand
processImportanceApi34.especially the
processImportanceApi34 == 100 (FOREGROUND)→ cold-launcher-startcorrespondence.
ProcessStartCausefirst, gated by a remote-config kill switch, with the timing window as fallback.
machinery for API 34+ entirely.
This PR ships none of those next steps — it only adds the telemetry that informs PR 2.
Test plan
./gradlew :firebase-perf:spotlessApply./gradlew :firebase-perf:testReleaseUnitTest— 12 test suites, 0 failures /0 errors.
AppStartTraceTest: 9/9 (was 8/8).ProcessStartCauseTest: 7/7 (new).compileSdkVersion = 34confirmed (compileReleaseJavaWithJavacpasses; the API 35+ reference is reflective so no missing-class warnings).
~/AndroidStudioProjects/api34-appstart-cause/against a physical API 34 Pixeldevice to confirm the importance signal matches the bug-reported environment.
(Not gating this PR — PR 1 is observation-only and the emulator data already
validates the signal collection.)