Skip to content

Commit 09fc6ec

Browse files
Added ability to block uploads on networks with "Low Data Mode" enabled
1 parent 968bf95 commit 09fc6ec

File tree

10 files changed

+137
-45
lines changed

10 files changed

+137
-45
lines changed

DatadogCore/Sources/Core/Upload/DataUploadConditions.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import DatadogInternal
1010
/// Tells if data upload can be performed based on given system conditions.
1111
internal struct DataUploadConditions {
1212
enum Blocker {
13+
case allowsConstrainedNetworkAccessFalse
1314
case battery(level: Int, state: BatteryStatus.State)
1415
case lowPowerModeOn
1516
case networkReachability(description: String)
@@ -23,8 +24,12 @@ internal struct DataUploadConditions {
2324
/// Battery level above which data upload can be performed.
2425
let minBatteryLevel: Float
2526

26-
init(minBatteryLevel: Float = Constants.minBatteryLevel) {
27+
/// Blocks uploads on networks with "Low Data Mode" enabled when set to `false`. Defaults to `true`.
28+
let allowsConstrainedNetworkAccess: Bool
29+
30+
init(minBatteryLevel: Float = Constants.minBatteryLevel, allowsConstrainedNetworkAccess: Bool = true) {
2731
self.minBatteryLevel = minBatteryLevel
32+
self.allowsConstrainedNetworkAccess = allowsConstrainedNetworkAccess
2833
}
2934

3035
func blockersForUpload(with context: DatadogContext) -> [Blocker] {
@@ -38,6 +43,9 @@ internal struct DataUploadConditions {
3843
if !networkIsReachable {
3944
blockers = [.networkReachability(description: reachability.rawValue)]
4045
}
46+
if let isConstrained = context.networkConnectionInfo?.isConstrained, isConstrained, !allowsConstrainedNetworkAccess {
47+
return [.allowsConstrainedNetworkAccessFalse]
48+
}
4149
#endif
4250

4351
guard let battery = context.batteryStatus, battery.state != .unknown else {

DatadogCore/Sources/Core/Upload/DataUploadWorker.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ internal class DataUploadWorker: DataUploadWorkerType {
244244
failure: "blocker",
245245
blockers: blockers.map {
246246
switch $0 {
247+
case .allowsConstrainedNetworkAccessFalse: return "allows_constrained_network_access_false"
247248
case .battery: return "low_battery"
248249
case .lowPowerModeOn: return "lpm"
249250
case .networkReachability: return "offline"
@@ -291,6 +292,8 @@ internal class DataUploadWorker: DataUploadWorkerType {
291292
extension DataUploadConditions.Blocker: CustomStringConvertible {
292293
var description: String {
293294
switch self {
295+
case .allowsConstrainedNetworkAccessFalse:
296+
return "🛜 Network is in Low Data Mode and uploads are disabled for constrained networks"
294297
case let .battery(level: level, state: state):
295298
return "🔋 Battery state is: \(state) (\(level)%)"
296299
case .lowPowerModeOn:

DatadogCore/Sources/Core/Upload/FeatureUpload.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ internal struct FeatureUpload {
5858
fileReader: fileReader,
5959
dataUploader: dataUploader,
6060
contextProvider: contextProvider,
61-
uploadConditions: DataUploadConditions(),
61+
uploadConditions: DataUploadConditions(allowsConstrainedNetworkAccess: performance.constrainedNetworkAccessEnabled),
6262
delay: DataUploadDelay(performance: performance),
6363
featureName: featureName,
6464
telemetry: telemetry,

DatadogCore/Sources/Datadog.swift

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -136,48 +136,56 @@ public enum Datadog {
136136
/// `false` by default.
137137
public var backgroundTasksEnabled: Bool
138138

139+
/// Allows uploads on networks with "Low Data Mode" enabled.
140+
/// When set to `false`, uploads are deferred until the device is connected to an unconstrained network.
141+
///
142+
/// Defaults to `true`.
143+
public var constrainedNetworkAccessEnabled: Bool
144+
139145
/// Creates a Datadog SDK Configuration object.
140146
///
141147
/// - Parameters:
142-
/// - clientToken: Either the RUM client token (which supports RUM, Logging and APM) or regular client token,
143-
/// only for Logging and APM.
148+
/// - clientToken: Either the RUM client token (which supports RUM, Logging and APM) or regular client token,
149+
/// only for Logging and APM.
144150
///
145-
/// - env: The environment name which will be sent to Datadog. This can be used
146-
/// To filter events on different environments (e.g. "staging" or "production").
151+
/// - env: The environment name which will be sent to Datadog. This can be used
152+
/// To filter events on different environments (e.g. "staging" or "production").
147153
///
148-
/// - site: Datadog site endpoint, default value is `.us1`.
154+
/// - site: Datadog site endpoint, default value is `.us1`.
149155
///
150-
/// - service: The service name associated with data send to Datadog.
151-
/// Default value is set to application bundle identifier.
156+
/// - service: The service name associated with data send to Datadog.
157+
/// Default value is set to application bundle identifier.
152158
///
153-
/// - bundle: The bundle object that contains the current executable.
159+
/// - bundle: The bundle object that contains the current executable.
154160
///
155-
/// - batchSize: The preferred size of batched data uploaded to Datadog servers.
156-
/// This value impacts the size and number of requests performed by the SDK.
157-
/// `.medium` by default.
161+
/// - batchSize: The preferred size of batched data uploaded to Datadog servers.
162+
/// This value impacts the size and number of requests performed by the SDK.
163+
/// `.medium` by default.
158164
///
159-
/// - uploadFrequency: The preferred frequency of uploading data to Datadog servers.
160-
/// This value impacts the frequency of performing network requests by the SDK.
161-
/// `.average` by default.
165+
/// - uploadFrequency: The preferred frequency of uploading data to Datadog servers.
166+
/// This value impacts the frequency of performing network requests by the SDK.
167+
/// `.average` by default.
162168
///
163-
/// - proxyConfiguration: A proxy configuration attributes.
164-
/// This can be used to a enable a custom proxy for uploading tracked data to Datadog's intake.
169+
/// - proxyConfiguration: A proxy configuration attributes.
170+
/// This can be used to a enable a custom proxy for uploading tracked data to Datadog's intake.
165171
///
166-
/// - encryption: Data encryption to use for on-disk data persistency by providing an object
167-
/// complying with `DataEncryption` protocol.
172+
/// - encryption: Data encryption to use for on-disk data persistency by providing an object
173+
/// complying with `DataEncryption` protocol.
168174
///
169-
/// - serverDateProvider: A custom NTP synchronization interface.
170-
/// By default, the Datadog SDK synchronizes with dedicated NTP pools provided by the
171-
/// https://www.ntppool.org/ . Using different pools or setting a no-op `ServerDateProvider`
172-
/// implementation will result in desynchronization of the SDK instance and the Datadog servers.
173-
/// This can lead to significant time shift in RUM sessions or distributed traces.
174-
/// - backgroundTasksEnabled: A flag that determines if `UIApplication` methods
175-
/// `beginBackgroundTask(expirationHandler:)` and `endBackgroundTask:`
176-
/// are used to perform background uploads.
177-
/// It may extend the amount of time the app is operating in background by 30 seconds.
178-
/// Tasks are normally stopped when there's nothing to upload or when encountering
179-
/// any upload blocker such us no internet connection or low battery.
180-
/// By default it's set to `false`.
175+
/// - serverDateProvider: A custom NTP synchronization interface.
176+
/// By default, the Datadog SDK synchronizes with dedicated NTP pools provided by the
177+
/// https://www.ntppool.org/ . Using different pools or setting a no-op `ServerDateProvider`
178+
/// implementation will result in desynchronization of the SDK instance and the Datadog servers.
179+
/// This can lead to significant time shift in RUM sessions or distributed traces.
180+
/// - backgroundTasksEnabled: A flag that determines if `UIApplication` methods
181+
/// `beginBackgroundTask(expirationHandler:)` and `endBackgroundTask:`
182+
/// are used to perform background uploads.
183+
/// It may extend the amount of time the app is operating in background by 30 seconds.
184+
/// Tasks are normally stopped when there's nothing to upload or when encountering
185+
/// any upload blocker such us no internet connection or low battery.
186+
/// By default it's set to `false`.
187+
/// - constrainedNetworkAccessEnabled: A flag that determines if uploads should be enabled on networks with "Low Data Mode" enabled.
188+
/// By default it's set to `true`.
181189
public init(
182190
clientToken: String,
183191
env: String,
@@ -190,7 +198,8 @@ public enum Datadog {
190198
encryption: DataEncryption? = nil,
191199
serverDateProvider: ServerDateProvider? = nil,
192200
batchProcessingLevel: BatchProcessingLevel = .medium,
193-
backgroundTasksEnabled: Bool = false
201+
backgroundTasksEnabled: Bool = false,
202+
constrainedNetworkAccessEnabled: Bool = true
194203
) {
195204
self.clientToken = clientToken
196205
self.env = env
@@ -204,6 +213,7 @@ public enum Datadog {
204213
self.serverDateProvider = serverDateProvider ?? DatadogNTPDateProvider()
205214
self.batchProcessingLevel = batchProcessingLevel
206215
self.backgroundTasksEnabled = backgroundTasksEnabled
216+
self.constrainedNetworkAccessEnabled = constrainedNetworkAccessEnabled
207217
}
208218

209219
// MARK: - Internal
@@ -596,7 +606,8 @@ extension DatadogCore {
596606
batchSize: debug ? .small : configuration.batchSize,
597607
uploadFrequency: debug ? .frequent : configuration.uploadFrequency,
598608
bundleType: bundleType,
599-
batchProcessingLevel: configuration.batchProcessingLevel
609+
batchProcessingLevel: configuration.batchProcessingLevel,
610+
constrainedNetworkAccessEnabled: configuration.constrainedNetworkAccessEnabled
600611
)
601612
let isRunFromExtension = bundleType == .iOSAppExtension
602613

DatadogCore/Sources/PerformancePreset.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,16 @@ internal struct PerformancePreset: Equatable, StoragePerformancePreset, UploadPe
6666
let maxUploadDelay: TimeInterval
6767
let uploadDelayChangeRate: Double
6868
let maxBatchesPerUpload: Int
69+
let constrainedNetworkAccessEnabled: Bool
6970
}
7071

7172
internal extension PerformancePreset {
7273
init(
7374
batchSize: Datadog.Configuration.BatchSize,
7475
uploadFrequency: Datadog.Configuration.UploadFrequency,
7576
bundleType: BundleType,
76-
batchProcessingLevel: Datadog.Configuration.BatchProcessingLevel
77+
batchProcessingLevel: Datadog.Configuration.BatchProcessingLevel,
78+
constrainedNetworkAccessEnabled: Bool = true
7779
) {
7880
let meanFileAgeInSeconds: TimeInterval = {
7981
switch (bundleType, batchSize) {
@@ -116,15 +118,17 @@ internal extension PerformancePreset {
116118
meanFileAge: meanFileAgeInSeconds,
117119
minUploadDelay: minUploadDelayInSeconds,
118120
uploadDelayFactors: uploadDelayFactors,
119-
maxBatchesPerUpload: batchProcessingLevel.maxBatchesPerUpload
121+
maxBatchesPerUpload: batchProcessingLevel.maxBatchesPerUpload,
122+
constrainedNetworkAccessEnabled: constrainedNetworkAccessEnabled
120123
)
121124
}
122125

123126
init(
124127
meanFileAge: TimeInterval,
125128
minUploadDelay: TimeInterval,
126129
uploadDelayFactors: (initial: Double, min: Double, max: Double, changeRate: Double),
127-
maxBatchesPerUpload: Int
130+
maxBatchesPerUpload: Int,
131+
constrainedNetworkAccessEnabled: Bool
128132
) {
129133
self.maxFileSize = 4.MB.asUInt32()
130134
self.maxDirectorySize = 512.MB.asUInt32()
@@ -138,6 +142,7 @@ internal extension PerformancePreset {
138142
self.maxUploadDelay = minUploadDelay * uploadDelayFactors.max
139143
self.uploadDelayChangeRate = uploadDelayFactors.changeRate
140144
self.maxBatchesPerUpload = maxBatchesPerUpload
145+
self.constrainedNetworkAccessEnabled = constrainedNetworkAccessEnabled
141146
}
142147

143148
func updated(with override: PerformancePresetOverride) -> PerformancePreset {
@@ -153,7 +158,8 @@ internal extension PerformancePreset {
153158
minUploadDelay: override.minUploadDelay ?? minUploadDelay,
154159
maxUploadDelay: override.maxUploadDelay ?? maxUploadDelay,
155160
uploadDelayChangeRate: override.uploadDelayChangeRate ?? uploadDelayChangeRate,
156-
maxBatchesPerUpload: maxBatchesPerUpload
161+
maxBatchesPerUpload: maxBatchesPerUpload,
162+
constrainedNetworkAccessEnabled: override.constrainedNetworkAccessEnabled ?? constrainedNetworkAccessEnabled
157163
)
158164
}
159165
}

DatadogCore/Tests/Datadog/Core/Upload/DataUploadConditionsTests.swift

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class DataUploadConditionsTests: XCTestCase {
1919
state: .mockRandom(), level: Constants.minBatteryLevel + 0.01
2020
),
2121
isLowPowerModeEnabled: false,
22+
allowsConstrainedNetworkAccess: true,
2223
forNetwork: .mockWith(reachability: .mockRandom(within: [.yes, .maybe]))
2324
)
2425
assert(
@@ -27,12 +28,14 @@ class DataUploadConditionsTests: XCTestCase {
2728
state: .mockRandom(within: [.charging, .full]), level: .random(in: 0...100)
2829
),
2930
isLowPowerModeEnabled: false,
31+
allowsConstrainedNetworkAccess: true,
3032
forNetwork: .mockWith(reachability: .mockRandom(within: [.yes, .maybe]))
3133
)
3234
assert(
3335
canPerformUploadReturns: true,
3436
forBattery: nil,
3537
isLowPowerModeEnabled: false,
38+
allowsConstrainedNetworkAccess: true,
3639
forNetwork: .mockWith(reachability: .mockRandom(within: [.yes, .maybe]))
3740
)
3841
}
@@ -46,6 +49,7 @@ class DataUploadConditionsTests: XCTestCase {
4649
state: .mockRandom(within: [.unplugged, .charging, .full]), level: .random(in: 0...100)
4750
),
4851
isLowPowerModeEnabled: .random(),
52+
allowsConstrainedNetworkAccess: true,
4953
forNetwork: .mockWith(reachability: .no)
5054
)
5155
assert(
@@ -54,6 +58,7 @@ class DataUploadConditionsTests: XCTestCase {
5458
state: .mockRandom(within: [.unplugged, .charging, .full]), level: .random(in: 0...100)
5559
),
5660
isLowPowerModeEnabled: true,
61+
allowsConstrainedNetworkAccess: true,
5762
forNetwork: .mockWith(reachability: .mockRandom())
5863
)
5964
assert(
@@ -62,12 +67,14 @@ class DataUploadConditionsTests: XCTestCase {
6267
state: .unplugged, level: Constants.minBatteryLevel - 0.01
6368
),
6469
isLowPowerModeEnabled: .random(),
70+
allowsConstrainedNetworkAccess: true,
6571
forNetwork: .mockWith(reachability: .mockRandom())
6672
)
6773
assert(
6874
canPerformUploadReturns: false,
6975
forBattery: nil,
7076
isLowPowerModeEnabled: .random(),
77+
allowsConstrainedNetworkAccess: true,
7178
forNetwork: .mockWith(reachability: .no)
7279
)
7380
}
@@ -79,21 +86,61 @@ class DataUploadConditionsTests: XCTestCase {
7986
canPerformUploadReturns: true,
8087
forBattery: BatteryStatus(state: .unknown, level: .random(in: -100...100)),
8188
isLowPowerModeEnabled: .random(),
89+
allowsConstrainedNetworkAccess: true,
8290
forNetwork: .mockWith(reachability: .mockRandom(within: [.yes, .maybe]))
8391
)
8492
}
8593
}
8694

95+
func testItSaysToUploadIfNetworkIsNotConstrained() {
96+
assert(
97+
canPerformUploadReturns: true,
98+
forBattery: nil,
99+
isLowPowerModeEnabled: false,
100+
allowsConstrainedNetworkAccess: true,
101+
forNetwork: .mockWith(reachability: .yes, isConstrained: false)
102+
)
103+
104+
assert(
105+
canPerformUploadReturns: true,
106+
forBattery: nil,
107+
isLowPowerModeEnabled: false,
108+
allowsConstrainedNetworkAccess: false,
109+
forNetwork: .mockWith(reachability: .yes, isConstrained: false)
110+
)
111+
}
112+
113+
func testItSaysToUploadIfNetworkIsConstrained() {
114+
assert(
115+
canPerformUploadReturns: true,
116+
forBattery: nil,
117+
isLowPowerModeEnabled: false,
118+
allowsConstrainedNetworkAccess: true,
119+
forNetwork: .mockWith(reachability: .yes, isConstrained: true)
120+
)
121+
}
122+
123+
func testItSaysToNotUploadIfNetworkIsConstrained() {
124+
assert(
125+
canPerformUploadReturns: false,
126+
forBattery: nil,
127+
isLowPowerModeEnabled: false,
128+
allowsConstrainedNetworkAccess: false,
129+
forNetwork: .mockWith(reachability: .yes, isConstrained: true)
130+
)
131+
}
132+
87133
private func assert(
88134
canPerformUploadReturns value: Bool,
89135
forBattery battery: BatteryStatus?,
90136
isLowPowerModeEnabled: Bool,
137+
allowsConstrainedNetworkAccess: Bool,
91138
forNetwork network: NetworkConnectionInfo,
92139
file: StaticString = #file,
93140
line: UInt = #line
94141
) {
95142
let context: DatadogContext = .mockWith(networkConnectionInfo: network, batteryStatus: battery, isLowPowerModeEnabled: isLowPowerModeEnabled)
96-
let conditions = DataUploadConditions()
143+
let conditions = DataUploadConditions(allowsConstrainedNetworkAccess: allowsConstrainedNetworkAccess)
97144
let canPerformUpload = conditions.blockersForUpload(with: context).isEmpty
98145
XCTAssertEqual(
99146
value,

0 commit comments

Comments
 (0)