/* 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 androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting.NONE import androidx.annotation.VisibleForTesting.PRIVATE import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import mozilla.components.support.base.log.logger.Logger import org.mozilla.fenix.GleanMetrics.PerfStartup import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.perf.StartupPathProvider.StartupPath import org.mozilla.fenix.perf.StartupStateProvider.StartupState private val activityClass = HomeActivity::class.java private val logger = Logger("StartupTypeTelemetry") /** * Records telemetry for the number of start ups. See the * [Fenix perf glossary](https://wiki.mozilla.org/index.php?title=Performance/Fenix/Glossary) * for specific definitions. * * This should be a member variable of [HomeActivity] because its data is tied to the lifecycle of an * Activity. Call [attachOnHomeActivityOnCreate] for this class to work correctly. * * N.B.: this class is lightly hardcoded to HomeActivity. */ class StartupTypeTelemetry( private val startupStateProvider: StartupStateProvider, private val startupPathProvider: StartupPathProvider ) { fun attachOnHomeActivityOnCreate(lifecycle: Lifecycle) { lifecycle.addObserver(StartupTypeLifecycleObserver()) } private fun getTelemetryLabel(startupState: StartupState, startupPath: StartupPath): String { // We don't use the enum name directly to avoid unintentional changes when refactoring. val stateLabel = when (startupState) { StartupState.COLD -> "cold" StartupState.WARM -> "warm" StartupState.HOT -> "hot" StartupState.UNKNOWN -> "unknown" } val pathLabel = when (startupPath) { StartupPath.MAIN -> "main" StartupPath.VIEW -> "view" // To avoid combinatorial explosion in label names, we bucket NOT_SET into UNKNOWN. StartupPath.NOT_SET, StartupPath.UNKNOWN -> "unknown" } return "${stateLabel}_$pathLabel" } @VisibleForTesting(otherwise = NONE) fun getTestCallbacks() = StartupTypeLifecycleObserver() @VisibleForTesting(otherwise = PRIVATE) fun record() { val startupState = startupStateProvider.getStartupStateForStartedActivity(activityClass) val startupPath = startupPathProvider.startupPathForActivity val label = getTelemetryLabel(startupState, startupPath) PerfStartup.startupType[label].add(1) logger.info("Recorded start up: $label") } @VisibleForTesting(otherwise = PRIVATE) inner class StartupTypeLifecycleObserver : DefaultLifecycleObserver { private var shouldRecordStart = false override fun onStart(owner: LifecycleOwner) { shouldRecordStart = true } override fun onResume(owner: LifecycleOwner) { // We must record in onResume because the StartupStateProvider can only be called for // STARTED activities and we can't guarantee our onStart is called before its. // // We only record if start was called for this resume to avoid recording // for onPause -> onResume states. if (shouldRecordStart) { record() shouldRecordStart = false } } } }