Skip to content

Commit

Permalink
chore(ios): add session sampling and data cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
Adwin Ronald Ross committed Jan 16, 2025
1 parent 9b46c98 commit 0887025
Show file tree
Hide file tree
Showing 30 changed files with 414 additions and 73 deletions.
2 changes: 1 addition & 1 deletion ios/DemoApp/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
apiUrl: "http://localhost:8080")
let config = BaseMeasureConfig(enableLogging: true,
trackScreenshotOnCrash: false,
sessionSamplingRate: 1.0)
samplingRateForErrorFreeSessions: 0.5)
measureInstance.initialize(with: clientInfo, config: config)

return true
Expand Down
12 changes: 10 additions & 2 deletions ios/MeasureSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
5222C9E82D14605000B198DA /* NetworkInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5222C9E72D14605000B198DA /* NetworkInterceptor.swift */; };
5224ECE02C88057A00D1B1F7 /* FatalErrorUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5224ECDF2C88057A00D1B1F7 /* FatalErrorUtil.swift */; };
5224ECE32C880FA400D1B1F7 /* XCTextCase+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5224ECE22C880FA300D1B1F7 /* XCTextCase+Extension.swift */; };
522532CA2D295F3F001B5D7C /* AttributeValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522532C92D295F3F001B5D7C /* AttributeValue.swift */; };
5225D02E2D088B7100FD240D /* HttpData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5225D0272D088B7100FD240D /* HttpData.swift */; };
5225D02F2D088B7100FD240D /* HttpEventCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5225D0282D088B7100FD240D /* HttpEventCollector.swift */; };
5225D0302D088B7100FD240D /* HttpInterceptorCallbacks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5225D0292D088B7100FD240D /* HttpInterceptorCallbacks.swift */; };
Expand All @@ -47,8 +48,9 @@
5225D0332D088B7100FD240D /* URLSessionTaskSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5225D02C2D088B7100FD240D /* URLSessionTaskSwizzler.swift */; };
5225D0352D0AEB1A00FD240D /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5225D0342D0AEB1A00FD240D /* String+Extension.swift */; };
5225D0502D0FECFF00FD240D /* InputStream+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5225D04F2D0FECFF00FD240D /* InputStream+Extension.swift */; };
522532CA2D295F3F001B5D7C /* AttributeValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522532C92D295F3F001B5D7C /* AttributeValue.swift */; };
5229D16E2CCB533C00EFFE44 /* RecentSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5229D16D2CCB533C00EFFE44 /* RecentSession.swift */; };
522BA9D42D36579100DBF4A3 /* DataCleanupService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522BA9D32D36579000DBF4A3 /* DataCleanupService.swift */; };
522BA9D62D37C2A000DBF4A3 /* DataCleanupServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522BA9D52D37C2A000DBF4A3 /* DataCleanupServiceTests.swift */; };
523287692C85E07B000EE268 /* LifecycleObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 523287682C85E07B000EE268 /* LifecycleObserverTests.swift */; };
523287732C86195E000EE268 /* SessionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 523287722C86195E000EE268 /* SessionManagerTests.swift */; };
523287752C8619C4000EE268 /* MockIdProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 523287742C8619C4000EE268 /* MockIdProvider.swift */; };
Expand Down Expand Up @@ -359,6 +361,7 @@
5222C9E72D14605000B198DA /* NetworkInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInterceptor.swift; sourceTree = "<group>"; };
5224ECDF2C88057A00D1B1F7 /* FatalErrorUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FatalErrorUtil.swift; sourceTree = "<group>"; };
5224ECE22C880FA300D1B1F7 /* XCTextCase+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTextCase+Extension.swift"; sourceTree = "<group>"; };
522532C92D295F3F001B5D7C /* AttributeValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeValue.swift; sourceTree = "<group>"; };
5225D0272D088B7100FD240D /* HttpData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpData.swift; sourceTree = "<group>"; };
5225D0282D088B7100FD240D /* HttpEventCollector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpEventCollector.swift; sourceTree = "<group>"; };
5225D0292D088B7100FD240D /* HttpInterceptorCallbacks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpInterceptorCallbacks.swift; sourceTree = "<group>"; };
Expand All @@ -367,8 +370,9 @@
5225D02C2D088B7100FD240D /* URLSessionTaskSwizzler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionTaskSwizzler.swift; sourceTree = "<group>"; };
5225D0342D0AEB1A00FD240D /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
5225D04F2D0FECFF00FD240D /* InputStream+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InputStream+Extension.swift"; sourceTree = "<group>"; };
522532C92D295F3F001B5D7C /* AttributeValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeValue.swift; sourceTree = "<group>"; };
5229D16D2CCB533C00EFFE44 /* RecentSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentSession.swift; sourceTree = "<group>"; };
522BA9D32D36579000DBF4A3 /* DataCleanupService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataCleanupService.swift; sourceTree = "<group>"; };
522BA9D52D37C2A000DBF4A3 /* DataCleanupServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataCleanupServiceTests.swift; sourceTree = "<group>"; };
523287682C85E07B000EE268 /* LifecycleObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifecycleObserverTests.swift; sourceTree = "<group>"; };
523287722C86195E000EE268 /* SessionManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManagerTests.swift; sourceTree = "<group>"; };
523287742C8619C4000EE268 /* MockIdProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockIdProvider.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -803,6 +807,7 @@
children = (
524576722CC116DD00B288E5 /* BatchStore.swift */,
524CC5D22C6A4B48001AB506 /* CoreDataManager.swift */,
522BA9D32D36579000DBF4A3 /* DataCleanupService.swift */,
52A1A94B2CA3D3B000461103 /* Entities */,
52A1A9492CA3CF9B00461103 /* EventStore.swift */,
52A1A93B2CA0777300461103 /* SessionStore.swift */,
Expand Down Expand Up @@ -894,6 +899,7 @@
isa = PBXGroup;
children = (
52D3D7372CC404DA004E404B /* BatchStoreTests.swift */,
522BA9D52D37C2A000DBF4A3 /* DataCleanupServiceTests.swift */,
52A1A94E2CA52C8A00461103 /* EventStoreTests.swift */,
52A1A9402CA087BA00461103 /* SessionStoreTests.swift */,
);
Expand Down Expand Up @@ -1401,6 +1407,7 @@
5202BE472C89600200A3496E /* UIDevice+Extension.swift in Sources */,
52AE72012CABAE9000F2830A /* GestureCollector.swift in Sources */,
5202BE3B2C895FC800A3496E /* AttributeProcessor.swift in Sources */,
522BA9D42D36579100DBF4A3 /* DataCleanupService.swift in Sources */,
5202BE3C2C895FC800A3496E /* ComputeOnceAttributeProcessor.swift in Sources */,
5202BE3D2C895FC800A3496E /* DeviceAttributeProcessor.swift in Sources */,
52BCF1DC2CB42026003102DF /* MeasureModel.xcdatamodeld in Sources */,
Expand Down Expand Up @@ -1523,6 +1530,7 @@
5202BE582C89603400A3496E /* MockUserDefaultStorage.swift in Sources */,
52D3D73F2CC4E696004E404B /* EventExporterTests.swift in Sources */,
52D3D73D2CC415B2004E404B /* MockBatchStore.swift in Sources */,
522BA9D62D37C2A000DBF4A3 /* DataCleanupServiceTests.swift in Sources */,
5202BE522C89601200A3496E /* UserAttributeProcessorTests.swift in Sources */,
5224ECE32C880FA400D1B1F7 /* XCTextCase+Extension.swift in Sources */,
52A1A9662CA5AC9900461103 /* Attachment+Extension.swift in Sources */,
Expand Down
8 changes: 6 additions & 2 deletions ios/MeasureSDK/Config/BaseConfigProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ final class BaseConfigProvider: ConfigProvider {
self.cachedConfig = configLoader.getCachedConfig()
}

var eventTypeExportAllowList: [EventType] {
return getMergedConfig(\.eventTypeExportAllowList)
}

var maxUserDefinedAttributesPerEvent: Int {
return getMergedConfig(\.maxUserDefinedAttributesPerEvent)
}
Expand Down Expand Up @@ -69,8 +73,8 @@ final class BaseConfigProvider: ConfigProvider {
return getMergedConfig(\.maxEventsInBatch)
}

var sessionSamplingRate: Float {
return getMergedConfig(\.sessionSamplingRate)
var samplingRateForErrorFreeSessions: Float {
return getMergedConfig(\.samplingRateForErrorFreeSessions)
}

var enableLogging: Bool {
Expand Down
15 changes: 11 additions & 4 deletions ios/MeasureSDK/Config/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Foundation
struct Config: InternalConfig, MeasureConfig {
let enableLogging: Bool
let trackScreenshotOnCrash: Bool
let sessionSamplingRate: Float
let samplingRateForErrorFreeSessions: Float
let eventsBatchingIntervalMs: Number
let sessionEndLastEventThresholdMs: Number
let longPressTimeout: TimeInterval
Expand All @@ -35,14 +35,15 @@ struct Config: InternalConfig, MeasureConfig {
let maxUserDefinedAttributeKeyLength: Int
let maxUserDefinedAttributeValueLength: Int
let maxUserDefinedAttributesPerEvent: Int
let eventTypeExportAllowList: [EventType]

internal init(enableLogging: Bool = DefaultConfig.enableLogging,
trackScreenshotOnCrash: Bool = DefaultConfig.trackScreenshotOnCrash,
sessionSamplingRate: Float = DefaultConfig.sessionSamplingRate) {
samplingRateForErrorFreeSessions: Float = DefaultConfig.sessionSamplingRate) {
self.enableLogging = enableLogging
self.trackScreenshotOnCrash = trackScreenshotOnCrash
self.sessionSamplingRate = sessionSamplingRate
self.eventsBatchingIntervalMs = 3000 // 30 seconds
self.samplingRateForErrorFreeSessions = samplingRateForErrorFreeSessions
self.eventsBatchingIntervalMs = 30000 // 30 seconds
self.maxEventsInBatch = 500
self.sessionEndLastEventThresholdMs = 20 * 60 * 1000 // 20 minitues
self.timeoutIntervalForRequest = 30 // 30 seconds
Expand All @@ -64,5 +65,11 @@ struct Config: InternalConfig, MeasureConfig {
self.maxUserDefinedAttributeKeyLength = 256 // 256 chars
self.maxUserDefinedAttributeValueLength = 256 // 256 chars
self.maxUserDefinedAttributesPerEvent = 100
self.eventTypeExportAllowList = [.coldLaunch,
.hotLaunch,
.warmLaunch,
.lifecycleSwiftUI,
.lifecycleViewController,
.screenView]
}
}
2 changes: 1 addition & 1 deletion ios/MeasureSDK/Config/DefaultConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ import Foundation
struct DefaultConfig {
static let enableLogging = false
static let trackScreenshotOnCrash = true
static let sessionSamplingRate: Float = 1.0
static let sessionSamplingRate: Float = 0.0
}
4 changes: 4 additions & 0 deletions ios/MeasureSDK/Config/InternalConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,8 @@ protocol InternalConfig {

/// The maximum number of user defined attributes for an event. Defaults to 100.
var maxUserDefinedAttributesPerEvent: Int { get }

/// All `EventType`s that are always exported, regardless of other filters like session sampling rate and whether the session crashed or not.
var eventTypeExportAllowList: [EventType] { get }

}
13 changes: 7 additions & 6 deletions ios/MeasureSDK/Config/MeasureConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Foundation
protocol MeasureConfig {
var enableLogging: Bool { get }
var trackScreenshotOnCrash: Bool { get }
var sessionSamplingRate: Float { get }
var samplingRateForErrorFreeSessions: Float { get }
}

/// Configuration options for the Measure SDK. Used to customize the behavior of the SDK on initialization.
Expand All @@ -24,21 +24,22 @@ protocol MeasureConfig {
@objc public final class BaseMeasureConfig: NSObject, MeasureConfig {
let enableLogging: Bool
let trackScreenshotOnCrash: Bool
let sessionSamplingRate: Float
let samplingRateForErrorFreeSessions: Float

/// Configuration options for the Measure SDK. Used to customize the behavior of the SDK on initialization.
/// - Parameters:
/// - enableLogging: Enable or disable internal SDK logs. Defaults to `false`.
/// - trackScreenshotOnCrash: Whether to capture a screenshot of the app when it crashes due to an unhandled exception. Defaults to `true`.
/// - sessionSamplingRate: Allows setting a sampling rate for non-crashed sessions. Session sampling rate must be between 0.0 and 1.0. By default, all non-crashed sessions are always exported.
/// - samplingRateForErrorFreeSessions: Sampling rate for sessions without a crash. The sampling rate is a value between 0 and 1.
/// For example, a value of `0.5` will export only 50% of the non-crashed sessions, and a value of `0` will disable sending non-crashed sessions to the server.
public init(enableLogging: Bool? = nil,
trackScreenshotOnCrash: Bool? = nil,
sessionSamplingRate: Float? = nil) {
samplingRateForErrorFreeSessions: Float? = nil) {
self.enableLogging = enableLogging ?? DefaultConfig.enableLogging
self.trackScreenshotOnCrash = trackScreenshotOnCrash ?? DefaultConfig.trackScreenshotOnCrash
self.sessionSamplingRate = sessionSamplingRate ?? DefaultConfig.sessionSamplingRate
self.samplingRateForErrorFreeSessions = samplingRateForErrorFreeSessions ?? DefaultConfig.sessionSamplingRate

if !(0.0...1.0).contains(self.sessionSamplingRate) {
if !(0.0...1.0).contains(self.samplingRateForErrorFreeSessions) {
fatalError("Session sampling rate must be between 0.0 and 1.0")
}
}
Expand Down
46 changes: 46 additions & 0 deletions ios/MeasureSDK/CoreData/DataCleanupService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// DataCleanupService.swift
// MeasureSDK
//
// Created by Adwin Ross on 14/01/25.
//

import Foundation

protocol DataCleanupService {
func clearStaleData()
}

final class BaseDataCleanupService: DataCleanupService {
private let eventStore: EventStore
private let sessionStore: SessionStore
private let logger: Logger
private let sessionManager: SessionManager

init(eventStore: EventStore, sessionStore: SessionStore, logger: Logger, sessionManager: SessionManager) {
self.eventStore = eventStore
self.sessionStore = sessionStore
self.logger = logger
self.sessionManager = sessionManager
}

func clearStaleData() {
guard let sessionsToDelete = getSessionsToDelete() else {
logger.internalLog(level: .info, message: "No session data to clear.", error: nil, data: nil)
return
}

sessionStore.deleteSessions(sessionsToDelete)
eventStore.deleteEvents(sessionIds: sessionsToDelete)
}

private func getSessionsToDelete() -> [String]? {
guard var sessionsToDelete = sessionStore.getSessionsToDelete() else {
return nil
}

sessionsToDelete.removeAll { $0 == sessionManager.sessionId }

return sessionsToDelete
}
}
8 changes: 6 additions & 2 deletions ios/MeasureSDK/CoreData/Entities/EventEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ struct EventEntity { // swiftlint:disable:this type_body_length
var batchId: String?
let http: Data?
let customEvent: Data?
var needsReporting: Bool

init<T: Codable>(_ event: Event<T>) { // swiftlint:disable:this cyclomatic_complexity function_body_length
init<T: Codable>(_ event: Event<T>, needsReporting: Bool) { // swiftlint:disable:this cyclomatic_complexity function_body_length
self.id = event.id
self.sessionId = event.sessionId
self.timestamp = event.timestamp
Expand All @@ -46,6 +47,7 @@ struct EventEntity { // swiftlint:disable:this type_body_length
self.attachmentSize = 0
self.batchId = nil
self.userDefinedAttributes = event.userDefinedAttributes
self.needsReporting = needsReporting

if let exception = event.data as? Exception {
do {
Expand Down Expand Up @@ -268,7 +270,8 @@ struct EventEntity { // swiftlint:disable:this type_body_length
http: Data?,
networkChange: Data?,
customEvent: Data?,
screenView: Data?) {
screenView: Data?,
needsReporting: Bool) {
self.id = id
self.sessionId = sessionId
self.timestamp = timestamp
Expand Down Expand Up @@ -296,6 +299,7 @@ struct EventEntity { // swiftlint:disable:this type_body_length
self.networkChange = networkChange
self.customEvent = customEvent
self.screenView = screenView
self.needsReporting = needsReporting
}

func getEvent<T: Codable>() -> Event<T> { // swiftlint:disable:this cyclomatic_complexity function_body_length
Expand Down
Loading

0 comments on commit 0887025

Please sign in to comment.