1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 package org.mozilla.fenix.telemetry
7 import mozilla.components.browser.state.action.BrowserAction
8 import mozilla.components.browser.state.action.ContentAction
9 import mozilla.components.browser.state.action.DownloadAction
10 import mozilla.components.browser.state.action.EngineAction
11 import mozilla.components.browser.state.action.TabListAction
12 import mozilla.components.browser.state.selector.findTab
13 import mozilla.components.browser.state.selector.findTabOrCustomTab
14 import mozilla.components.browser.state.selector.normalTabs
15 import mozilla.components.browser.state.state.BrowserState
16 import mozilla.components.browser.state.state.EngineState
17 import mozilla.components.browser.state.state.SessionState
18 import mozilla.components.concept.base.crash.CrashReporting
19 import mozilla.components.lib.state.Middleware
20 import mozilla.components.lib.state.MiddlewareContext
21 import mozilla.components.support.base.android.Clock
22 import mozilla.components.support.base.log.logger.Logger
23 import mozilla.telemetry.glean.private.NoExtras
24 import org.mozilla.fenix.Config
25 import org.mozilla.fenix.GleanMetrics.Events
26 import org.mozilla.fenix.GleanMetrics.Metrics
27 import org.mozilla.fenix.components.metrics.Event
28 import org.mozilla.fenix.components.metrics.MetricController
29 import org.mozilla.fenix.utils.Settings
30 import org.mozilla.fenix.GleanMetrics.EngineTab as EngineMetrics
33 * [Middleware] to record telemetry in response to [BrowserAction]s.
35 * @property settings reference to the application [Settings].
36 * @property metrics [MetricController] to pass events that have been mapped from actions
38 class TelemetryMiddleware(
39 private val settings: Settings,
40 private val metrics: MetricController,
41 private val crashReporting: CrashReporting? = null,
42 ) : Middleware<BrowserState, BrowserAction> {
44 private val logger = Logger("TelemetryMiddleware")
46 @Suppress("TooGenericExceptionCaught", "ComplexMethod", "NestedBlockDepth")
48 context: MiddlewareContext<BrowserState, BrowserAction>,
49 next: (BrowserAction) -> Unit,
50 action: BrowserAction,
52 // Pre process actions
54 is ContentAction.UpdateLoadingStateAction -> {
55 context.state.findTab(action.sessionId)?.let { tab ->
56 // Record UriOpened event when a non-private page finishes loading
57 if (tab.content.loading && !action.loading) {
58 Events.normalAndPrivateUriCount.add()
62 is DownloadAction.AddDownloadAction -> { /* NOOP */ }
63 is EngineAction.KillEngineSessionAction -> {
64 val tab = context.state.findTabOrCustomTab(action.tabId)
65 onEngineSessionKilled(context.state, tab)
67 is ContentAction.CheckForFormDataExceptionAction -> {
68 Events.formDataFailure.record(NoExtras())
69 if (Config.channel.isNightlyOrDebug) {
70 crashReporting?.submitCaughtException(action.throwable)
74 is EngineAction.LoadUrlAction -> {
75 metrics.track(Event.GrowthData.FirstUriLoadForDay)
84 // Post process actions
86 is TabListAction.AddTabAction,
87 is TabListAction.AddMultipleTabsAction,
88 is TabListAction.RemoveTabAction,
89 is TabListAction.RemoveAllNormalTabsAction,
90 is TabListAction.RemoveAllTabsAction,
91 is TabListAction.RestoreAction,
93 // Update/Persist tabs count whenever it changes
94 settings.openTabsCount = context.state.normalTabs.count()
95 if (context.state.normalTabs.isNotEmpty()) {
96 Metrics.hasOpenTabs.set(true)
98 Metrics.hasOpenTabs.set(false)
108 * Collecting some engine-specific (GeckoView) telemetry.
109 * https://github.com/mozilla-mobile/android-components/issues/9366
111 private fun onEngineSessionKilled(state: BrowserState, tab: SessionState?) {
113 logger.debug("Could not find tab for killed engine session")
117 val isSelected = tab.id == state.selectedTabId
118 val age = tab.engineState.age()
120 // Increment the counter of killed foreground/background tabs
121 val tabKillLabel = if (isSelected) { "foreground" } else { "background" }
122 EngineMetrics.kills[tabKillLabel].add()
124 // Record the age of the engine session of the killed foreground/background tab.
125 if (isSelected && age != null) {
126 EngineMetrics.killForegroundAge.accumulateSamples(listOf(age))
127 } else if (age != null) {
128 EngineMetrics.killBackgroundAge.accumulateSamples(listOf(age))
133 @Suppress("MagicNumber")
134 private fun EngineState.age(): Long? {
135 val timestamp = (timestamp ?: return null)
136 val now = Clock.elapsedRealtime()
137 return (now - timestamp)