diff --git a/Examples/apps/BasicExample/BasicExample/AppDelegate.swift b/Examples/apps/BasicExample/BasicExample/AppDelegate.swift index 45afd0d6..b165a176 100644 --- a/Examples/apps/BasicExample/BasicExample/AppDelegate.swift +++ b/Examples/apps/BasicExample/BasicExample/AppDelegate.swift @@ -16,15 +16,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - let configuration = Configuration(writeKey: "eHitWfSPZKoXF8c6iVb6QRlIPA6P9X8G") + let configuration = Configuration(writeKey: "WRITE KEY") .trackApplicationLifecycleEvents(true) .flushInterval(10) .flushAt(2) - Telemetry.shared.flushTimer = 5 * 1000 - Telemetry.shared.enable = true -// Telemetry.shared.sendErrorLogData = true -// Telemetry.shared.host = "webhook.site/8d339731-c5a7-45b5-9b82-d545b6e48e6c" analytics = Analytics(configuration: configuration) return true diff --git a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/ExtensionDelegate.swift b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/ExtensionDelegate.swift index 9a20f3f5..63341a66 100644 --- a/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/ExtensionDelegate.swift +++ b/Examples/apps/watchOSExample/watchOSExample WatchKit Extension/ExtensionDelegate.swift @@ -18,7 +18,6 @@ class ExtensionDelegate: NSObject, WKExtensionDelegate { .flushInterval(10) analytics = Analytics(configuration: configuration) - analytics?.add(plugin: ConsoleLogger(name: "consoleLogger")) analytics?.add(plugin: NotificationTracking()) } diff --git a/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.pbxproj b/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.pbxproj index f40b98f0..629cff69 100644 --- a/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.pbxproj +++ b/Examples/apps/watchOSExample/watchOSExample.xcodeproj/project.pbxproj @@ -3,11 +3,10 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ - 465879B22685058800180335 /* ConsoleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465879B12685058800180335 /* ConsoleLogger.swift */; }; 465879B4268641B900180335 /* SomeScreenController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465879B3268641B900180335 /* SomeScreenController.swift */; }; 469ECD4D2684F9080028BE9A /* watchOSExample WatchKit App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 469ECD4C2684F9080028BE9A /* watchOSExample WatchKit App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 469ECD532684F9080028BE9A /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 469ECD512684F9080028BE9A /* Interface.storyboard */; }; @@ -65,7 +64,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 465879B12685058800180335 /* ConsoleLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConsoleLogger.swift; path = ../../../other_plugins/ConsoleLogger.swift; sourceTree = ""; }; 465879B3268641B900180335 /* SomeScreenController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SomeScreenController.swift; sourceTree = ""; }; 469ECD482684F9080028BE9A /* watchOSExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = watchOSExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 469ECD4C2684F9080028BE9A /* watchOSExample WatchKit App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "watchOSExample WatchKit App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -129,7 +127,6 @@ 469ECD5F2684F9090028BE9A /* watchOSExample WatchKit Extension */ = { isa = PBXGroup; children = ( - 465879B12685058800180335 /* ConsoleLogger.swift */, 469ECD602684F9090028BE9A /* InterfaceController.swift */, 465879B3268641B900180335 /* SomeScreenController.swift */, 469ECD622684F9090028BE9A /* ExtensionDelegate.swift */, @@ -291,7 +288,6 @@ 469ECD672684F9090028BE9A /* ComplicationController.swift in Sources */, 469ECD632684F9090028BE9A /* ExtensionDelegate.swift in Sources */, 46E73DA626F5389E0021042C /* NotificationTracking.swift in Sources */, - 465879B22685058800180335 /* ConsoleLogger.swift in Sources */, 469ECD612684F9090028BE9A /* InterfaceController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/Segment/Utilities/Telemetry.swift b/Sources/Segment/Utilities/Telemetry.swift index 031d4a34..55e4f87c 100644 --- a/Sources/Segment/Utilities/Telemetry.swift +++ b/Sources/Segment/Utilities/Telemetry.swift @@ -1,5 +1,8 @@ import Foundation import Sovran +#if os(Linux) || os(Windows) +import FoundationNetworking +#endif public struct RemoteMetric: Codable { let type: String @@ -68,8 +71,8 @@ public class Telemetry: Subscriber { internal var session: any HTTPSession internal var host: String = HTTPClient.getDefaultAPIHost() - var sampleRate: Double = 0.10 - private var flushTimer: Int = 30 * 1000 + var sampleRate: Double = 1.0 // inital sample rate should be 1.0, will be downsampled on start + private var flushTimer: Int = 30 internal var maxQueueSize: Int = 20 var errorLogSizeMax: Int = 4000 @@ -87,7 +90,8 @@ public class Telemetry: Subscriber { internal var started = false private var rateLimitEndTime: TimeInterval = 0 private var telemetryQueue = DispatchQueue(label: "telemetryQueue") - private var telemetryTimer: Timer? + private var updateQueue = DispatchQueue(label: "updateQueue") + private var telemetryTimer: QueueTimer? /// Starts the Telemetry send loop. Requires both `enable` to be set and a configuration to be retrieved from Segment. func start() { @@ -96,12 +100,19 @@ public class Telemetry: Subscriber { if Double.random(in: 0...1) > sampleRate { resetQueue() + } else { + telemetryQueue.async { + self.queue = self.queue.map { var metric = $0 + metric.value = Int(Double(metric.value) / self.sampleRate) + return metric + } + } } - telemetryTimer = Timer.scheduledTimer(withTimeInterval: TimeInterval(flushTimer) / 1000.0, repeats: true) { [weak self] _ in + self.telemetryTimer = QueueTimer(interval: .seconds(self.flushTimer), queue: .main) { [weak self] in if (!(self?.enable ?? false)) { self?.started = false - self?.telemetryTimer?.invalidate() + self?.telemetryTimer?.suspend() } self?.flush() } @@ -109,7 +120,7 @@ public class Telemetry: Subscriber { /// Resets the telemetry state, including the queue and seen errors. func reset() { - telemetryTimer?.invalidate() + telemetryTimer?.suspend() resetQueue() seenErrors.removeAll() started = false @@ -121,10 +132,12 @@ public class Telemetry: Subscriber { /// - metric: The metric name. /// - buildTags: A closure to build the tags dictionary. func increment(metric: String, buildTags: (inout [String: String]) -> Void) { + guard enable, sampleRate > 0.0 && sampleRate <= 1.0, metric.hasPrefix(Telemetry.METRICS_BASE_TAG), queueHasSpace() else { return } + var tags = [String: String]() buildTags(&tags) + guard !tags.isEmpty else { return } - guard enable, sampleRate > 0.0 && sampleRate <= 1.0, metric.hasPrefix(Telemetry.METRICS_BASE_TAG), !tags.isEmpty, queueHasSpace() else { return } if Double.random(in: 0...1) > sampleRate { return } addRemoteMetric(metric: metric, tags: tags) @@ -136,10 +149,11 @@ public class Telemetry: Subscriber { /// - log: The log data. /// - buildTags: A closure to build the tags dictionary. func error(metric: String, log: String, buildTags: (inout [String: String]) -> Void) { + guard enable, sampleRate > 0.0 && sampleRate <= 1.0, metric.hasPrefix(Telemetry.METRICS_BASE_TAG), queueHasSpace() else { return } + var tags = [String: String]() buildTags(&tags) - - guard enable, sampleRate > 0.0 && sampleRate <= 1.0, metric.hasPrefix(Telemetry.METRICS_BASE_TAG), !tags.isEmpty, queueHasSpace() else { return } + guard !tags.isEmpty else { return } var filteredTags = tags if (!sendWriteKeyOnError) { @@ -248,8 +262,8 @@ public class Telemetry: Subscriber { let fullTags = tags.merging(additionalTags) { (_, new) in new } telemetryQueue.sync { - if var found = queue.first(where: { $0.metric == metric && $0.tags == fullTags }) { - found.value += value + if let index = queue.firstIndex(where: { $0.metric == metric && $0.tags == fullTags }) { + queue[index].value += value return } @@ -275,7 +289,7 @@ public class Telemetry: Subscriber { public func subscribe(_ store: Store) { store.subscribe(self, initialState: true, - queue: telemetryQueue, + queue: updateQueue, handler: systemUpdate ) } diff --git a/Tests/Segment-Tests/Telemetry_Tests.swift b/Tests/Segment-Tests/Telemetry_Tests.swift index 8b85e197..a182b33e 100644 --- a/Tests/Segment-Tests/Telemetry_Tests.swift +++ b/Tests/Segment-Tests/Telemetry_Tests.swift @@ -1,4 +1,6 @@ +#if !os(Linux) && !os(Windows) import XCTest + @testable import Segment class TelemetryTests: XCTestCase { @@ -163,4 +165,6 @@ class URLSessionMock: RestrictedHTTPSession { // Mock URLSessionDataTask class URLSessionDataTaskMock: URLSessionDataTask, @unchecked Sendable { override func resume() {} -} \ No newline at end of file +} + +#endif