Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tds experiment metrics #3839

Merged
merged 23 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions Core/ContentBlocking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Foundation
import BrowserServicesKit
import Combine
import Common
import PixelExperimentKit

public final class ContentBlocking {

Expand All @@ -41,6 +42,11 @@ public final class ContentBlocking {
}
}

enum PixelParameterName {
static let experimentName = "experimentName"
static let etag = "etag"
}

private init(privacyConfigurationManager: PrivacyConfigurationManaging? = nil) {
let internalUserDecider = DefaultInternalUserDecider(store: InternalUserStore())
let statisticsStore = StatisticsUserDefaults()
Expand Down Expand Up @@ -80,9 +86,14 @@ public final class ContentBlocking {

private static let debugEvents = EventMapping<ContentBlockerDebugEvents> { event, error, parameters, onComplete in
let domainEvent: Pixel.Event
var finalParameters = parameters ?? [:]
switch event {
case .trackerDataParseFailed:
domainEvent = .trackerDataParseFailed
if let experimentName = TDSOverrideExperimentMetrics.activeTDSExperimentNameWithCohort {
finalParameters[PixelParameterName.experimentName] = experimentName
finalParameters[PixelParameterName.etag] = UserDefaultsETagStorage().loadEtag(for: .trackerDataSet)
}

case .trackerDataReloadFailed:
domainEvent = .trackerDataReloadFailed
Expand Down Expand Up @@ -131,16 +142,20 @@ public final class ContentBlocking {
case .contentBlockingCompilationTaskPerformance(let retryCount, let timeBucketAggregation):
domainEvent = .contentBlockingCompilationTaskPerformance(iterationCount: retryCount,
timeBucketAggregation: Pixel.Event.CompileTimeBucketAggregation(number: timeBucketAggregation))
if let experimentName = TDSOverrideExperimentMetrics.activeTDSExperimentNameWithCohort {
finalParameters[PixelParameterName.experimentName] = experimentName
finalParameters[PixelParameterName.etag] = UserDefaultsETagStorage().loadEtag(for: .trackerDataSet)
}
}

if let error = error {
Pixel.fire(pixel: domainEvent,
error: error,
withAdditionalParameters: parameters ?? [:],
withAdditionalParameters: finalParameters,
onComplete: onComplete)
} else {
Pixel.fire(pixel: domainEvent,
withAdditionalParameters: parameters ?? [:],
withAdditionalParameters: finalParameters,
includedParameters: [],
onComplete: onComplete)
}
Expand Down
11 changes: 10 additions & 1 deletion Core/FileStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import Foundation
import Configuration
import PixelExperimentKit

public class FileStore {

Expand Down Expand Up @@ -79,7 +80,15 @@ public class FileStore {
} catch {
let nserror = error as NSError
if nserror.domain != NSCocoaErrorDomain || nserror.code != NSFileReadNoSuchFileError {
Pixel.fire(pixel: .trackerDataCouldNotBeLoaded, error: error)
if configuration == .trackerDataSet, let experimentName = TDSOverrideExperimentMetrics.activeTDSExperimentNameWithCohort {
let parameters = [
"experimentName": experimentName,
"etag": UserDefaultsETagStorage().loadEtag(for: .trackerDataSet) ?? ""
]
Pixel.fire(pixel: .trackerDataCouldNotBeLoaded, error: error, withAdditionalParameters: parameters)
} else {
Pixel.fire(pixel: .trackerDataCouldNotBeLoaded, error: error)
}
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,9 @@ extension Pixel {
case debugWebsiteDataStoresCleared

case debugBookmarksMigratedMoreThanOnce


case debugBreakageExperiment

// Return user measurement
case debugReturnUserAddATB
case debugReturnUserUpdateATB
Expand Down Expand Up @@ -1498,9 +1500,9 @@ extension Pixel.Event {

case .configurationFetchInfo: return "m_d_cfgfetch"

case .trackerDataParseFailed: return "m_d_tds_p"
case .trackerDataParseFailed: return "m_d_tracker_data_parse_failed"
case .trackerDataReloadFailed: return "m_d_tds_r"
case .trackerDataCouldNotBeLoaded: return "m_d_tds_l"
case .trackerDataCouldNotBeLoaded: return "m_d_tracker_data_could_not_be_loaded"
case .fileStoreWriteFailed: return "m_d_fswf"
case .fileStoreCoordinatorFailed: return "m_d_configuration_file_coordinator_error"
case .privacyConfigurationReloadFailed: return "m_d_pc_r"
Expand Down Expand Up @@ -1935,6 +1937,7 @@ extension Pixel.Event {
// MARK: Lifecycle
case .appDidTransitionToUnexpectedState: return "m_debug_app-did-transition-to-unexpected-state-3"

case .debugBreakageExperiment: return "m_debug_breakage_experiment_u"
}
}
}
Expand Down
18 changes: 13 additions & 5 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@
564DE4572C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4562C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift */; };
564DE45A2C450BE600D23241 /* DaxDialogsNewTabTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4592C450BE600D23241 /* DaxDialogsNewTabTests.swift */; };
564DE45E2C45218500D23241 /* OnboardingNavigationDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE45D2C45218500D23241 /* OnboardingNavigationDelegateTests.swift */; };
5650E36F2D3E8E5900D41ECF /* PageRefreshMonitorExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5650E36E2D3E8E5900D41ECF /* PageRefreshMonitorExtensionTests.swift */; };
566B73702BECD46800FF1959 /* MainViewController+SyncAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B736F2BECD46800FF1959 /* MainViewController+SyncAlerts.swift */; };
566B73732BECE4F200FF1959 /* SyncErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B73712BECE4F200FF1959 /* SyncErrorHandling.swift */; };
566B73762BECE53D00FF1959 /* SyncPausedStateManaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B73742BECE53D00FF1959 /* SyncPausedStateManaging.swift */; };
Expand Down Expand Up @@ -1028,7 +1029,6 @@
CB3C788D2D06D3A700A7E4ED /* AppStateTransitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAD0F072CFE27D5006267B8 /* AppStateTransitions.swift */; };
CB3C788E2D06D3A700A7E4ED /* Initializing.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAD0EF82CFE1D35006267B8 /* Initializing.swift */; };
CB3C788F2D06D3A700A7E4ED /* Suspending.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAD0EFE2CFE1D4E006267B8 /* Suspending.swift */; };
CB48D3332B90CE9F00631D8B /* PageRefreshStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48D3312B90CE9F00631D8B /* PageRefreshStore.swift */; };
CB4FA44E2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB4FA44D2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift */; };
CB5516D0286500290079B175 /* TrackerRadarIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85519124247468580010FDD0 /* TrackerRadarIntegrationTests.swift */; };
CB5516D1286500290079B175 /* ContentBlockingRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CA904C24FD2DB000D41DDF /* ContentBlockingRulesTests.swift */; };
Expand Down Expand Up @@ -1694,6 +1694,7 @@
564DE4562C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageControllerDaxDialogTests.swift; sourceTree = "<group>"; };
564DE4592C450BE600D23241 /* DaxDialogsNewTabTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxDialogsNewTabTests.swift; sourceTree = "<group>"; };
564DE45D2C45218500D23241 /* OnboardingNavigationDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationDelegateTests.swift; sourceTree = "<group>"; };
5650E36E2D3E8E5900D41ECF /* PageRefreshMonitorExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageRefreshMonitorExtensionTests.swift; sourceTree = "<group>"; };
566B736F2BECD46800FF1959 /* MainViewController+SyncAlerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainViewController+SyncAlerts.swift"; sourceTree = "<group>"; };
566B73712BECE4F200FF1959 /* SyncErrorHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncErrorHandling.swift; sourceTree = "<group>"; };
566B73742BECE53D00FF1959 /* SyncPausedStateManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPausedStateManaging.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2962,7 +2963,6 @@
CB2A7EF3285383B300885F67 /* AppLastCompiledRulesStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLastCompiledRulesStore.swift; sourceTree = "<group>"; };
CB2C47822AF6D55800AEDCD9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = "<group>"; };
CB4448752AF6D51D001F93F7 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
CB48D3312B90CE9F00631D8B /* PageRefreshStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageRefreshStore.swift; sourceTree = "<group>"; };
CB4FA44D2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorPageUserScript.swift; sourceTree = "<group>"; };
CB5038622AF6D563007FD69F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
CB6ABD002AF6D52B004A8224 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4097,6 +4097,14 @@
path = Configuration;
sourceTree = "<group>";
};
5650E36D2D3E8E3100D41ECF /* PageRefreshMonitor */ = {
isa = PBXGroup;
children = (
5650E36E2D3E8E5900D41ECF /* PageRefreshMonitorExtensionTests.swift */,
);
path = PageRefreshMonitor;
sourceTree = "<group>";
};
566B736E2BECD3DC00FF1959 /* Utilities */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4635,6 +4643,7 @@
84E341A91E2F7EFB00BDBA6F /* UnitTests */ = {
isa = PBXGroup;
children = (
5650E36D2D3E8E3100D41ECF /* PageRefreshMonitor */,
F1BDDC012C340DDF00459306 /* SyncUI */,
F12D98401F266B30003C2EE3 /* DuckDuckGo */,
F1E092B31E92A6B900732CCC /* Core */,
Expand Down Expand Up @@ -5746,7 +5755,6 @@
isa = PBXGroup;
children = (
CBECDB792CD981C6005B8B87 /* AppPageRefreshMonitor.swift */,
CB48D3312B90CE9F00631D8B /* PageRefreshStore.swift */,
);
name = PageRefreshMonitor;
sourceTree = "<group>";
Expand Down Expand Up @@ -8344,7 +8352,6 @@
9FE05CEE2C36424E00D9046B /* OnboardingPixelReporter.swift in Sources */,
310C4B47281B60E300BA79A9 /* AutofillLoginDetailsViewModel.swift in Sources */,
85EE7F572246685B000FE757 /* WebContainerViewController.swift in Sources */,
CB48D3332B90CE9F00631D8B /* PageRefreshStore.swift in Sources */,
1EC458462948932500CB2B13 /* UIHostingControllerExtension.swift in Sources */,
1E4DCF4E27B6A69600961E25 /* DownloadsListHostingController.swift in Sources */,
850F93DB2B594AB800823EEA /* ZippedPassKitPreviewHelper.swift in Sources */,
Expand Down Expand Up @@ -8740,6 +8747,7 @@
6AC98419288055C1005FA9CA /* BarsAnimatorTests.swift in Sources */,
85E065C12C73ADDD00D73E2A /* UsageSegmentationStorageTests.swift in Sources */,
8536A1CA209AF6490050739E /* HomeRowReminderTests.swift in Sources */,
5650E36F2D3E8E5900D41ECF /* PageRefreshMonitorExtensionTests.swift in Sources */,
C1106F312D0EFD8B0054A221 /* FreeTrialsFeatureFlagExperimentTests.swift in Sources */,
851DFD8A212C5EE800D95F20 /* TabSwitcherButtonTests.swift in Sources */,
98983096255B5019003339A2 /* BookmarksCachingSearchTests.swift in Sources */,
Expand Down Expand Up @@ -11953,7 +11961,7 @@
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 228.0.0;
version = 229.0.0;
};
};
9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/DuckDuckGo/BrowserServicesKit",
"state" : {
"revision" : "12227e2c97ad93f8924551e5ead831c79386b19b",
"version" : "228.0.0"
"revision" : "4232acb81dc42311832cdaab042cbcafd530f9bc",
"version" : "229.0.0"
}
},
{
Expand Down
3 changes: 1 addition & 2 deletions DuckDuckGo/AppDependencyProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ final class AppDependencyProvider: DependencyProvider {
let configurationManager: ConfigurationManager
let configurationStore = ConfigurationStore()

let pageRefreshMonitor = PageRefreshMonitor(onDidDetectRefreshPattern: PageRefreshMonitor.onDidDetectRefreshPattern,
store: PageRefreshStore())
let pageRefreshMonitor = PageRefreshMonitor(onDidDetectRefreshPattern: PageRefreshMonitor.onDidDetectRefreshPattern)

// Subscription
let subscriptionManager: SubscriptionManager
Expand Down
18 changes: 16 additions & 2 deletions DuckDuckGo/AppPageRefreshMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,25 @@
import Core
import Common
import PageRefreshMonitor
import PixelExperimentKit

extension PageRefreshMonitor {

static let onDidDetectRefreshPattern: () -> Void = {
Pixel.fire(pixel: .pageRefreshThreeTimesWithin20Seconds)
static let onDidDetectRefreshPattern: (NumberOfRefreshes) -> Void = { numberOfRefreshes in
let tdsEtag = AppDependencyProvider.shared.configurationStore.loadEtag(for: .trackerDataSet) ?? ""
switch numberOfRefreshes {
case 2:
TDSOverrideExperimentMetrics.fireTDSExperimentMetric(metricType: .refresh2X, etag: tdsEtag, fireDebugExperiment: { parameters in
UniquePixel.fire(pixel: .debugBreakageExperiment, withAdditionalParameters: parameters)
})
case 3:
Pixel.fire(pixel: .pageRefreshThreeTimesWithin20Seconds)
TDSOverrideExperimentMetrics.fireTDSExperimentMetric(metricType: .refresh3X, etag: tdsEtag, fireDebugExperiment: { parameters in
UniquePixel.fire(pixel: .debugBreakageExperiment, withAdditionalParameters: parameters)
})
default:
return
}
}

}
29 changes: 0 additions & 29 deletions DuckDuckGo/PageRefreshStore.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import BrowserServicesKit
import PrivacyDashboard
import Common
import os.log
import PixelExperimentKit

final class PrivacyDashboardViewController: UIViewController {

Expand Down Expand Up @@ -136,6 +137,10 @@ final class PrivacyDashboardViewController: UIViewController {
ActionMessageView.present(message: UserText.messageProtectionDisabled.format(arguments: domain))
}
Pixel.fire(pixel: .dashboardProtectionAllowlistAdd, withAdditionalParameters: pixelParam)
let tdsEtag = AppDependencyProvider.shared.configurationStore.loadEtag(for: .trackerDataSet) ?? ""
TDSOverrideExperimentMetrics.fireTDSExperimentMetric(metricType: .privacyToggleUsed, etag: tdsEtag) { parameters in
UniquePixel.fire(pixel: .debugBreakageExperiment, withAdditionalParameters: parameters)
}
}

contentBlockingManager.scheduleCompilation()
Expand Down
5 changes: 5 additions & 0 deletions DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import simd
import WidgetKit
import Common
import PrivacyDashboard
import PixelExperimentKit

extension TabViewController {

Expand Down Expand Up @@ -505,6 +506,10 @@ extension TabViewController {
togglePrivacyProtection(domain: domain)
}
Pixel.fire(pixel: isProtected ? .browsingMenuDisableProtection : .browsingMenuEnableProtection)
let tdsEtag = AppDependencyProvider.shared.configurationStore.loadEtag(for: .trackerDataSet) ?? ""
TDSOverrideExperimentMetrics.fireTDSExperimentMetric(metricType: .privacyToggleUsed, etag: tdsEtag) { parameters in
UniquePixel.fire(pixel: .debugBreakageExperiment, withAdditionalParameters: parameters)
}
}

private func togglePrivacyProtection(domain: String, didSendReport: Bool = false) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// PageRefreshMonitorExtensionTests.swift
// DuckDuckGo
//
// Copyright © 2025 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import XCTest
@testable import DuckDuckGo
@testable import PixelExperimentKit
import PageRefreshMonitor
import BrowserServicesKit

final class PageRefreshMonitorExtensionTests: XCTestCase {

var captureMetric: String?

override func setUpWithError() throws {
TDSOverrideExperimentMetrics.configureTDSOverrideExperimentMetrics { _, metric, _, _ in
self.captureMetric = metric
}
}

override func tearDownWithError() throws {
captureMetric = nil
}

func test_OnDidDetectRefreshPattern_WithValue1_FireExperimentFuncNotCalled() throws {
PageRefreshMonitor.onDidDetectRefreshPattern(1)

XCTAssertNil(captureMetric)
}

func test_OnDidDetectRefreshPattern_WithValue2_ExpectedFireExperimentFuncCalled() throws {
PageRefreshMonitor.onDidDetectRefreshPattern(2)

XCTAssertEqual(captureMetric, "2XRefresh")
}

func test_OnDidDetectRefreshPattern_WithValue3_ExpectedFireExperimentFuncCalled() throws {
PageRefreshMonitor.onDidDetectRefreshPattern(3)

XCTAssertEqual(captureMetric, "3XRefresh")
}

func test_OnDidDetectRefreshPattern_WithValue4_FireExperimentFuncNotCalled() throws {
PageRefreshMonitor.onDidDetectRefreshPattern(4)

XCTAssertNil(captureMetric)
}

}
Loading