Skip to content

Commit

Permalink
Merge pull request #358 from DataDog/ncreated/RUMM-824-RUMM-823-add-a…
Browse files Browse the repository at this point in the history
…pi-for-custom-upload-frequency-and-batch-size

RUMM-824 Add API for customizing batch size and upload frequency
  • Loading branch information
ncreated authored Jan 7, 2021
2 parents f842174 + 92e2750 commit 53109cf
Show file tree
Hide file tree
Showing 20 changed files with 362 additions and 85 deletions.
4 changes: 4 additions & 0 deletions Datadog/Example/AppConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ struct ExampleAppConfiguration: AppConfiguration {
environment: "tests"
)
.set(serviceName: serviceName)
.set(batchSize: .small)
.set(uploadFrequency: .frequent)

// If the app was launched with test scenarion ENV, apply the scenario configuration
if let testScenario = testScenario {
Expand Down Expand Up @@ -75,6 +77,8 @@ struct UITestsAppConfiguration: AppConfiguration {
environment: "integration"
)
.set(serviceName: "ui-tests-service-name")
.set(batchSize: .small)
.set(uploadFrequency: .frequent)

let serverMockConfiguration = Environment.serverMockConfiguration()

Expand Down
6 changes: 5 additions & 1 deletion Sources/Datadog/Core/FeaturesConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ extension FeaturesConfiguration {
applicationBundleIdentifier: appContext.bundleIdentifier ?? "unknown",
serviceName: configuration.serviceName ?? appContext.bundleIdentifier ?? "ios",
environment: try ifValid(environment: configuration.environment),
performance: .best(for: appContext.bundleType)
performance: PerformancePreset(
batchSize: configuration.batchSize,
uploadFrequency: configuration.uploadFrequency,
bundleType: appContext.bundleType
)
)

if configuration.loggingEnabled {
Expand Down
116 changes: 70 additions & 46 deletions Sources/Datadog/Core/PerformancePreset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,56 +68,80 @@ internal struct PerformancePreset: Equatable, StoragePerformancePreset, UploadPe
let minUploadDelay: TimeInterval
let maxUploadDelay: TimeInterval
let uploadDelayChangeRate: Double
}

// MARK: - Predefined presets

/// Default performance preset.
static let `default` = lowRuntimeImpact

/// Performance preset optimized for low runtime impact.
/// Minimalizes number of data requests send to the server.
static let lowRuntimeImpact = PerformancePreset(
// persistence
maxFileSize: 4 * 1_024 * 1_024, // 4MB
maxDirectorySize: 512 * 1_024 * 1_024, // 512 MB
maxFileAgeForWrite: 4.75,
minFileAgeForRead: 4.75 + 0.5, // `maxFileAgeForWrite` + 0.5s margin
maxFileAgeForRead: 18 * 60 * 60, // 18h
maxObjectsInFile: 500,
maxObjectSize: 256 * 1_024, // 256KB
internal extension PerformancePreset {
init(
batchSize: Datadog.Configuration.BatchSize,
uploadFrequency: Datadog.Configuration.UploadFrequency,
bundleType: BundleType
) {
let meanFileAgeInSeconds: TimeInterval = {
switch (bundleType, batchSize) {
case (.iOSApp, .small): return 5
case (.iOSApp, .medium): return 15
case (.iOSApp, .large): return 60
case (.iOSAppExtension, .small): return 1
case (.iOSAppExtension, .medium): return 3
case (.iOSAppExtension, .large): return 12
}
}()

// upload
initialUploadDelay: 5, // postpone to not impact app launch time
defaultUploadDelay: 5,
minUploadDelay: 1,
maxUploadDelay: 20,
uploadDelayChangeRate: 0.1
)
let minUploadDelayInSeconds: TimeInterval = {
switch (bundleType, uploadFrequency) {
case (.iOSApp, .frequent): return 1
case (.iOSApp, .average): return 5
case (.iOSApp, .rare): return 10
case (.iOSAppExtension, .frequent): return 0.5
case (.iOSAppExtension, .average): return 1
case (.iOSAppExtension, .rare): return 5
}
}()

/// Performance preset optimized for instant data delivery.
/// Minimalizes the time between receiving data form the user and delivering it to the server.
static let instantDataDelivery = PerformancePreset(
// persistence
maxFileSize: `default`.maxFileSize,
maxDirectorySize: `default`.maxDirectorySize,
maxFileAgeForWrite: 2.75,
minFileAgeForRead: 2.75 + 0.5, // `maxFileAgeForWrite` + 0.5s margin
maxFileAgeForRead: `default`.maxFileAgeForRead,
maxObjectsInFile: `default`.maxObjectsInFile,
maxObjectSize: `default`.maxObjectSize,
let uploadDelayFactors: (initial: Double, default: Double, min: Double, max: Double, changeRate: Double) = {
switch bundleType {
case .iOSApp:
return (
initial: 5,
default: 5,
min: 1,
max: 10,
changeRate: 0.1
)
case .iOSAppExtension:
return (
initial: 0.5, // ensures the the first upload is checked quickly after starting the short-lived app extension
default: 3,
min: 1,
max: 5,
changeRate: 0.5 // if batches are found, reduces interval significantly for more uploads in short-lived app extension
)
}
}()

// upload
initialUploadDelay: 0.5, // send quick to have a chance for upload in short-lived app extensions
defaultUploadDelay: 3,
minUploadDelay: 1,
maxUploadDelay: 5,
uploadDelayChangeRate: 0.5 // reduce significantly for more uploads in short-lived app extensions
)
self.init(
meanFileAge: meanFileAgeInSeconds,
minUploadDelay: minUploadDelayInSeconds,
uploadDelayFactors: uploadDelayFactors
)
}

static func best(for bundleType: BundleType) -> PerformancePreset {
switch bundleType {
case .iOSApp: return `default`
case .iOSAppExtension: return instantDataDelivery
}
init(
meanFileAge: TimeInterval,
minUploadDelay: TimeInterval,
uploadDelayFactors: (initial: Double, default: Double, min: Double, max: Double, changeRate: Double)
) {
self.maxFileSize = 4 * 1_024 * 1_024 // 4MB
self.maxDirectorySize = 512 * 1_024 * 1_024 // 512 MB
self.maxFileAgeForWrite = meanFileAge * 0.95 // 5% below the mean age
self.minFileAgeForRead = meanFileAge * 1.05 // 5% above the mean age
self.maxFileAgeForRead = 18 * 60 * 60 // 18h
self.maxObjectsInFile = 500
self.maxObjectSize = 256 * 1_024 // 256KB
self.initialUploadDelay = minUploadDelay * uploadDelayFactors.initial
self.defaultUploadDelay = minUploadDelay * uploadDelayFactors.default
self.minUploadDelay = minUploadDelay * uploadDelayFactors.min
self.maxUploadDelay = minUploadDelay * uploadDelayFactors.max
self.uploadDelayChangeRate = uploadDelayFactors.changeRate
}
}
43 changes: 42 additions & 1 deletion Sources/Datadog/DatadogConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,27 @@ extension Datadog {

/// Datadog SDK configuration.
public struct Configuration {
/// Defines the Datadog SDK policy when batching data together before uploading it to Datadog servers.
/// Smaller batches mean smaller but more network requests, whereas larger batches mean fewer but larger network requests.
public enum BatchSize {
/// Prefer small sized data batches.
case small
/// Prefer medium sized data batches.
case medium
/// Prefer large sized data batches.
case large
}

/// Defines the frequency at which Datadog SDK will try to upload data batches.
public enum UploadFrequency {
/// Try to upload batched data frequently.
case frequent
/// Try to upload batched data with a medium frequency.
case average
/// Try to upload batched data rarely.
case rare
}

public enum DatadogEndpoint {
/// US based servers.
/// Sends data to [app.datadoghq.com](https://app.datadoghq.com/).
Expand Down Expand Up @@ -152,6 +173,8 @@ extension Datadog {
private(set) var rumSessionsSamplingRate: Float
private(set) var rumUIKitViewsPredicate: UIKitRUMViewsPredicate?
private(set) var rumUIKitActionsTrackingEnabled: Bool
private(set) var batchSize: BatchSize
private(set) var uploadFrequency: UploadFrequency

/// Creates the builder for configuring the SDK to work with RUM, Logging and Tracing features.
/// - Parameter rumApplicationID: RUM Application ID obtained on Datadog website.
Expand Down Expand Up @@ -209,7 +232,9 @@ extension Datadog {
firstPartyHosts: nil,
rumSessionsSamplingRate: 100.0,
rumUIKitViewsPredicate: nil,
rumUIKitActionsTrackingEnabled: false
rumUIKitActionsTrackingEnabled: false,
batchSize: .medium,
uploadFrequency: .average
)
}

Expand Down Expand Up @@ -425,6 +450,22 @@ extension Datadog {
return self
}

/// Sets the preferred size of batched data uploaded to Datadog servers.
/// This value impacts the size and number of requests performed by the SDK.
/// - Parameter batchSize: `.medium` by default.
public func set(batchSize: BatchSize) -> Builder {
configuration.batchSize = batchSize
return self
}

/// Sets the preferred frequency of uploading data to Datadog servers.
/// This value impacts the frequency of performing network requests by the SDK.
/// - Parameter uploadFrequency: `.average` by default.
public func set(uploadFrequency: UploadFrequency) -> Builder {
configuration.uploadFrequency = uploadFrequency
return self
}

/// Builds `Datadog.Configuration` object.
public func build() -> Configuration {
return configuration
Expand Down
38 changes: 38 additions & 0 deletions Sources/DatadogObjc/DatadogConfiguration+objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,36 @@ public class DDTracesEndpoint: NSObject {
public static func custom(url: String) -> DDTracesEndpoint { .init(sdkEndpoint: .custom(url: url)) }
}

@objc
public enum DDBatchSize: Int {
case small
case medium
case large

internal var swiftType: Datadog.Configuration.BatchSize {
switch self {
case .small: return .small
case .medium: return .medium
case .large: return .large
}
}
}

@objc
public enum DDUploadFrequency: Int {
case frequent
case average
case rare

internal var swiftType: Datadog.Configuration.UploadFrequency {
switch self {
case .frequent: return .frequent
case .average: return .average
case .rare: return .rare
}
}
}

@objcMembers
public class DDConfiguration: NSObject {
internal let sdkConfiguration: Datadog.Configuration
Expand Down Expand Up @@ -157,6 +187,14 @@ public class DDConfigurationBuilder: NSObject {
_ = sdkBuilder.trackUIKitActions(true)
}

public func set(batchSize: DDBatchSize) {
_ = sdkBuilder.set(batchSize: batchSize.swiftType)
}

public func set(uploadFrequency: DDUploadFrequency) {
_ = sdkBuilder.set(uploadFrequency: uploadFrequency.swiftType)
}

public func build() -> DDConfiguration {
return DDConfiguration(sdkConfiguration: sdkBuilder.build())
}
Expand Down
6 changes: 5 additions & 1 deletion Tests/DatadogBenchmarkTests/BenchmarkMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ private struct DateCorrectorMock: DateCorrectorType {
}
}

extension PerformancePreset {
static let benchmarksPreset = PerformancePreset(batchSize: .small, uploadFrequency: .frequent, bundleType: .iOSApp)
}

extension FeaturesCommonDependencies {
static func mockAny() -> Self {
return .init(
consentProvider: ConsentProvider(initialConsent: .granted),
performance: .default,
performance: .benchmarksPreset,
httpClient: HTTPClient(),
mobileDevice: .current,
dateProvider: SystemDateProvider(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class LoggingStorageBenchmarkTests: XCTestCase {
}

// Wait enough time for `reader` to accept the youngest batch file
Thread.sleep(forTimeInterval: PerformancePreset.default.minFileAgeForRead + 0.1)
Thread.sleep(forTimeInterval: PerformancePreset.benchmarksPreset.minFileAgeForRead + 0.1)

measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) {
self.startMeasuring()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class RUMStorageBenchmarkTests: XCTestCase {
}

// Wait enough time for `reader` to accept the youngest batch file
Thread.sleep(forTimeInterval: PerformancePreset.default.minFileAgeForRead + 0.1)
Thread.sleep(forTimeInterval: PerformancePreset.benchmarksPreset.minFileAgeForRead + 0.1)

measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) {
self.startMeasuring()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class TracingStorageBenchmarkTests: XCTestCase {
}

// Wait enough time for `reader` to accept the youngest batch file
Thread.sleep(forTimeInterval: PerformancePreset.default.minFileAgeForRead + 0.1)
Thread.sleep(forTimeInterval: PerformancePreset.benchmarksPreset.minFileAgeForRead + 0.1)

measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) {
self.startMeasuring()
Expand Down
25 changes: 15 additions & 10 deletions Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,21 @@ class FeaturesConfigurationTests: XCTestCase {
verify(invalidEnvironmentName: String(repeating: "a", count: 197))
}

func testPerformance() throws {
let iOSAppConfiguration = try FeaturesConfiguration(
configuration: .mockAny(), appContext: .mockWith(bundleType: .iOSApp)
)
XCTAssertEqual(iOSAppConfiguration.common.performance, .lowRuntimeImpact)

let iOSAppExtensionConfiguration = try FeaturesConfiguration(
configuration: .mockAny(), appContext: .mockWith(bundleType: .iOSAppExtension)
)
XCTAssertEqual(iOSAppExtensionConfiguration.common.performance, .instantDataDelivery)
func testPerformancePreset() throws {
try BatchSize.allCases
.combined(with: UploadFrequency.allCases)
.combined(with: BundleType.allCases)
.map { ($0.0, $0.1, $1) }
.forEach { batchSize, uploadFrequency, bundleType in
let actualPerformancePreset = try FeaturesConfiguration(
configuration: .mockWith(batchSize: batchSize,uploadFrequency: uploadFrequency),
appContext: .mockWith(bundleType: bundleType)
).common.performance

let expectedPerformancePreset = PerformancePreset(batchSize: batchSize, uploadFrequency: uploadFrequency, bundleType: bundleType)

XCTAssertEqual(actualPerformancePreset, expectedPerformancePreset)
}
}

func testEndpoint() throws {
Expand Down
Loading

0 comments on commit 53109cf

Please sign in to comment.