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

DBP: Add sleep duration on pixels #2771

Merged
merged 2 commits into from
May 15, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,13 @@ final class DataBrokerRunCustomJSONViewModel: ObservableObject {
}
}

final class FakeSleepObserver: SleepObserver {

func totalSleepTime() -> TimeInterval {
return 0
}
}

final class FakeStageDurationCalculator: StageDurationCalculator {
var attemptId: UUID = UUID()
var isManualScan: Bool = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ final class DebugScanOperation: DataBrokerOperation {
let cookieHandler: CookieHandler
let pixelHandler: EventMapping<DataBrokerProtectionPixels>
var postLoadingSiteStartTime: Date?
let sleepObserver: SleepObserver

private let fileManager = FileManager.default
private let debugScanContentPath: String?
Expand Down Expand Up @@ -101,6 +102,7 @@ final class DebugScanOperation: DataBrokerOperation {
pixelHandler = EventMapping(mapping: { _, _, _, _ in
// We do not need the pixel handler for the debug
})
self.sleepObserver = FakeSleepObserver()
}

func run(inputValue: Void,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ protocol DataBrokerOperation: CCFCommunicationDelegate {
var cookieHandler: CookieHandler { get }
var stageCalculator: StageDurationCalculator { get }
var pixelHandler: EventMapping<DataBrokerProtectionPixels> { get }
var sleepObserver: SleepObserver { get }

var webViewHandler: WebViewHandler? { get set }
var actionsHandler: ActionsHandler? { get }
Expand Down Expand Up @@ -202,15 +203,15 @@ extension DataBrokerOperation {
if stageCalculator.isManualScan {
let dataBrokerURL = self.query.dataBroker.url
let durationInMs = (Date().timeIntervalSince(startTime) * 1000).rounded(.towardZero)
pixelHandler.fire(.initialScanSiteLoadDuration(duration: durationInMs, hasError: hasError, brokerURL: dataBrokerURL))
pixelHandler.fire(.initialScanSiteLoadDuration(duration: durationInMs, hasError: hasError, brokerURL: dataBrokerURL, sleepDuration: sleepObserver.totalSleepTime()))
}
}

func firePostLoadingDurationPixel(hasError: Bool) {
if stageCalculator.isManualScan, let postLoadingSiteStartTime = self.postLoadingSiteStartTime {
let dataBrokerURL = self.query.dataBroker.url
let durationInMs = (Date().timeIntervalSince(postLoadingSiteStartTime) * 1000).rounded(.towardZero)
pixelHandler.fire(.initialScanPostLoadingDuration(duration: durationInMs, hasError: hasError, brokerURL: dataBrokerURL))
pixelHandler.fire(.initialScanPostLoadingDuration(duration: durationInMs, hasError: hasError, brokerURL: dataBrokerURL, sleepDuration: sleepObserver.totalSleepTime()))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ final class DataBrokerOperationRunner: WebOperationRunner {
pixelHandler: EventMapping<DataBrokerProtectionPixels>,
showWebView: Bool,
shouldRunNextStep: @escaping () -> Bool) async throws -> [ExtractedProfile] {
let sleepObserver = DataBrokerProtectionSleepObserver(brokerProfileQueryData: profileQuery)
let scan = ScanOperation(
privacyConfig: privacyConfigManager,
prefs: contentScopeProperties,
Expand All @@ -95,6 +96,7 @@ final class DataBrokerOperationRunner: WebOperationRunner {
captchaService: captchaService,
stageDurationCalculator: stageCalculator,
pixelHandler: pixelHandler,
sleepObserver: sleepObserver,
shouldRunNextStep: shouldRunNextStep
)
return try await scan.run(inputValue: (), showWebView: showWebView)
Expand All @@ -106,6 +108,7 @@ final class DataBrokerOperationRunner: WebOperationRunner {
pixelHandler: EventMapping<DataBrokerProtectionPixels>,
showWebView: Bool,
shouldRunNextStep: @escaping () -> Bool) async throws {
let sleepObserver = DataBrokerProtectionSleepObserver(brokerProfileQueryData: profileQuery)
let optOut = OptOutOperation(
privacyConfig: privacyConfigManager,
prefs: contentScopeProperties,
Expand All @@ -114,6 +117,7 @@ final class DataBrokerOperationRunner: WebOperationRunner {
captchaService: captchaService,
stageCalculator: stageCalculator,
pixelHandler: pixelHandler,
sleepObserver: sleepObserver,
shouldRunNextStep: shouldRunNextStep
)
try await optOut.run(inputValue: extractedProfile, showWebView: showWebView)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ final class OptOutOperation: DataBrokerOperation {
let clickAwaitTime: TimeInterval
let pixelHandler: EventMapping<DataBrokerProtectionPixels>
var postLoadingSiteStartTime: Date?
let sleepObserver: SleepObserver

// Captcha is a third-party resource that sometimes takes more time to load
// if we are not able to get the captcha information. We will try to run the action again
Expand All @@ -60,6 +61,7 @@ final class OptOutOperation: DataBrokerOperation {
clickAwaitTime: TimeInterval = 40,
stageCalculator: StageDurationCalculator,
pixelHandler: EventMapping<DataBrokerProtectionPixels>,
sleepObserver: SleepObserver,
shouldRunNextStep: @escaping () -> Bool
) {
self.privacyConfig = privacyConfig
Expand All @@ -73,6 +75,7 @@ final class OptOutOperation: DataBrokerOperation {
self.clickAwaitTime = clickAwaitTime
self.cookieHandler = cookieHandler
self.pixelHandler = pixelHandler
self.sleepObserver = sleepObserver
}

func run(inputValue: ExtractedProfile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ final class ScanOperation: DataBrokerOperation {
let clickAwaitTime: TimeInterval
let pixelHandler: EventMapping<DataBrokerProtectionPixels>
var postLoadingSiteStartTime: Date?
let sleepObserver: SleepObserver

init(privacyConfig: PrivacyConfigurationManaging,
prefs: ContentScopeProperties,
Expand All @@ -54,6 +55,7 @@ final class ScanOperation: DataBrokerOperation {
clickAwaitTime: TimeInterval = 0,
stageDurationCalculator: StageDurationCalculator,
pixelHandler: EventMapping<DataBrokerProtectionPixels>,
sleepObserver: SleepObserver,
shouldRunNextStep: @escaping () -> Bool
) {
self.privacyConfig = privacyConfig
Expand All @@ -67,6 +69,7 @@ final class ScanOperation: DataBrokerOperation {
self.clickAwaitTime = clickAwaitTime
self.cookieHandler = cookieHandler
self.pixelHandler = pixelHandler
self.sleepObserver = sleepObserver
}

func run(inputValue: InputValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public enum DataBrokerProtectionPixels {
static let profileQueries = "profile_queries"
static let hasError = "has_error"
static let brokerURL = "broker_url"
static let sleepDuration = "sleep_duration"
}

case error(error: DataBrokerProtectionError, dataBroker: String)
Expand Down Expand Up @@ -177,8 +178,8 @@ public enum DataBrokerProtectionPixels {
// Initial scans pixels
// https://app.asana.com/0/1204006570077678/1206981742767458/f
case initialScanTotalDuration(duration: Double, profileQueries: Int)
case initialScanSiteLoadDuration(duration: Double, hasError: Bool, brokerURL: String)
case initialScanPostLoadingDuration(duration: Double, hasError: Bool, brokerURL: String)
case initialScanSiteLoadDuration(duration: Double, hasError: Bool, brokerURL: String, sleepDuration: Double)
case initialScanPostLoadingDuration(duration: Double, hasError: Bool, brokerURL: String, sleepDuration: Double)
case initialScanPreStartDuration(duration: Double)
}

Expand Down Expand Up @@ -437,10 +438,10 @@ extension DataBrokerProtectionPixels: PixelKitEvent {
Consts.backendServiceCallSite: backendServiceCallSite.rawValue]
case .initialScanTotalDuration(let duration, let profileQueries):
return [Consts.durationInMs: String(duration), Consts.profileQueries: String(profileQueries)]
case .initialScanSiteLoadDuration(let duration, let hasError, let brokerURL):
return [Consts.durationInMs: String(duration), Consts.hasError: hasError.description, Consts.brokerURL: brokerURL]
case .initialScanPostLoadingDuration(let duration, let hasError, let brokerURL):
return [Consts.durationInMs: String(duration), Consts.hasError: hasError.description, Consts.brokerURL: brokerURL]
case .initialScanSiteLoadDuration(let duration, let hasError, let brokerURL, let sleepDuration):
return [Consts.durationInMs: String(duration), Consts.hasError: hasError.description, Consts.brokerURL: brokerURL, Consts.sleepDuration: String(sleepDuration)]
case .initialScanPostLoadingDuration(let duration, let hasError, let brokerURL, let sleepDuration):
return [Consts.durationInMs: String(duration), Consts.hasError: hasError.description, Consts.brokerURL: brokerURL, Consts.sleepDuration: String(sleepDuration)]
case .initialScanPreStartDuration(let duration):
return [Consts.durationInMs: String(duration)]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// DataBrokerProtectionSleepObserver.swift
//
// Copyright © 2023 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 Foundation
import Cocoa
import Common

protocol SleepObserver {
func totalSleepTime() -> TimeInterval
}
/// This class purpose is to measure from the background agent how much time the operations
/// are working while the computer is asleep.
/// This will help us gather metrics around what happen to WebViews when the computer is sleeping.
///
/// https://app.asana.com/0/1204006570077678/1207278682082256/f
final class DataBrokerProtectionSleepObserver: SleepObserver {
private var startSleepTime: Date?
private var endTime: TimeInterval?
private let brokerProfileQueryData: BrokerProfileQueryData

init(brokerProfileQueryData: BrokerProfileQueryData) {
self.brokerProfileQueryData = brokerProfileQueryData
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(willSleepNotification(_:)), name: NSWorkspace.willSleepNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(didWakeNotification(_:)), name: NSWorkspace.didWakeNotification, object: nil)
}

deinit {
os_log(.debug, log: .dataBrokerProtection, "SleepObserver: Deinit %{public}s %{public}s %{public}s", brokerProfileQueryData.dataBroker.name, brokerProfileQueryData.profileQuery.firstName, brokerProfileQueryData.profileQuery.city)
NotificationCenter.default.removeObserver(self)
}

func totalSleepTime() -> TimeInterval {
guard let totalSleepTime = self.endTime else {
return 0
}

os_log(.debug, log: .dataBrokerProtection, "SleepObserver: Total Sleep time more than zero: %{public}s", String(totalSleepTime))

return totalSleepTime
}

@objc func willSleepNotification(_ notification: Notification) {
os_log(.debug, log: .dataBrokerProtection, "SleepObserver: Computer will sleep on %{public}s %{public}s %{public}s %{public}s", brokerProfileQueryData.dataBroker.name, brokerProfileQueryData.profileQuery.firstName, brokerProfileQueryData.profileQuery.city)
startSleepTime = Date()
}

@objc func didWakeNotification(_ notification: Notification) {
os_log(.debug, log: .dataBrokerProtection, "SleepObserver: Computer waking up %{public}s %{public}s %{public}s", brokerProfileQueryData.dataBroker.name, brokerProfileQueryData.profileQuery.firstName, brokerProfileQueryData.profileQuery.city)
guard let startSleepTime = self.startSleepTime else {
return
}

if let endTime = self.endTime {
// This scenario can happen if during the scan the computer goes to sleep more than once.
let currentSleepIterationTime = Date().timeIntervalSince(startSleepTime).toMs
self.endTime = endTime + currentSleepIterationTime
} else {
endTime = Date().timeIntervalSince(startSleepTime).toMs
}
}
}

extension TimeInterval {
var toMs: TimeInterval {
(self * 1000).rounded(.towardZero)
}
}
Loading
Loading