From 51b152e53cf5a27e2a1e221e8109b2d513c40d80 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 15 Nov 2024 14:49:29 -0500 Subject: [PATCH 1/6] Fixing watchOS crash and timer startup issue, fixing watchOS sample app --- .../ExtensionDelegate.swift | 1 - .../watchOSExample.xcodeproj/project.pbxproj | 6 +--- Sources/Segment/Utilities/Telemetry.swift | 32 +++++++++++++------ 3 files changed, 24 insertions(+), 15 deletions(-) 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..63a0578d 100644 --- a/Sources/Segment/Utilities/Telemetry.swift +++ b/Sources/Segment/Utilities/Telemetry.swift @@ -68,8 +68,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: Double = 30.0 internal var maxQueueSize: Int = 20 var errorLogSizeMax: Int = 4000 @@ -87,6 +87,7 @@ public class Telemetry: Subscriber { internal var started = false private var rateLimitEndTime: TimeInterval = 0 private var telemetryQueue = DispatchQueue(label: "telemetryQueue") + private var updateQueue = DispatchQueue(label: "updateQueue") private var telemetryTimer: Timer? /// Starts the Telemetry send loop. Requires both `enable` to be set and a configuration to be retrieved from Segment. @@ -96,14 +97,24 @@ 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 + DispatchQueue.main.async { + self.telemetryTimer = Timer.scheduledTimer(withTimeInterval: self.flushTimer, repeats: true) { [weak self] _ in if (!(self?.enable ?? false)) { self?.started = false self?.telemetryTimer?.invalidate() } self?.flush() + } + self.telemetryTimer?.tolerance = self.flushTimer / 10.0 // 10% tolerance } } @@ -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 ) } From 4de3ecde27a9739ae944b56875e09f79b8cb07fb Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 15 Nov 2024 15:15:29 -0500 Subject: [PATCH 2/6] Using internal QueueTimer --- Sources/Segment/Utilities/Telemetry.swift | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Sources/Segment/Utilities/Telemetry.swift b/Sources/Segment/Utilities/Telemetry.swift index 63a0578d..330286e9 100644 --- a/Sources/Segment/Utilities/Telemetry.swift +++ b/Sources/Segment/Utilities/Telemetry.swift @@ -69,7 +69,7 @@ public class Telemetry: Subscriber { internal var session: any HTTPSession internal var host: String = HTTPClient.getDefaultAPIHost() var sampleRate: Double = 1.0 // inital sample rate should be 1.0, will be downsampled on start - private var flushTimer: Double = 30.0 + private var flushTimer: Int = 30 internal var maxQueueSize: Int = 20 var errorLogSizeMax: Int = 4000 @@ -88,7 +88,7 @@ public class Telemetry: Subscriber { private var rateLimitEndTime: TimeInterval = 0 private var telemetryQueue = DispatchQueue(label: "telemetryQueue") private var updateQueue = DispatchQueue(label: "updateQueue") - private var telemetryTimer: Timer? + 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() { @@ -106,21 +106,18 @@ public class Telemetry: Subscriber { } } - DispatchQueue.main.async { - self.telemetryTimer = Timer.scheduledTimer(withTimeInterval: self.flushTimer, 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() - } - self.telemetryTimer?.tolerance = self.flushTimer / 10.0 // 10% tolerance } } /// Resets the telemetry state, including the queue and seen errors. func reset() { - telemetryTimer?.invalidate() + telemetryTimer?.suspend() resetQueue() seenErrors.removeAll() started = false From 728eec6379fdbb2f0eb1b2d78feadb7962cf50e4 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 15 Nov 2024 15:33:03 -0500 Subject: [PATCH 3/6] Removing extraneous testing setup from basic example --- Examples/apps/BasicExample/BasicExample/AppDelegate.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 From e5147646ab7d59dc8b66daed8f8d43139f14ca3d Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 15 Nov 2024 16:01:01 -0500 Subject: [PATCH 4/6] Fixing windows/linux networking errors --- Sources/Segment/Utilities/Telemetry.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/Segment/Utilities/Telemetry.swift b/Sources/Segment/Utilities/Telemetry.swift index 330286e9..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 From 952773209e7b92ca5230908872767b5d09cdc5fa Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 15 Nov 2024 16:09:08 -0500 Subject: [PATCH 5/6] Fixing networking errors in tests on linux --- Tests/Segment-Tests/Telemetry_Tests.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/Segment-Tests/Telemetry_Tests.swift b/Tests/Segment-Tests/Telemetry_Tests.swift index 8b85e197..c25d47b7 100644 --- a/Tests/Segment-Tests/Telemetry_Tests.swift +++ b/Tests/Segment-Tests/Telemetry_Tests.swift @@ -1,4 +1,8 @@ import XCTest +#if os(Linux) || os(Windows) +import FoundationNetworking +#endif + @testable import Segment class TelemetryTests: XCTestCase { @@ -163,4 +167,4 @@ class URLSessionMock: RestrictedHTTPSession { // Mock URLSessionDataTask class URLSessionDataTaskMock: URLSessionDataTask, @unchecked Sendable { override func resume() {} -} \ No newline at end of file +} From 78f1d887af319e718a0759a5edd25be431441c1a Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Fri, 15 Nov 2024 16:22:22 -0500 Subject: [PATCH 6/6] Excluding network-related tests on windows/linux --- Tests/Segment-Tests/Telemetry_Tests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Segment-Tests/Telemetry_Tests.swift b/Tests/Segment-Tests/Telemetry_Tests.swift index c25d47b7..a182b33e 100644 --- a/Tests/Segment-Tests/Telemetry_Tests.swift +++ b/Tests/Segment-Tests/Telemetry_Tests.swift @@ -1,7 +1,5 @@ +#if !os(Linux) && !os(Windows) import XCTest -#if os(Linux) || os(Windows) -import FoundationNetworking -#endif @testable import Segment @@ -168,3 +166,5 @@ class URLSessionMock: RestrictedHTTPSession { class URLSessionDataTaskMock: URLSessionDataTask, @unchecked Sendable { override func resume() {} } + +#endif