You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
154 lines
6.9 KiB
Kotlin
154 lines
6.9 KiB
Kotlin
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.fenix.perf
|
|
|
|
import android.app.Activity
|
|
import org.mozilla.fenix.perf.AppStartReasonProvider.StartReason
|
|
import org.mozilla.fenix.perf.StartupActivityLog.LogEntry
|
|
|
|
/**
|
|
* Identifies the "state" of start up where state can be COLD/WARM/HOT and possibly others. See
|
|
* the [Fenix perf glossary](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary)
|
|
* for specific definitions.
|
|
*
|
|
* This class is nuanced: **please read the kdoc carefully before using it.** Consider contacting
|
|
* the perf team with your use case.
|
|
*
|
|
* For this class, we use the terminology from the [StartupActivityLog] such as STARTED and STOPPED.
|
|
* However, we're assuming STARTED means foregrounded and STOPPED means backgrounded. If this
|
|
* assumption is false, the logic in this class may be incorrect.
|
|
*/
|
|
class StartupStateProvider(
|
|
private val startupLog: StartupActivityLog,
|
|
private val startReasonProvider: AppStartReasonProvider
|
|
) {
|
|
|
|
/**
|
|
* The restoration state of the application upon this most recent start up. See the
|
|
* [Fenix perf glossary](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary)
|
|
* for specific definitions.
|
|
*/
|
|
enum class StartupState {
|
|
COLD, WARM, HOT,
|
|
|
|
/**
|
|
* A start up state where we weren't able to bucket it into the other categories.
|
|
* This includes, but is not limited to:
|
|
* - if the activity this is called from is not currently started
|
|
* - if the currently started activity is not the first started activity
|
|
*/
|
|
UNKNOWN;
|
|
}
|
|
|
|
/**
|
|
* Returns the [StartupState] for the currently started activity. Note: the state will be
|
|
* [StartupState.UNKNOWN] if the currently started activity is not the first started activity.
|
|
*
|
|
* This method must be called after the foreground Activity is STARTED.
|
|
*/
|
|
fun getStartupStateForStartedActivity(activityClass: Class<out Activity>): StartupState = when {
|
|
isColdStartForStartedActivity(activityClass) -> StartupState.COLD
|
|
isWarmStartForStartedActivity(activityClass) -> StartupState.WARM
|
|
isHotStartForStartedActivity(activityClass) -> StartupState.HOT
|
|
else -> StartupState.UNKNOWN
|
|
}
|
|
|
|
/**
|
|
* Returns true if the current startup state is COLD and the currently started activity is the
|
|
* first started activity (i.e. we can use it for performance measurements).
|
|
*
|
|
* This method must be called after the foreground Activity is STARTED.
|
|
*/
|
|
fun isColdStartForStartedActivity(activityClass: Class<out Activity>): Boolean {
|
|
// A cold start means:
|
|
// - the process was started for the first started activity (e.g. not a service)
|
|
// - the first started activity ever is still active
|
|
//
|
|
// Thus, for the activity log we expect:
|
|
// [... Activity-STARTED, App-STARTED]
|
|
// since if another Activity was started, it would appear after App-STARTED. This is where:
|
|
// - the app has not been stopped ever
|
|
if (startReasonProvider.reason != StartReason.ACTIVITY) {
|
|
return false
|
|
}
|
|
|
|
val isLastStartedActivityStillStarted = startupLog.log.takeLast(2) == listOf(
|
|
LogEntry.ActivityStarted(activityClass),
|
|
LogEntry.AppStarted
|
|
)
|
|
return !startupLog.log.contains(LogEntry.AppStopped) && isLastStartedActivityStillStarted
|
|
}
|
|
|
|
/**
|
|
* A short-circuit implementation of [isColdStartForStartedActivity] that will return false early
|
|
* so we don't have to call [isColdStartForStartedActivity].
|
|
*
|
|
* When this can be called might be tightly coupled to [ColdStartupDurationTelemetry]: use at
|
|
* your own risk.
|
|
*/
|
|
fun shouldShortCircuitColdStart(): Boolean = startupLog.log.contains(LogEntry.AppStopped)
|
|
|
|
/**
|
|
* Returns true if the current startup state is WARM and the currently started activity is the
|
|
* first started activity for this start up (i.e. we can use it for performance measurements).
|
|
*
|
|
* This method must be called after the foreground activity is STARTED.
|
|
*/
|
|
fun isWarmStartForStartedActivity(activityClass: Class<out Activity>): Boolean {
|
|
// A warm start means:
|
|
// - the app was backgrounded and has since been started
|
|
// - the first started activity since the app was started is still active.
|
|
// - that activity was created before being started
|
|
//
|
|
// For the activity log, we expect:
|
|
// [... App-STOPPED, ... Activity-CREATED, Activity-STARTED, App-STARTED]
|
|
// where:
|
|
// - App-STOPPED is the last STOPPED seen
|
|
// - we're assuming App-STARTED will only be last if one activity is started (as observed)
|
|
if (!startupLog.log.contains(LogEntry.AppStopped)) {
|
|
return false // if the app hasn't been stopped, it's not a warm start.
|
|
}
|
|
val afterLastStopped = startupLog.log.takeLastWhile { it != LogEntry.AppStopped }
|
|
|
|
val isLastActivityCreatedStillStarted = afterLastStopped.takeLast(3) == listOf(
|
|
LogEntry.ActivityCreated(activityClass),
|
|
LogEntry.ActivityStarted(activityClass),
|
|
LogEntry.AppStarted
|
|
)
|
|
return isLastActivityCreatedStillStarted
|
|
}
|
|
|
|
/**
|
|
* Returns true if the current startup state is HOT and the currently started activity is the
|
|
* first started activity for this start up (i.e. we can use it for performance measurements).
|
|
*
|
|
* This method must be called after the foreground activity is STARTED.
|
|
*/
|
|
fun isHotStartForStartedActivity(activityClass: Class<out Activity>): Boolean {
|
|
// A hot start means:
|
|
// - the app was backgrounded and has since been started
|
|
// - the first started activity since the app was started is still active.
|
|
// - that activity was not created before being started
|
|
//
|
|
// For the activity log, we expect:
|
|
// [... App-STOPPED, ... Activity-STARTED, App-STARTED]
|
|
// where:
|
|
// - App-STOPPED is the last STOPPED seen
|
|
// - App-CREATED is NOT called for this activity
|
|
// - we're assuming App-STARTED will only be last if one activity is started (as observed)
|
|
if (!startupLog.log.contains(LogEntry.AppStopped)) {
|
|
return false // if the app hasn't been stopped, it's not a hot start.
|
|
}
|
|
val afterLastStopped = startupLog.log.takeLastWhile { it != LogEntry.AppStopped }
|
|
|
|
val isLastActivityStartedStillStarted = afterLastStopped.takeLast(2) == listOf(
|
|
LogEntry.ActivityStarted(activityClass),
|
|
LogEntry.AppStarted
|
|
)
|
|
return !afterLastStopped.contains(LogEntry.ActivityCreated(activityClass)) &&
|
|
isLastActivityStartedStillStarted
|
|
}
|
|
}
|