Skip to content

Commit ea47053

Browse files
committed
chore(ios): add http_sampling_rate config
1 parent 07163f8 commit ea47053

16 files changed

+148
-139
lines changed

ios/PublicApi/Measure.swiftinterface

Lines changed: 10 additions & 90 deletions
Large diffs are not rendered by default.

ios/Sources/MeasureSDK/Swift/Config/ConfigLoader.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ struct BaseConfigLoader: ConfigLoader {
4646

4747
func loadDynamicConfig(onLoaded: @escaping (DynamicConfig) -> Void) {
4848
let cachedConfig = loadConfigFromDisk()
49-
onLoaded(cachedConfig ?? BaseDynamicConfig.default())
49+
onLoaded(cachedConfig ?? BaseDynamicConfig())
5050
refreshConfigFromServer()
5151
}
5252

ios/Sources/MeasureSDK/Swift/Config/ConfigProvider.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ final class BaseConfigProvider: ConfigProvider {
4242
}
4343
private let defaultConfig: Config
4444
private let lockQueue = DispatchQueue(label: "sh.measure.config-provider")
45-
private var dynamicConfig: DynamicConfig = BaseDynamicConfig.default()
45+
private var dynamicConfig: DynamicConfig = BaseDynamicConfig()
4646
private var httpPatternState = HttpPatternState(
4747
disableEventPatterns: [],
4848
blocklistPatterns: [],
@@ -132,6 +132,7 @@ final class BaseConfigProvider: ConfigProvider {
132132
var anrTakeScreenshot: Bool { dynamicConfig.anrTakeScreenshot }
133133
var launchSamplingRate: Float { dynamicConfig.launchSamplingRate }
134134
var gestureClickTakeSnapshot: Bool { dynamicConfig.gestureClickTakeSnapshot }
135+
var httpSamplingRate: Float { dynamicConfig.httpSamplingRate }
135136
var httpDisableEventForUrls: [String] { dynamicConfig.httpDisableEventForUrls }
136137
var httpTrackRequestForUrls: [String] { dynamicConfig.httpTrackRequestForUrls }
137138
var httpTrackResponseForUrls: [String] { dynamicConfig.httpTrackResponseForUrls }

ios/Sources/MeasureSDK/Swift/Config/DynamicConfig.swift

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ protocol DynamicConfig {
5959
/// Defaults to true.
6060
var gestureClickTakeSnapshot: Bool { get }
6161

62+
/// Sampling rate for htto events.
63+
/// Defaults to 0.01%.
64+
var httpSamplingRate: Float { get }
65+
6266
/// URLs for which HTTP events should be disabled.
6367
var httpDisableEventForUrls: [String] { get }
6468

@@ -86,36 +90,86 @@ struct BaseDynamicConfig: DynamicConfig, Codable {
8690
let anrTakeScreenshot: Bool
8791
let launchSamplingRate: Float
8892
let gestureClickTakeSnapshot: Bool
93+
let httpSamplingRate: Float
8994
let httpDisableEventForUrls: [String]
9095
let httpTrackRequestForUrls: [String]
9196
let httpTrackResponseForUrls: [String]
9297
let httpBlockedHeaders: [String]
93-
94-
static func `default`() -> BaseDynamicConfig {
95-
BaseDynamicConfig(maxEventsInBatch: 10_000,
96-
crashTimelineDurationSeconds: 300,
97-
anrTimelineDurationSeconds: 300,
98-
bugReportTimelineDurationSeconds: 300,
99-
traceSamplingRate: 0.01,
100-
journeySamplingRate: 0.01,
101-
screenshotMaskLevel: .allTextAndMedia,
102-
cpuUsageInterval: 5,
103-
memoryUsageInterval: 5,
104-
crashTakeScreenshot: true,
105-
anrTakeScreenshot: true,
106-
launchSamplingRate: 0.01,
107-
gestureClickTakeSnapshot: true,
108-
httpDisableEventForUrls: [],
109-
httpTrackRequestForUrls: [],
110-
httpTrackResponseForUrls: [],
111-
httpBlockedHeaders: [
112-
"Authorization",
113-
"Cookie",
114-
"Set-Cookie",
115-
"Proxy-Authorization",
116-
"WWW-Authenticate",
117-
"X-Api-Key",
118-
])
98+
99+
init(maxEventsInBatch: Number = 10_000,
100+
crashTimelineDurationSeconds: Number = 300,
101+
anrTimelineDurationSeconds: Number = 300,
102+
bugReportTimelineDurationSeconds: Number = 300,
103+
traceSamplingRate: Float = 0.01,
104+
journeySamplingRate: Float = 0.01,
105+
screenshotMaskLevel: ScreenshotMaskLevel = .allTextAndMedia,
106+
cpuUsageInterval: Number = 5,
107+
memoryUsageInterval: Number = 5,
108+
crashTakeScreenshot: Bool = true,
109+
anrTakeScreenshot: Bool = true,
110+
launchSamplingRate: Float = 0.01,
111+
gestureClickTakeSnapshot: Bool = true,
112+
httpSamplingRate: Float = 0.01,
113+
httpDisableEventForUrls: [String] = [],
114+
httpTrackRequestForUrls: [String] = [],
115+
httpTrackResponseForUrls: [String] = [],
116+
httpBlockedHeaders: [String] = [
117+
"Authorization",
118+
"Cookie",
119+
"Set-Cookie",
120+
"Proxy-Authorization",
121+
"WWW-Authenticate",
122+
"X-Api-Key",
123+
]
124+
) {
125+
self.maxEventsInBatch = maxEventsInBatch
126+
self.crashTimelineDurationSeconds = crashTimelineDurationSeconds
127+
self.anrTimelineDurationSeconds = anrTimelineDurationSeconds
128+
self.bugReportTimelineDurationSeconds = bugReportTimelineDurationSeconds
129+
self.traceSamplingRate = traceSamplingRate
130+
self.journeySamplingRate = journeySamplingRate
131+
self.screenshotMaskLevel = screenshotMaskLevel
132+
self.cpuUsageInterval = cpuUsageInterval
133+
self.memoryUsageInterval = memoryUsageInterval
134+
self.crashTakeScreenshot = crashTakeScreenshot
135+
self.anrTakeScreenshot = anrTakeScreenshot
136+
self.launchSamplingRate = launchSamplingRate
137+
self.gestureClickTakeSnapshot = gestureClickTakeSnapshot
138+
self.httpSamplingRate = httpSamplingRate
139+
self.httpDisableEventForUrls = httpDisableEventForUrls
140+
self.httpTrackRequestForUrls = httpTrackRequestForUrls
141+
self.httpTrackResponseForUrls = httpTrackResponseForUrls
142+
self.httpBlockedHeaders = httpBlockedHeaders
143+
}
144+
145+
init(from decoder: Decoder) throws {
146+
let c = try decoder.container(keyedBy: CodingKeys.self)
147+
148+
maxEventsInBatch = try c.decodeIfPresent(Number.self, forKey: .maxEventsInBatch) ?? 10_000
149+
crashTimelineDurationSeconds = try c.decodeIfPresent(Number.self, forKey: .crashTimelineDurationSeconds) ?? 300
150+
anrTimelineDurationSeconds = try c.decodeIfPresent(Number.self, forKey: .anrTimelineDurationSeconds) ?? 300
151+
bugReportTimelineDurationSeconds = try c.decodeIfPresent(Number.self, forKey: .bugReportTimelineDurationSeconds) ?? 300
152+
traceSamplingRate = try c.decodeIfPresent(Float.self, forKey: .traceSamplingRate) ?? 0.01
153+
journeySamplingRate = try c.decodeIfPresent(Float.self, forKey: .journeySamplingRate) ?? 0.01
154+
screenshotMaskLevel = try c.decodeIfPresent(ScreenshotMaskLevel.self, forKey: .screenshotMaskLevel) ?? .allTextAndMedia
155+
cpuUsageInterval = try c.decodeIfPresent(Number.self, forKey: .cpuUsageInterval) ?? 5
156+
memoryUsageInterval = try c.decodeIfPresent(Number.self, forKey: .memoryUsageInterval) ?? 5
157+
crashTakeScreenshot = try c.decodeIfPresent(Bool.self, forKey: .crashTakeScreenshot) ?? true
158+
anrTakeScreenshot = try c.decodeIfPresent(Bool.self, forKey: .anrTakeScreenshot) ?? true
159+
launchSamplingRate = try c.decodeIfPresent(Float.self, forKey: .launchSamplingRate) ?? 0.01
160+
gestureClickTakeSnapshot = try c.decodeIfPresent(Bool.self, forKey: .gestureClickTakeSnapshot) ?? true
161+
httpSamplingRate = try c.decodeIfPresent(Float.self, forKey: .httpSamplingRate) ?? 0.01
162+
httpDisableEventForUrls = try c.decodeIfPresent([String].self, forKey: .httpDisableEventForUrls) ?? []
163+
httpTrackRequestForUrls = try c.decodeIfPresent([String].self, forKey: .httpTrackRequestForUrls) ?? []
164+
httpTrackResponseForUrls = try c.decodeIfPresent([String].self, forKey: .httpTrackResponseForUrls) ?? []
165+
httpBlockedHeaders = try c.decodeIfPresent([String].self, forKey: .httpBlockedHeaders) ?? [
166+
"Authorization",
167+
"Cookie",
168+
"Set-Cookie",
169+
"Proxy-Authorization",
170+
"WWW-Authenticate",
171+
"X-Api-Key",
172+
]
119173
}
120174

121175
private enum CodingKeys: String, CodingKey {
@@ -132,6 +186,7 @@ struct BaseDynamicConfig: DynamicConfig, Codable {
132186
case anrTakeScreenshot = "anr_take_screenshot"
133187
case launchSamplingRate = "launch_sampling_rate"
134188
case gestureClickTakeSnapshot = "gesture_click_take_snapshot"
189+
case httpSamplingRate = "http_sampling_rate"
135190
case httpDisableEventForUrls = "http_disable_event_for_urls"
136191
case httpTrackRequestForUrls = "http_track_request_for_urls"
137192
case httpTrackResponseForUrls = "http_track_response_for_urls"

ios/Sources/MeasureSDK/Swift/Events/InternalSignalCollector.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ final class BaseInternalSignalCollector: InternalSignalCollector {
173173
attachments: nil,
174174
userDefinedAttributes: serializedUserDefinedAttributes,
175175
threadName: threadName,
176-
needsReporting: false
176+
needsReporting: signalSampler.shouldSampleHttpEvent()
177177
)
178178

179179
case EventType.bugReport.rawValue:

ios/Sources/MeasureSDK/Swift/Events/SignalSampler.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ protocol SignalSampler {
1111
func shouldTrackLaunchEvents() -> Bool
1212
func shouldSampleTrace(_ traceId: String) -> Bool
1313
func shouldTrackJourneyForSession(sessionId: String) -> Bool
14+
func shouldSampleHttpEvent() -> Bool
1415
}
1516

1617
final class BaseSignalSampler: SignalSampler {
@@ -69,6 +70,14 @@ final class BaseSignalSampler: SignalSampler {
6970

7071
return stableSamplingValue(sessionId: sessionId) < samplingRate
7172
}
73+
74+
func shouldSampleHttpEvent() -> Bool {
75+
if configProvider.enableFullCollectionMode {
76+
return true
77+
}
78+
return shouldTrack(configProvider.httpSamplingRate / 100)
79+
}
80+
7281

7382
/// Generates a stable sampling value in [0, 1] from a session ID.
7483
///

ios/Sources/MeasureSDK/Swift/Events/UserTriggeredEventCollector.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ final class BaseUserTriggeredEventCollector: UserTriggeredEventCollector {
178178
responseBody: shouldTrackResponseHttpBody ? responseBody : nil,
179179
client: client)
180180

181-
track(data, type: .http, needsReporting: false)
181+
track(data, type: .http, needsReporting: signalSampler.shouldSampleHttpEvent())
182182
}
183183

184184
private func track(_ data: Codable, type: EventType, userDefinedAttributes: String? = nil, needsReporting: Bool?) {
@@ -190,7 +190,7 @@ final class BaseUserTriggeredEventCollector: UserTriggeredEventCollector {
190190
attachments: nil,
191191
userDefinedAttributes: userDefinedAttributes,
192192
threadName: nil,
193-
needsReporting: needsReporting)
193+
needsReporting: signalSampler.shouldSampleHttpEvent())
194194
}
195195

196196
private func getContentType(headers: [String: String]?) -> String? {

ios/Sources/MeasureSDK/Swift/Http/HttpEventCollector.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ final class BaseHttpEventCollector: HttpEventCollector {
2020
private let httpInterceptorCallbacks: HttpInterceptorCallbacks
2121
private let client: Client
2222
private let configProvider: ConfigProvider
23+
private let signalSampler: SignalSampler
2324
private let httpEventValidator: HttpEventValidator
2425
private var isEnabled = AtomicBool(false)
2526

@@ -30,6 +31,7 @@ final class BaseHttpEventCollector: HttpEventCollector {
3031
httpInterceptorCallbacks: HttpInterceptorCallbacks,
3132
client: Client,
3233
configProvider: ConfigProvider,
34+
signalSampler: SignalSampler,
3335
httpEventValidator: HttpEventValidator) {
3436
self.logger = logger
3537
self.signalProcessor = signalProcessor
@@ -38,6 +40,7 @@ final class BaseHttpEventCollector: HttpEventCollector {
3840
self.httpInterceptorCallbacks = httpInterceptorCallbacks
3941
self.client = client
4042
self.configProvider = configProvider
43+
self.signalSampler = signalSampler
4144
self.httpEventValidator = httpEventValidator
4245
self.httpInterceptorCallbacks.httpDataCallback = onHttpCompletion(data:)
4346
}
@@ -51,6 +54,7 @@ final class BaseHttpEventCollector: HttpEventCollector {
5154
URLSessionTaskInterceptor.shared.setHttpInterceptorCallbacks(httpInterceptorCallbacks)
5255
URLSessionTaskInterceptor.shared.setTimeProvider(timeProvider)
5356
URLSessionTaskInterceptor.shared.setConfigProvider(configProvider)
57+
URLSessionTaskInterceptor.shared.setSignalSampler(signalSampler)
5458
logger.log(level: .info, message: "HttpEventCollector enabled.", error: nil, data: nil)
5559
}
5660
}

ios/Sources/MeasureSDK/Swift/Http/URLSessionTaskInterceptor.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ final class URLSessionTaskInterceptor {
1515
private var configProvider: ConfigProvider?
1616
private var recentRequests: [String: UInt64] = [:]
1717
private let dedupeWindowMs: UInt64 = 300
18+
private var signalSampler: SignalSampler?
1819

1920
private init() {}
2021

@@ -29,18 +30,27 @@ final class URLSessionTaskInterceptor {
2930
func setConfigProvider(_ configProvider: ConfigProvider) {
3031
self.configProvider = configProvider
3132
}
33+
34+
func setSignalSampler(_ signalSampler: SignalSampler) {
35+
self.signalSampler = signalSampler
36+
}
3237

3338
func urlSessionTask(_ task: URLSessionTask, setState state: URLSessionTask.State) { // swiftlint:disable:this function_body_length
3439
guard !MSRNetworkInterceptor.isEnabled else { return }
3540

3641
guard let httpInterceptorCallbacks = self.httpInterceptorCallbacks,
3742
let timeProvider = self.timeProvider,
3843
let configProvider = self.configProvider,
44+
let signalSampler = self.signalSampler,
3945
let url = task.currentRequest?.url?.absoluteString else { return }
4046

4147
guard configProvider.shouldTrackHttpUrl(url: url) else {
4248
return
4349
}
50+
51+
guard signalSampler.shouldTrackLaunchEvents() else {
52+
return
53+
}
4454

4555
if state == .running, taskStartTimes[task] == nil {
4656
taskStartTimes[task] = UnsignedNumber(timeProvider.millisTime)

ios/Sources/MeasureSDK/Swift/MeasureInitializer.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ final class BaseMeasureInitializer: MeasureInitializer {
418418
httpInterceptorCallbacks: HttpInterceptorCallbacks(),
419419
client: client,
420420
configProvider: configProvider,
421+
signalSampler: signalSampler,
421422
httpEventValidator: httpEventValidator)
422423
self.internalSignalCollector = BaseInternalSignalCollector(logger: logger,
423424
timeProvider: timeProvider,

0 commit comments

Comments
 (0)