Skip to content

Commit

Permalink
Add FXIOS-10125 Telemetry event: tab error detection (#22221)
Browse files Browse the repository at this point in the history
* [FXIOS-10125] Initial work to add a telemetry event for detecting potential tab loss issues in production

* [FXIOS-10125] Comment

* [FXIOS-10125] Record and validate on main thread only

* [FXIOS-10125] Add tab-loss-detected to metrics.yaml

* [FXIOS-10125] Add Glean telemetry test

* [FXIOS-10125] Minor fixes for updated telemetry

* [FXIOS-10125] Use UserDefaultsInterface

* [FXIOS-10125] Use WindowManager protocol
  • Loading branch information
mattreaganmozilla authored Oct 7, 2024
1 parent 6dfff7e commit 42301f0
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 1 deletion.
4 changes: 4 additions & 0 deletions firefox-ios/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
1DA6F6512B48B42900BB5AD6 /* WindowEventCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA6F6502B48B42900BB5AD6 /* WindowEventCoordinator.swift */; };
1DA710072AE7106B00677F6B /* AppDataUsageReportSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA710062AE7106B00677F6B /* AppDataUsageReportSetting.swift */; };
1DC372022B23C80F000F96C8 /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC372012B23C80F000F96C8 /* WindowManager.swift */; };
1DD4B26E2CA4D09100B51945 /* TabErrorTelemetryHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DD4B26D2CA4D09100B51945 /* TabErrorTelemetryHelper.swift */; };
1DDAD13E24F0651C007623C8 /* TopSitesWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDAD13C24F064F7007623C8 /* TopSitesWidget.swift */; };
1DDE3DB32AC34E1E0039363B /* TabCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDE3DB22AC34E1E0039363B /* TabCell.swift */; };
1DDE3DB52AC360EC0039363B /* TabCellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDE3DB42AC360EC0039363B /* TabCellTests.swift */; };
Expand Down Expand Up @@ -2435,6 +2436,7 @@
1DA6F6502B48B42900BB5AD6 /* WindowEventCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowEventCoordinator.swift; sourceTree = "<group>"; };
1DA710062AE7106B00677F6B /* AppDataUsageReportSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDataUsageReportSetting.swift; sourceTree = "<group>"; };
1DC372012B23C80F000F96C8 /* WindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = "<group>"; };
1DD4B26D2CA4D09100B51945 /* TabErrorTelemetryHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabErrorTelemetryHelper.swift; sourceTree = "<group>"; };
1DDAD13C24F064F7007623C8 /* TopSitesWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopSitesWidget.swift; sourceTree = "<group>"; };
1DDE3DB22AC34E1E0039363B /* TabCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabCell.swift; sourceTree = "<group>"; };
1DDE3DB42AC360EC0039363B /* TabCellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabCellTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -13107,6 +13109,7 @@
8A44F20D2B585E1F0016BC81 /* HomepageTelemetry.swift */,
8A95FF632B1E969E00AC303D /* TelemetryContextualIdentifier.swift */,
EBF47E6F1F7979DF00899189 /* TelemetryWrapper.swift */,
1DD4B26D2CA4D09100B51945 /* TabErrorTelemetryHelper.swift */,
8A0727452B4890B50071BB9F /* WebviewTelemetry.swift */,
8A359EF42A1FD4CF004A5BB7 /* Wrapper */,
);
Expand Down Expand Up @@ -15599,6 +15602,7 @@
D88FDAAF1F4E2BA000FD9709 /* PhotonActionSheetAnimator.swift in Sources */,
C83432FE26BAD30D00ABAAA6 /* EnhancedTrackingProtectionDetailsVC.swift in Sources */,
E698FFDA1B4AADF40001F623 /* TabScrollController.swift in Sources */,
1DD4B26E2CA4D09100B51945 /* TabErrorTelemetryHelper.swift in Sources */,
8A359EF32A1FD449004A5BB7 /* AdjustWrapper.swift in Sources */,
8AAEBA022BF5110A000C02B5 /* MicrosurveyAction.swift in Sources */,
D34510881ACF415700EC27F0 /* SearchLoader.swift in Sources */,
Expand Down
8 changes: 7 additions & 1 deletion firefox-ios/Client/Application/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var sceneCoordinator: SceneCoordinator?
var routeBuilder = RouteBuilder()
var logger: Logger = DefaultLogger.shared

private let logger: Logger = DefaultLogger.shared
private let tabErrorTelemetryHelper = TabErrorTelemetryHelper.shared

// MARK: - Connecting / Disconnecting Scenes

Expand Down Expand Up @@ -87,6 +89,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// Resume previously stopped downloads for, and on, THIS scene only.
if let uuid = sceneCoordinator?.windowUUID {
downloadQueue.resumeAll(for: uuid)
AppEventQueue.wait(for: .tabRestoration(uuid)) {
self.tabErrorTelemetryHelper.validateTabCountForForegroundedScene(uuid)
}
}
}

Expand All @@ -101,6 +106,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
logger.log("SceneDelegate: scene did enter background. UUID: \(logUUID)", level: .info, category: .lifecycle)
if let uuid = sceneCoordinator?.windowUUID {
downloadQueue.pauseAll(for: uuid)
tabErrorTelemetryHelper.recordTabCountForBackgroundedScene(uuid)
}
}

Expand Down
65 changes: 65 additions & 0 deletions firefox-ios/Client/Telemetry/TabErrorTelemetryHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// 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/

import UIKit
import Shared
import Common

/// Helper utility that aims to detect potential bugs in production which could result in tab loss.
final class TabErrorTelemetryHelper {
static let shared = TabErrorTelemetryHelper()
private let defaultsKey = "TabErrorTelemetryHelper_Data"
private let telemetryWrapper: TelemetryWrapperProtocol
private let defaults: UserDefaultsInterface
private let windowManager: WindowManager

private init(telemetryWrapper: TelemetryWrapperProtocol = TelemetryWrapper.shared,
windowManager: WindowManager = AppContainer.shared.resolve(),
defaults: UserDefaultsInterface = UserDefaults.standard) {
self.telemetryWrapper = telemetryWrapper
self.defaults = defaults
self.windowManager = windowManager
}

/// Records the scene (windows) tab count for the purposes of sanity-checking for
/// potential tab-loss related errors. Such bugs can significantly impact users, so
/// we attempt to detect any issues which could indicate potential tab loss.
func recordTabCountForBackgroundedScene(_ window: WindowUUID) {
ensureMainThread {
var tabCounts = self.defaults.object(forKey: self.defaultsKey) as? [String: Int] ?? [String: Int]()
let tabCount = self.getTabCount(window: window)
tabCounts[window.uuidString] = tabCount
UserDefaults.standard.set(tabCounts, forKey: self.defaultsKey)
}
}

/// Validates the tab count against the recorded tab count. If this has decreased
/// without any obvious cause (e.g. Close All Tabs action) then it is suggestive of
/// a potential bug impacting users, and a MetricKit event is logged.
func validateTabCountForForegroundedScene(_ window: WindowUUID) {
ensureMainThread {
guard let tabCounts = self.defaults.object(forKey: self.defaultsKey) as? [String: Int],
let expectedTabCount = tabCounts[window.uuidString] else { return }
let currentTabCount = self.getTabCount(window: window)

if expectedTabCount > 1 && (expectedTabCount - currentTabCount) > 1 {
// Potential tab loss bug detected. Log a MetricKit error.
self.sendTelemetryTabLossDetectedEvent()
}
}
}

// MARK: - Internal Utility

private func getTabCount(window: WindowUUID) -> Int {
return windowManager.tabManager(for: window).normalTabs.count
}

private func sendTelemetryTabLossDetectedEvent() {
telemetryWrapper.recordEvent(category: .information,
method: .error,
object: .app,
value: .tabLossDetected)
}
}
3 changes: 3 additions & 0 deletions firefox-ios/Client/Telemetry/TelemetryWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,7 @@ extension TelemetryWrapper {
case crashedLastLaunch = "crashed_last_launch"
case cpuException = "cpu_exception"
case hangException = "hang-exception"
case tabLossDetected = "tab_loss_detected"
case fxSuggestionTelemetryInfo = "fx-suggestion-telemetry-info"
case fxSuggestionPosition = "fx-suggestion-position"
case fxSuggestionDidTap = "fx-suggestion-did-tap"
Expand Down Expand Up @@ -2011,6 +2012,8 @@ extension TelemetryWrapper {
}
case(.information, .error, .app, .crashedLastLaunch, _):
GleanMetrics.AppErrors.crashedLastLaunch.record()
case(.information, .error, .app, .tabLossDetected, _):
GleanMetrics.AppErrors.tabLossDetected.record()
case(.information, .error, .app, .cpuException, let extras):
if let quantity = extras?[EventExtraKey.size.rawValue] as? Int32 {
let properties = GleanMetrics.AppErrors.CpuExceptionExtra(size: quantity)
Expand Down
11 changes: 11 additions & 0 deletions firefox-ios/Client/metrics.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5704,6 +5704,17 @@ app_errors:
notification_emails:
- [email protected]
expires: never
tab_loss_detected:
type: event
description: |
Recorded when we detect potential tab loss
bugs:
- https://github.com/mozilla-mobile/firefox-ios/issues/22180
data_reviews:
- https://github.com/mozilla-mobile/firefox-ios/pull/22221
notification_emails:
- [email protected]
expires: never

webview:
did_fail_provisional:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1512,6 +1512,15 @@ class TelemetryWrapperTests: XCTestCase {
testEventMetricRecordingSuccess(metric: GleanMetrics.AppErrors.hangException)
}

func test_error_tabLossDetectedIsCalled() {
TelemetryWrapper.recordEvent(category: .information,
method: .error,
object: .app,
value: .tabLossDetected)

testEventMetricRecordingSuccess(metric: GleanMetrics.AppErrors.tabLossDetected)
}

// MARK: - RecordSearch
func test_RecordSearch_GleanIsCalledSearchSuggestion() {
let extras = [TelemetryWrapper.EventExtraKey.recordSearchLocation.rawValue: "suggestion",
Expand Down

0 comments on commit 42301f0

Please sign in to comment.