diff --git a/Changelog.md b/Changelog.md index bcd80ff..45f53f8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ - Fix unit test cases that failed in command line execution - Fix unit test cases that failed in Xcode 15 - Change beacon id from 128 bit UUID to 64 bit hex string +- Suspend beacon flushing (on low battery and/or cellular network) becomes configurable ## 1.6.5 - Add crash to mobile feature list and send to Instana backend diff --git a/README.md b/README.md index c2d7b2d..cb8114b 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ To install the iOS agent, use Swift Package Manager (via Xcode) or CocoaPods. 1. Open Xcode. 2. Open your Xcode project. -2. Select menu File -> Add Package Dependencies... -3. In the window that pops up, enter repository https://github.com/instana/iOSAgent to top right edit field "Search or Enter Package URL". +2. Select menu **`File`** -> **`Add Package Dependencies...`** +3. In the window that pops up, enter the repository https://github.com/instana/iOSAgent in the **Search or Enter Package URL** field. Then, select **`iosagent`** from the search result and click **`Add Package`** button. #### CocoaPods diff --git a/Sources/InstanaAgent/Beacons/Reporter.swift b/Sources/InstanaAgent/Beacons/Reporter.swift index 1102694..f960e33 100644 --- a/Sources/InstanaAgent/Beacons/Reporter.swift +++ b/Sources/InstanaAgent/Beacons/Reporter.swift @@ -14,8 +14,8 @@ public class Reporter { internal var sendFirstBeacon = true // first beacon is sent all by itself, not in a batch private var slowSendStartTime: Date? private var inSlowModeBeforeFlush = false - private var lastFlushStartTime: Double? - private var flusher: BeaconFlusher? + internal var lastFlushStartTime: Double? + internal var flusher: BeaconFlusher? internal var send: BeaconFlusher.Sender? private let rateLimiter: ReporterRateLimiter private let batterySafeForNetworking: () -> Bool diff --git a/Sources/InstanaAgent/Configuration/InstanaConfiguraton.swift b/Sources/InstanaAgent/Configuration/InstanaConfiguraton.swift index 7af4cc4..456687d 100644 --- a/Sources/InstanaAgent/Configuration/InstanaConfiguraton.swift +++ b/Sources/InstanaAgent/Configuration/InstanaConfiguraton.swift @@ -75,16 +75,17 @@ class InstanaConfiguration { var isValid: Bool { !key.isEmpty && !reportingURL.absoluteString.isEmpty } required init(reportingURL: URL, key: String, httpCaptureConfig: HTTPCaptureConfig, - enableCrashReporting: Bool, slowSendInterval: Instana.Types.Seconds, + enableCrashReporting: Bool, suspendReporting: Set? = nil, + slowSendInterval: Instana.Types.Seconds, usiRefreshTimeIntervalInHrs: Double) { self.reportingURL = reportingURL self.key = key self.httpCaptureConfig = httpCaptureConfig - suspendReporting = SuspendReporting.defaults monitorTypes = MonitorTypes.current if enableCrashReporting { monitorTypes.insert(.crash) } + self.suspendReporting = suspendReporting ?? SuspendReporting.defaults self.slowSendInterval = slowSendInterval self.usiRefreshTimeIntervalInHrs = usiRefreshTimeIntervalInHrs reporterSendDebounce = Defaults.reporterSendDebounce @@ -98,11 +99,15 @@ class InstanaConfiguration { } static func `default`(key: String, reportingURL: URL, httpCaptureConfig: HTTPCaptureConfig = .automatic, - enableCrashReporting: Bool, slowSendInterval: Instana.Types.Seconds = 0.0, + enableCrashReporting: Bool, + suspendReporting: Set? = nil, + slowSendInterval: Instana.Types.Seconds = 0.0, usiRefreshTimeIntervalInHrs: Double = defaultUsiRefreshTimeIntervalInHrs) -> InstanaConfiguration { self.init(reportingURL: reportingURL, key: key, httpCaptureConfig: httpCaptureConfig, - enableCrashReporting: enableCrashReporting, slowSendInterval: slowSendInterval, + enableCrashReporting: enableCrashReporting, + suspendReporting: suspendReporting, + slowSendInterval: slowSendInterval, usiRefreshTimeIntervalInHrs: usiRefreshTimeIntervalInHrs) } } diff --git a/Sources/InstanaAgent/Instana.swift b/Sources/InstanaAgent/Instana.swift index 59c8a52..bf67151 100644 --- a/Sources/InstanaAgent/Instana.swift +++ b/Sources/InstanaAgent/Instana.swift @@ -84,6 +84,7 @@ import Foundation var httpCaptureConfig = HTTPCaptureConfig.automatic var collectionEnabled = true var enableCrashReporting = false + var suspendReporting = InstanaConfiguration.SuspendReporting.defaults var slowSendInterval = 0.0 var usiRefreshTimeIntervalInHrs = defaultUsiRefreshTimeIntervalInHrs if let options = options { @@ -91,6 +92,16 @@ import Foundation collectionEnabled = options.collectionEnabled enableCrashReporting = options.enableCrashReporting + let suspendReportingOnLowBattery = options.suspendReportingOnLowBattery + let suspendReportingOnCellular = options.suspendReportingOnCellular + suspendReporting = [] + if suspendReportingOnLowBattery { + suspendReporting.insert(InstanaConfiguration.SuspendReporting.lowBattery) + } + if suspendReportingOnCellular { + suspendReporting.insert(InstanaConfiguration.SuspendReporting.cellularConnection) + } + let debounce = InstanaConfiguration.Defaults.reporterSendDebounce if options.slowSendInterval != 0.0, options.slowSendInterval < debounce || options.slowSendInterval > maxSlowSendInterval { @@ -104,6 +115,7 @@ import Foundation let config = InstanaConfiguration.default(key: key, reportingURL: reportingURL, httpCaptureConfig: httpCaptureConfig, enableCrashReporting: enableCrashReporting, + suspendReporting: suspendReporting, slowSendInterval: slowSendInterval, usiRefreshTimeIntervalInHrs: usiRefreshTimeIntervalInHrs) let session = InstanaSession(configuration: config, propertyHandler: InstanaPropertyHandler(), diff --git a/Sources/InstanaAgent/InstanaSetupOptions.swift b/Sources/InstanaAgent/InstanaSetupOptions.swift index 4b814e9..8393816 100644 --- a/Sources/InstanaAgent/InstanaSetupOptions.swift +++ b/Sources/InstanaAgent/InstanaSetupOptions.swift @@ -8,6 +8,8 @@ import Foundation public var httpCaptureConfig: HTTPCaptureConfig public var collectionEnabled: Bool public var enableCrashReporting: Bool + public var suspendReportingOnLowBattery: Bool + public var suspendReportingOnCellular: Bool public var slowSendInterval: Instana.Types.Seconds public var usiRefreshTimeIntervalInHrs: Double @@ -22,11 +24,15 @@ import Foundation @objc public init(httpCaptureConfig: HTTPCaptureConfig = .automatic, collectionEnabled: Bool = true, enableCrashReporting: Bool = false, + suspendReportingOnLowBattery: Bool = false, + suspendReportingOnCellular: Bool = false, slowSendInterval: Instana.Types.Seconds = 0.0, usiRefreshTimeIntervalInHrs: Double = defaultUsiRefreshTimeIntervalInHrs) { self.httpCaptureConfig = httpCaptureConfig self.collectionEnabled = collectionEnabled self.enableCrashReporting = enableCrashReporting + self.suspendReportingOnLowBattery = suspendReportingOnLowBattery + self.suspendReportingOnCellular = suspendReportingOnCellular self.slowSendInterval = slowSendInterval self.usiRefreshTimeIntervalInHrs = usiRefreshTimeIntervalInHrs } diff --git a/Tests/InstanaAgentTests/Beacons/ReporterTests.swift b/Tests/InstanaAgentTests/Beacons/ReporterTests.swift index cecace3..70cc528 100644 --- a/Tests/InstanaAgentTests/Beacons/ReporterTests.swift +++ b/Tests/InstanaAgentTests/Beacons/ReporterTests.swift @@ -36,6 +36,26 @@ class ReporterTests: InstanaTestCase { AssertTrue(didSubmit) } + func test_collection_not_enabled() { + // Given + session = InstanaSession.mock(configuration: config) + session.collectionEnabled = false + + var didSubmit = false + let submittedToQueue = expectation(description: "Submitted To Queue") + let reporter = Reporter(session, batterySafeForNetworking: { true }, networkUtility: .wifi) + + // When + reporter.submit(AlertBeacon(alertType: .lowMemory)) {result in + didSubmit = result + submittedToQueue.fulfill() + } + wait(for: [submittedToQueue], timeout: 3.0) + + // Then + AssertFalse(didSubmit) + } + func test_submit_rateLimit_two_NOT_exceeded() { // Given let submittedToQueue1 = expectation(description: "Submitted First Beacon") @@ -1012,6 +1032,50 @@ class ReporterTests: InstanaTestCase { AssertTrue(resultErrors.contains(instErr)) } + func test_canScheduleFlush_not_allowed_while_flushing() { + // Given + let reporter = Reporter(session, batterySafeForNetworking: { true }, networkUtility: .wifi) + let corebeacons = try! CoreBeaconFactory(session).map([HTTPBeacon.createMock()]) + reporter.queue.add(corebeacons) + + // When + reporter.scheduleFlush() + + // Then + XCTAssertFalse(reporter.canScheduleFlush()) + } + + func test_canScheduleFlush_lastFlushStartTime_nil() { + // Given + let reporter = Reporter(session, batterySafeForNetworking: { true }, networkUtility: .wifi) + let corebeacons = try! CoreBeaconFactory(session).map([HTTPBeacon.createMock()]) + reporter.queue.add(corebeacons) + + // When + reporter.scheduleFlush() + + reporter.lastFlushStartTime = nil // Force scheduling 2nd flushing + + // Then + XCTAssertTrue(reporter.canScheduleFlush()) + } + + + func test_canScheduleFlush_maxFlushingTimeExceeded() { + // Given + let reporter = Reporter(session, batterySafeForNetworking: { true }, networkUtility: .wifi) + let corebeacons = try! CoreBeaconFactory(session).map([HTTPBeacon.createMock()]) + reporter.queue.add(corebeacons) + + // When + reporter.scheduleFlush() + + // last flush time is stale, force flushing + reporter.lastFlushStartTime = reporter.lastFlushStartTime! - (10.0+1) * 60 + // Then + XCTAssertTrue(reporter.canScheduleFlush()) + } + func test_submit_and_flush_shouldNotCause_RetainCycle() { // Given let waitForCompletion = expectation(description: "waitForSend") @@ -1254,6 +1318,23 @@ class ReporterTests: InstanaTestCase { } AssertTrue(reporter.queue.isFull) } + + func test_runBackgroundFlush() { + // Given + let reporter = Reporter(session, batterySafeForNetworking: { true }, networkUtility: .wifi) + + XCTAssertNil(reporter.flusher) + + // When + let corebeacons = try! CoreBeaconFactory(session).map([HTTPBeacon.createMock()]) + reporter.queue.add(corebeacons) + + reporter.runBackgroundFlush() + Thread.sleep(forTimeInterval: 1) + + // Then + XCTAssertNotNil(reporter.flusher) + } } diff --git a/Tests/InstanaAgentTests/InstanaTests.swift b/Tests/InstanaAgentTests/InstanaTests.swift index c2e970f..9ca0670 100644 --- a/Tests/InstanaAgentTests/InstanaTests.swift +++ b/Tests/InstanaAgentTests/InstanaTests.swift @@ -45,6 +45,8 @@ class InstanaTests: InstanaTestCase { let options = InstanaSetupOptions(httpCaptureConfig: .automaticAndManual, collectionEnabled: false) options.enableCrashReporting = true + options.suspendReportingOnLowBattery = true + options.suspendReportingOnCellular = true options.slowSendInterval = 20.0 let ret = Instana.setup(key: key, reportingURL: reportingURL, options: options) @@ -87,6 +89,15 @@ class InstanaTests: InstanaTestCase { AssertFalse(ret2) } + func test_setup_invalid_configuration_empty_key() { + // Given + let reportingURL = URL(string: "http://www.instana.com")! + _ = Instana.setup(key: "", reportingURL: reportingURL, options: nil) + + // Then + AssertFalse(Instana.current!.session.configuration.isValid) + } + func test_setup() { // Given let key = "KEY" @@ -706,4 +717,70 @@ class InstanaTests: InstanaTestCase { // Covers negative case for empty current of Instana XCTAssertFalse(cancelled) } + + @available(*, deprecated) + func test_setup_deprecated1() { + // Given + let key = "KEY" + let reportingURL = URL(string: "http://www.instana.com")! + + Instana.setup(key: key, reportingURL: reportingURL) + + // Then + AssertEqualAndNotNil(Instana.key, key) + AssertEqualAndNotNil(Instana.reportingURL, reportingURL) + AssertTrue(Instana.collectionEnabled) + AssertTrue(Instana.current!.session.collectionEnabled) + AssertEqualAndNotNil(Instana.sessionID, Instana.current?.session.id.uuidString) + + let config = Instana.current?.session.configuration + XCTAssertNotNil(config) + AssertEqualAndNotNil(config!.key, key) + AssertEqualAndNotNil(config!.reportingURL, reportingURL) + AssertEqualAndNotNil(config!.httpCaptureConfig, .automatic) + AssertEqualAndNotNil(config!.slowSendInterval, 0.0) + AssertEqualAndNotNil(config!.usiRefreshTimeIntervalInHrs, defaultUsiRefreshTimeIntervalInHrs) + AssertFalse(config!.monitorTypes.contains(.crash)) + + let session = Instana.current?.session + XCTAssertNotNil(session) + AssertTrue(session!.collectionEnabled) + } + + @available(*, deprecated) + func test_setup_deprecated2() { + // Given + let key = "KEY" + let reportingURL = URL(string: "http://www.instana.com")! + + Instana.setup(key: key, reportingURL: reportingURL, + httpCaptureConfig: .manual, + collectionEnabled: true, + enableCrashReporting: true) + + // Then + AssertEqualAndNotNil(Instana.key, key) + AssertEqualAndNotNil(Instana.reportingURL, reportingURL) + AssertTrue(Instana.collectionEnabled) + AssertTrue(Instana.current!.session.collectionEnabled) + AssertEqualAndNotNil(Instana.sessionID, Instana.current?.session.id.uuidString) + + let config = Instana.current?.session.configuration + XCTAssertNotNil(config) + AssertEqualAndNotNil(config!.key, key) + AssertEqualAndNotNil(config!.reportingURL, reportingURL) + AssertEqualAndNotNil(config!.httpCaptureConfig, .manual) + AssertEqualAndNotNil(config!.slowSendInterval, 0.0) + AssertEqualAndNotNil(config!.usiRefreshTimeIntervalInHrs, defaultUsiRefreshTimeIntervalInHrs) + AssertTrue(config!.monitorTypes.contains(.crash)) + + let session = Instana.current?.session + XCTAssertNotNil(session) + AssertTrue(session!.collectionEnabled) + } + + func test_setup_not_called() { + Instana.current = nil + AssertFalse(Instana.collectionEnabled) + } } diff --git a/Tests/InstanaAgentTests/Monitors/HTTP/HTTPMarkerTests.swift b/Tests/InstanaAgentTests/Monitors/HTTP/HTTPMarkerTests.swift index 3f1e2b6..c94dcf2 100644 --- a/Tests/InstanaAgentTests/Monitors/HTTP/HTTPMarkerTests.swift +++ b/Tests/InstanaAgentTests/Monitors/HTTP/HTTPMarkerTests.swift @@ -61,7 +61,8 @@ class HTTPMarkerTests: InstanaTestCase { let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: ["KEY": "VALUE"])! let marker = HTTPMarker(url: url, method: "GET", trigger: .automatic, delegate: Delegate()) let responseSize = HTTPMarker.Size(response) - let excpectedHeaderSize = Instana.Types.Bytes(NSKeyedArchiver.archivedData(withRootObject: response.allHeaderFields).count) + let excpectedHeaderSize = try! Instana.Types.Bytes( + NSKeyedArchiver.archivedData(withRootObject: response.allHeaderFields, requiringSecureCoding: false).count) // When marker.set(responseSize: responseSize)