Skip to content
Open
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
10 changes: 9 additions & 1 deletion DatadogCore/Sources/Core/Upload/DataUploadConditions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import DatadogInternal
/// Tells if data upload can be performed based on given system conditions.
internal struct DataUploadConditions {
enum Blocker {
case constrainedNetworkAccess
case battery(level: Int, state: BatteryStatus.State)
case lowPowerModeOn
case networkReachability(description: String)
Expand All @@ -23,8 +24,12 @@ internal struct DataUploadConditions {
/// Battery level above which data upload can be performed.
let minBatteryLevel: Float

init(minBatteryLevel: Float = Constants.minBatteryLevel) {
/// Blocks uploads on networks with "Low Data Mode" enabled when set to `true`. Defaults to `false`.
let isNetworkAccessConstrained: Bool

init(minBatteryLevel: Float = Constants.minBatteryLevel, isNetworkAccessConstrained: Bool = false) {
self.minBatteryLevel = minBatteryLevel
self.isNetworkAccessConstrained = isNetworkAccessConstrained
}

func blockersForUpload(with context: DatadogContext) -> [Blocker] {
Expand All @@ -38,6 +43,9 @@ internal struct DataUploadConditions {
if !networkIsReachable {
blockers = [.networkReachability(description: reachability.rawValue)]
}
if context.networkConnectionInfo?.isConstrained == true, isNetworkAccessConstrained {
return [.constrainedNetworkAccess]
}
#endif

guard let battery = context.batteryStatus, battery.state != .unknown else {
Expand Down
3 changes: 3 additions & 0 deletions DatadogCore/Sources/Core/Upload/DataUploadWorker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ internal class DataUploadWorker: DataUploadWorkerType {
failure: "blocker",
blockers: blockers.map {
switch $0 {
case .constrainedNetworkAccess: return "constrained_network_access"
case .battery: return "low_battery"
case .lowPowerModeOn: return "lpm"
case .networkReachability: return "offline"
Expand Down Expand Up @@ -291,6 +292,8 @@ internal class DataUploadWorker: DataUploadWorkerType {
extension DataUploadConditions.Blocker: CustomStringConvertible {
var description: String {
switch self {
case .constrainedNetworkAccess:
return "🛜 Network is in Low Data Mode and uploads are disabled for constrained networks"
case let .battery(level: level, state: state):
return "🔋 Battery state is: \(state) (\(level)%)"
case .lowPowerModeOn:
Expand Down
2 changes: 1 addition & 1 deletion DatadogCore/Sources/Core/Upload/FeatureUpload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ internal struct FeatureUpload {
fileReader: fileReader,
dataUploader: dataUploader,
contextProvider: contextProvider,
uploadConditions: DataUploadConditions(),
uploadConditions: DataUploadConditions(isNetworkAccessConstrained: performance.constrainedNetworkAccessEnabled),
delay: DataUploadDelay(performance: performance),
featureName: featureName,
telemetry: telemetry,
Expand Down
78 changes: 46 additions & 32 deletions DatadogCore/Sources/Datadog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,48 +136,59 @@ public enum Datadog {
/// `false` by default.
public var backgroundTasksEnabled: Bool

/// Allows uploads on networks with "Low Data Mode" enabled.
/// When set to `false`, uploads are deferred until the device is connected to an unconstrained network.
/// This can lead to data loss if "Low Data Mode" remains enabled and the batches become obsolete.
///
/// Defaults to `false`.
/// Defaults to `true`.
public var constrainedNetworkAccessEnabled: Bool

/// Creates a Datadog SDK Configuration object.
///
/// - Parameters:
/// - clientToken: Either the RUM client token (which supports RUM, Logging and APM) or regular client token,
/// only for Logging and APM.
/// - clientToken: Either the RUM client token (which supports RUM, Logging and APM) or regular client token,
/// only for Logging and APM.
///
/// - env: The environment name which will be sent to Datadog. This can be used
/// To filter events on different environments (e.g. "staging" or "production").
/// - env: The environment name which will be sent to Datadog. This can be used
/// To filter events on different environments (e.g. "staging" or "production").
///
/// - site: Datadog site endpoint, default value is `.us1`.
/// - site: Datadog site endpoint, default value is `.us1`.
///
/// - service: The service name associated with data send to Datadog.
/// Default value is set to application bundle identifier.
/// - service: The service name associated with data send to Datadog.
/// Default value is set to application bundle identifier.
///
/// - bundle: The bundle object that contains the current executable.
/// - bundle: The bundle object that contains the current executable.
///
/// - batchSize: The preferred size of batched data uploaded to Datadog servers.
/// This value impacts the size and number of requests performed by the SDK.
/// `.medium` by default.
/// - batchSize: The preferred size of batched data uploaded to Datadog servers.
/// This value impacts the size and number of requests performed by the SDK.
/// `.medium` by default.
///
/// - uploadFrequency: The preferred frequency of uploading data to Datadog servers.
/// This value impacts the frequency of performing network requests by the SDK.
/// `.average` by default.
/// - uploadFrequency: The preferred frequency of uploading data to Datadog servers.
/// This value impacts the frequency of performing network requests by the SDK.
/// `.average` by default.
///
/// - proxyConfiguration: A proxy configuration attributes.
/// This can be used to a enable a custom proxy for uploading tracked data to Datadog's intake.
/// - proxyConfiguration: A proxy configuration attributes.
/// This can be used to a enable a custom proxy for uploading tracked data to Datadog's intake.
///
/// - encryption: Data encryption to use for on-disk data persistency by providing an object
/// complying with `DataEncryption` protocol.
/// - encryption: Data encryption to use for on-disk data persistency by providing an object
/// complying with `DataEncryption` protocol.
///
/// - serverDateProvider: A custom NTP synchronization interface.
/// By default, the Datadog SDK synchronizes with dedicated NTP pools provided by the
/// https://www.ntppool.org/ . Using different pools or setting a no-op `ServerDateProvider`
/// implementation will result in desynchronization of the SDK instance and the Datadog servers.
/// This can lead to significant time shift in RUM sessions or distributed traces.
/// - backgroundTasksEnabled: A flag that determines if `UIApplication` methods
/// `beginBackgroundTask(expirationHandler:)` and `endBackgroundTask:`
/// are used to perform background uploads.
/// It may extend the amount of time the app is operating in background by 30 seconds.
/// Tasks are normally stopped when there's nothing to upload or when encountering
/// any upload blocker such us no internet connection or low battery.
/// By default it's set to `false`.
/// - serverDateProvider: A custom NTP synchronization interface.
/// By default, the Datadog SDK synchronizes with dedicated NTP pools provided by the
/// https://www.ntppool.org/ . Using different pools or setting a no-op `ServerDateProvider`
/// implementation will result in desynchronization of the SDK instance and the Datadog servers.
/// This can lead to significant time shift in RUM sessions or distributed traces.
/// - backgroundTasksEnabled: A flag that determines if `UIApplication` methods
/// `beginBackgroundTask(expirationHandler:)` and `endBackgroundTask:`
/// are used to perform background uploads.
/// It may extend the amount of time the app is operating in background by 30 seconds.
/// Tasks are normally stopped when there's nothing to upload or when encountering
/// any upload blocker such us no internet connection or low battery.
/// By default it's set to `false`.
/// - isNetworkAccessConstrained: A flag that determines if uploads should be constrained on networks with "Low Data Mode" enabled.
/// By default it's set to `false`.
/// By default it's set to `true`.
public init(
clientToken: String,
env: String,
Expand All @@ -190,7 +201,8 @@ public enum Datadog {
encryption: DataEncryption? = nil,
serverDateProvider: ServerDateProvider? = nil,
batchProcessingLevel: BatchProcessingLevel = .medium,
backgroundTasksEnabled: Bool = false
backgroundTasksEnabled: Bool = false,
constrainedNetworkAccessEnabled: Bool = true
) {
self.clientToken = clientToken
self.env = env
Expand All @@ -204,6 +216,7 @@ public enum Datadog {
self.serverDateProvider = serverDateProvider ?? DatadogNTPDateProvider()
self.batchProcessingLevel = batchProcessingLevel
self.backgroundTasksEnabled = backgroundTasksEnabled
self.constrainedNetworkAccessEnabled = constrainedNetworkAccessEnabled
}

// MARK: - Internal
Expand Down Expand Up @@ -596,7 +609,8 @@ extension DatadogCore {
batchSize: debug ? .small : configuration.batchSize,
uploadFrequency: debug ? .frequent : configuration.uploadFrequency,
bundleType: bundleType,
batchProcessingLevel: configuration.batchProcessingLevel
batchProcessingLevel: configuration.batchProcessingLevel,
constrainedNetworkAccessEnabled: configuration.constrainedNetworkAccessEnabled
)
let isRunFromExtension = bundleType == .iOSAppExtension

Expand Down
14 changes: 10 additions & 4 deletions DatadogCore/Sources/PerformancePreset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,16 @@ internal struct PerformancePreset: Equatable, StoragePerformancePreset, UploadPe
let maxUploadDelay: TimeInterval
let uploadDelayChangeRate: Double
let maxBatchesPerUpload: Int
let constrainedNetworkAccessEnabled: Bool
}

internal extension PerformancePreset {
init(
batchSize: Datadog.Configuration.BatchSize,
uploadFrequency: Datadog.Configuration.UploadFrequency,
bundleType: BundleType,
batchProcessingLevel: Datadog.Configuration.BatchProcessingLevel
batchProcessingLevel: Datadog.Configuration.BatchProcessingLevel,
constrainedNetworkAccessEnabled: Bool = true
) {
let meanFileAgeInSeconds: TimeInterval = {
switch (bundleType, batchSize) {
Expand Down Expand Up @@ -116,15 +118,17 @@ internal extension PerformancePreset {
meanFileAge: meanFileAgeInSeconds,
minUploadDelay: minUploadDelayInSeconds,
uploadDelayFactors: uploadDelayFactors,
maxBatchesPerUpload: batchProcessingLevel.maxBatchesPerUpload
maxBatchesPerUpload: batchProcessingLevel.maxBatchesPerUpload,
constrainedNetworkAccessEnabled: constrainedNetworkAccessEnabled
)
}

init(
meanFileAge: TimeInterval,
minUploadDelay: TimeInterval,
uploadDelayFactors: (initial: Double, min: Double, max: Double, changeRate: Double),
maxBatchesPerUpload: Int
maxBatchesPerUpload: Int,
constrainedNetworkAccessEnabled: Bool
) {
self.maxFileSize = 4.MB.asUInt32()
self.maxDirectorySize = 512.MB.asUInt32()
Expand All @@ -138,6 +142,7 @@ internal extension PerformancePreset {
self.maxUploadDelay = minUploadDelay * uploadDelayFactors.max
self.uploadDelayChangeRate = uploadDelayFactors.changeRate
self.maxBatchesPerUpload = maxBatchesPerUpload
self.constrainedNetworkAccessEnabled = constrainedNetworkAccessEnabled
}

func updated(with override: PerformancePresetOverride) -> PerformancePreset {
Expand All @@ -153,7 +158,8 @@ internal extension PerformancePreset {
minUploadDelay: override.minUploadDelay ?? minUploadDelay,
maxUploadDelay: override.maxUploadDelay ?? maxUploadDelay,
uploadDelayChangeRate: override.uploadDelayChangeRate ?? uploadDelayChangeRate,
maxBatchesPerUpload: maxBatchesPerUpload
maxBatchesPerUpload: maxBatchesPerUpload,
constrainedNetworkAccessEnabled: override.constrainedNetworkAccessEnabled ?? constrainedNetworkAccessEnabled
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class DataUploadConditionsTests: XCTestCase {
state: .mockRandom(), level: Constants.minBatteryLevel + 0.01
),
isLowPowerModeEnabled: false,
allowsConstrainedNetworkAccess: true,
forNetwork: .mockWith(reachability: .mockRandom(within: [.yes, .maybe]))
)
assert(
Expand All @@ -27,12 +28,14 @@ class DataUploadConditionsTests: XCTestCase {
state: .mockRandom(within: [.charging, .full]), level: .random(in: 0...100)
),
isLowPowerModeEnabled: false,
allowsConstrainedNetworkAccess: true,
forNetwork: .mockWith(reachability: .mockRandom(within: [.yes, .maybe]))
)
assert(
canPerformUploadReturns: true,
forBattery: nil,
isLowPowerModeEnabled: false,
allowsConstrainedNetworkAccess: true,
forNetwork: .mockWith(reachability: .mockRandom(within: [.yes, .maybe]))
)
}
Expand All @@ -46,6 +49,7 @@ class DataUploadConditionsTests: XCTestCase {
state: .mockRandom(within: [.unplugged, .charging, .full]), level: .random(in: 0...100)
),
isLowPowerModeEnabled: .random(),
allowsConstrainedNetworkAccess: true,
forNetwork: .mockWith(reachability: .no)
)
assert(
Expand All @@ -54,6 +58,7 @@ class DataUploadConditionsTests: XCTestCase {
state: .mockRandom(within: [.unplugged, .charging, .full]), level: .random(in: 0...100)
),
isLowPowerModeEnabled: true,
allowsConstrainedNetworkAccess: true,
forNetwork: .mockWith(reachability: .mockRandom())
)
assert(
Expand All @@ -62,12 +67,14 @@ class DataUploadConditionsTests: XCTestCase {
state: .unplugged, level: Constants.minBatteryLevel - 0.01
),
isLowPowerModeEnabled: .random(),
allowsConstrainedNetworkAccess: true,
forNetwork: .mockWith(reachability: .mockRandom())
)
assert(
canPerformUploadReturns: false,
forBattery: nil,
isLowPowerModeEnabled: .random(),
allowsConstrainedNetworkAccess: true,
forNetwork: .mockWith(reachability: .no)
)
}
Expand All @@ -79,21 +86,61 @@ class DataUploadConditionsTests: XCTestCase {
canPerformUploadReturns: true,
forBattery: BatteryStatus(state: .unknown, level: .random(in: -100...100)),
isLowPowerModeEnabled: .random(),
allowsConstrainedNetworkAccess: true,
forNetwork: .mockWith(reachability: .mockRandom(within: [.yes, .maybe]))
)
}
}

func testItSaysToUploadIfNetworkIsNotConstrained() {
assert(
canPerformUploadReturns: true,
forBattery: nil,
isLowPowerModeEnabled: false,
allowsConstrainedNetworkAccess: true,
forNetwork: .mockWith(reachability: .yes, isConstrained: false)
)

assert(
canPerformUploadReturns: true,
forBattery: nil,
isLowPowerModeEnabled: false,
allowsConstrainedNetworkAccess: false,
forNetwork: .mockWith(reachability: .yes, isConstrained: false)
)
}

func testItSaysToUploadIfNetworkIsConstrained() {
assert(
canPerformUploadReturns: true,
forBattery: nil,
isLowPowerModeEnabled: false,
allowsConstrainedNetworkAccess: true,
forNetwork: .mockWith(reachability: .yes, isConstrained: true)
)
}

func testItSaysToNotUploadIfNetworkIsConstrained() {
assert(
canPerformUploadReturns: false,
forBattery: nil,
isLowPowerModeEnabled: false,
allowsConstrainedNetworkAccess: false,
forNetwork: .mockWith(reachability: .yes, isConstrained: true)
)
}

private func assert(
canPerformUploadReturns value: Bool,
forBattery battery: BatteryStatus?,
isLowPowerModeEnabled: Bool,
allowsConstrainedNetworkAccess: Bool,
forNetwork network: NetworkConnectionInfo,
file: StaticString = #file,
line: UInt = #line
) {
let context: DatadogContext = .mockWith(networkConnectionInfo: network, batteryStatus: battery, isLowPowerModeEnabled: isLowPowerModeEnabled)
let conditions = DataUploadConditions()
let conditions = DataUploadConditions(allowsConstrainedNetworkAccess: allowsConstrainedNetworkAccess)
let canPerformUpload = conditions.blockersForUpload(with: context).isEmpty
XCTAssertEqual(
value,
Expand Down
Loading