@rejourneyco/react-native 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +20 -18
- package/android/src/main/java/com/rejourney/recording/InteractionRecorder.kt +28 -0
- package/android/src/main/java/com/rejourney/recording/ReplayOrchestrator.kt +42 -33
- package/android/src/main/java/com/rejourney/recording/SegmentDispatcher.kt +242 -34
- package/android/src/main/java/com/rejourney/recording/SpecialCases.kt +572 -0
- package/android/src/main/java/com/rejourney/recording/TelemetryPipeline.kt +6 -4
- package/android/src/main/java/com/rejourney/recording/VisualCapture.kt +156 -64
- package/ios/Engine/RejourneyImpl.swift +3 -18
- package/ios/Recording/InteractionRecorder.swift +28 -0
- package/ios/Recording/ReplayOrchestrator.swift +50 -17
- package/ios/Recording/SegmentDispatcher.swift +147 -13
- package/ios/Recording/SpecialCases.swift +614 -0
- package/ios/Recording/StabilityMonitor.swift +2 -2
- package/ios/Recording/TelemetryPipeline.swift +21 -3
- package/ios/Recording/VisualCapture.swift +50 -20
- package/lib/commonjs/index.js +4 -5
- package/lib/commonjs/sdk/constants.js +2 -2
- package/lib/commonjs/sdk/utils.js +1 -1
- package/lib/module/index.js +4 -5
- package/lib/module/sdk/constants.js +2 -2
- package/lib/module/sdk/utils.js +1 -1
- package/lib/typescript/sdk/constants.d.ts +2 -2
- package/lib/typescript/types/index.d.ts +1 -6
- package/package.json +2 -2
- package/src/index.ts +9 -10
- package/src/sdk/constants.ts +2 -2
- package/src/sdk/utils.ts +1 -1
- package/src/types/index.ts +1 -6
|
@@ -58,11 +58,67 @@ class SegmentDispatcher private constructor() {
|
|
|
58
58
|
private val circuitBreakerThreshold = 5
|
|
59
59
|
private val circuitResetTime: Long = 60_000 // 60 seconds
|
|
60
60
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
var
|
|
64
|
-
var
|
|
65
|
-
var
|
|
61
|
+
// Per-session SDK telemetry counters
|
|
62
|
+
private val metricsLock = ReentrantLock()
|
|
63
|
+
private var _uploadSuccessCount = 0
|
|
64
|
+
private var _uploadFailureCount = 0
|
|
65
|
+
private var _retryAttemptCount = 0
|
|
66
|
+
private var _circuitBreakerOpenCount = 0
|
|
67
|
+
private var _memoryEvictionCount = 0
|
|
68
|
+
private var _offlinePersistCount = 0
|
|
69
|
+
private var _sessionStartCount = 0
|
|
70
|
+
private var _crashCount = 0
|
|
71
|
+
private var _totalBytesUploaded = 0L
|
|
72
|
+
private var _totalBytesEvicted = 0L
|
|
73
|
+
private var _totalUploadDurationMs = 0.0
|
|
74
|
+
private var _uploadDurationSampleCount = 0
|
|
75
|
+
private var _lastUploadTime: Long? = null
|
|
76
|
+
private var _lastRetryTime: Long? = null
|
|
77
|
+
|
|
78
|
+
val uploadSuccessCount: Int
|
|
79
|
+
get() = metricsLock.withLock { _uploadSuccessCount }
|
|
80
|
+
|
|
81
|
+
val uploadFailureCount: Int
|
|
82
|
+
get() = metricsLock.withLock { _uploadFailureCount }
|
|
83
|
+
|
|
84
|
+
val retryAttemptCount: Int
|
|
85
|
+
get() = metricsLock.withLock { _retryAttemptCount }
|
|
86
|
+
|
|
87
|
+
val circuitBreakerOpenCount: Int
|
|
88
|
+
get() = metricsLock.withLock { _circuitBreakerOpenCount }
|
|
89
|
+
|
|
90
|
+
val memoryEvictionCount: Int
|
|
91
|
+
get() = metricsLock.withLock { _memoryEvictionCount }
|
|
92
|
+
|
|
93
|
+
val offlinePersistCount: Int
|
|
94
|
+
get() = metricsLock.withLock { _offlinePersistCount }
|
|
95
|
+
|
|
96
|
+
val sessionStartCount: Int
|
|
97
|
+
get() = metricsLock.withLock { _sessionStartCount }
|
|
98
|
+
|
|
99
|
+
val crashCount: Int
|
|
100
|
+
get() = metricsLock.withLock { _crashCount }
|
|
101
|
+
|
|
102
|
+
val avgUploadDurationMs: Double
|
|
103
|
+
get() = metricsLock.withLock {
|
|
104
|
+
if (_uploadDurationSampleCount > 0) {
|
|
105
|
+
_totalUploadDurationMs / _uploadDurationSampleCount.toDouble()
|
|
106
|
+
} else {
|
|
107
|
+
0.0
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
val lastUploadTime: Long?
|
|
112
|
+
get() = metricsLock.withLock { _lastUploadTime }
|
|
113
|
+
|
|
114
|
+
val lastRetryTime: Long?
|
|
115
|
+
get() = metricsLock.withLock { _lastRetryTime }
|
|
116
|
+
|
|
117
|
+
val totalBytesUploaded: Long
|
|
118
|
+
get() = metricsLock.withLock { _totalBytesUploaded }
|
|
119
|
+
|
|
120
|
+
val totalBytesEvicted: Long
|
|
121
|
+
get() = metricsLock.withLock { _totalBytesEvicted }
|
|
66
122
|
|
|
67
123
|
private val workerExecutor = Executors.newFixedThreadPool(2)
|
|
68
124
|
private val scope = CoroutineScope(workerExecutor.asCoroutineDispatcher() + SupervisorJob())
|
|
@@ -86,6 +142,7 @@ class SegmentDispatcher private constructor() {
|
|
|
86
142
|
batchSeqNumber = 0
|
|
87
143
|
billingBlocked = false
|
|
88
144
|
consecutiveFailures = 0
|
|
145
|
+
resetSessionTelemetry()
|
|
89
146
|
}
|
|
90
147
|
|
|
91
148
|
fun activate() {
|
|
@@ -113,14 +170,14 @@ class SegmentDispatcher private constructor() {
|
|
|
113
170
|
) {
|
|
114
171
|
val sid = currentReplayId
|
|
115
172
|
val canUpload = canUploadNow()
|
|
116
|
-
DiagnosticLog.
|
|
173
|
+
DiagnosticLog.trace("[SegmentDispatcher] transmitFrameBundle: sid=${sid?.take(12) ?: "null"}, canUpload=$canUpload, frames=$frameCount, bytes=${payload.size}")
|
|
117
174
|
|
|
118
175
|
if (sid != null) {
|
|
119
176
|
DiagnosticLog.debugPresignRequest(endpoint, sid, "screenshots", payload.size)
|
|
120
177
|
}
|
|
121
178
|
|
|
122
179
|
if (sid == null || !canUpload) {
|
|
123
|
-
DiagnosticLog.
|
|
180
|
+
DiagnosticLog.trace("[SegmentDispatcher] transmitFrameBundle: rejected - sid=${sid != null}, canUpload=$canUpload")
|
|
124
181
|
completion?.invoke(false)
|
|
125
182
|
return
|
|
126
183
|
}
|
|
@@ -201,15 +258,18 @@ class SegmentDispatcher private constructor() {
|
|
|
201
258
|
concludedAt: Long,
|
|
202
259
|
backgroundDurationMs: Long,
|
|
203
260
|
metrics: Map<String, Any>?,
|
|
261
|
+
currentQueueDepth: Int = 0,
|
|
204
262
|
completion: (Boolean) -> Unit
|
|
205
263
|
) {
|
|
206
264
|
val url = "$endpoint/api/ingest/session/end"
|
|
265
|
+
ingestFinalizeMetrics(metrics)
|
|
207
266
|
|
|
208
267
|
val body = JSONObject().apply {
|
|
209
268
|
put("sessionId", replayId)
|
|
210
269
|
put("endedAt", concludedAt)
|
|
211
270
|
if (backgroundDurationMs > 0) put("totalBackgroundTimeMs", backgroundDurationMs)
|
|
212
271
|
metrics?.let { put("metrics", JSONObject(it)) }
|
|
272
|
+
put("sdkTelemetry", buildSdkTelemetry(currentQueueDepth))
|
|
213
273
|
}
|
|
214
274
|
|
|
215
275
|
val request = buildRequest(url, body)
|
|
@@ -257,6 +317,7 @@ class SegmentDispatcher private constructor() {
|
|
|
257
317
|
}
|
|
258
318
|
}
|
|
259
319
|
|
|
320
|
+
@Synchronized
|
|
260
321
|
private fun canUploadNow(): Boolean {
|
|
261
322
|
if (billingBlocked) return false
|
|
262
323
|
if (circuitOpen) {
|
|
@@ -269,25 +330,36 @@ class SegmentDispatcher private constructor() {
|
|
|
269
330
|
return true
|
|
270
331
|
}
|
|
271
332
|
|
|
333
|
+
@Synchronized
|
|
272
334
|
private fun registerFailure() {
|
|
273
335
|
consecutiveFailures++
|
|
274
|
-
|
|
336
|
+
metricsLock.withLock {
|
|
337
|
+
_uploadFailureCount++
|
|
338
|
+
}
|
|
275
339
|
if (consecutiveFailures >= circuitBreakerThreshold) {
|
|
276
|
-
if (!circuitOpen)
|
|
340
|
+
if (!circuitOpen) {
|
|
341
|
+
metricsLock.withLock {
|
|
342
|
+
_circuitBreakerOpenCount++
|
|
343
|
+
}
|
|
344
|
+
}
|
|
277
345
|
circuitOpen = true
|
|
278
346
|
circuitOpenTime = System.currentTimeMillis()
|
|
279
347
|
}
|
|
280
348
|
}
|
|
281
349
|
|
|
350
|
+
@Synchronized
|
|
282
351
|
private fun registerSuccess() {
|
|
283
352
|
consecutiveFailures = 0
|
|
284
|
-
|
|
353
|
+
metricsLock.withLock {
|
|
354
|
+
_uploadSuccessCount++
|
|
355
|
+
_lastUploadTime = System.currentTimeMillis()
|
|
356
|
+
}
|
|
285
357
|
}
|
|
286
358
|
|
|
287
359
|
private fun scheduleUpload(upload: PendingUpload, completion: ((Boolean) -> Unit)?) {
|
|
288
|
-
DiagnosticLog.
|
|
360
|
+
DiagnosticLog.trace("[SegmentDispatcher] scheduleUpload: active=$active, type=${upload.contentType}, items=${upload.itemCount}")
|
|
289
361
|
if (!active) {
|
|
290
|
-
DiagnosticLog.
|
|
362
|
+
DiagnosticLog.trace("[SegmentDispatcher] scheduleUpload: rejected - not active")
|
|
291
363
|
completion?.invoke(false)
|
|
292
364
|
return
|
|
293
365
|
}
|
|
@@ -304,16 +376,14 @@ class SegmentDispatcher private constructor() {
|
|
|
304
376
|
|
|
305
377
|
val presignResponse = requestPresignedUrl(upload)
|
|
306
378
|
if (presignResponse == null) {
|
|
307
|
-
DiagnosticLog.notice("[SegmentDispatcher] ❌ requestPresignedUrl FAILED for ${upload.contentType}")
|
|
308
379
|
DiagnosticLog.caution("[SegmentDispatcher] requestPresignedUrl FAILED for ${upload.contentType}")
|
|
309
380
|
registerFailure()
|
|
310
381
|
scheduleRetryIfNeeded(upload, completion)
|
|
311
382
|
return
|
|
312
383
|
}
|
|
313
384
|
|
|
314
|
-
val s3ok = uploadToS3(presignResponse.presignedUrl, upload.payload
|
|
385
|
+
val s3ok = uploadToS3(presignResponse.presignedUrl, upload.payload)
|
|
315
386
|
if (!s3ok) {
|
|
316
|
-
DiagnosticLog.notice("[SegmentDispatcher] ❌ uploadToS3 FAILED for ${upload.contentType}")
|
|
317
387
|
DiagnosticLog.caution("[SegmentDispatcher] uploadToS3 FAILED for ${upload.contentType}")
|
|
318
388
|
registerFailure()
|
|
319
389
|
scheduleRetryIfNeeded(upload, completion)
|
|
@@ -324,7 +394,6 @@ class SegmentDispatcher private constructor() {
|
|
|
324
394
|
if (confirmOk) {
|
|
325
395
|
registerSuccess()
|
|
326
396
|
} else {
|
|
327
|
-
DiagnosticLog.notice("[SegmentDispatcher] ❌ confirmBatchComplete FAILED for ${upload.contentType}")
|
|
328
397
|
DiagnosticLog.caution("[SegmentDispatcher] confirmBatchComplete FAILED for ${upload.contentType}")
|
|
329
398
|
registerFailure()
|
|
330
399
|
}
|
|
@@ -337,6 +406,10 @@ class SegmentDispatcher private constructor() {
|
|
|
337
406
|
retryLock.withLock {
|
|
338
407
|
retryQueue.add(retry)
|
|
339
408
|
}
|
|
409
|
+
metricsLock.withLock {
|
|
410
|
+
_retryAttemptCount++
|
|
411
|
+
_lastRetryTime = System.currentTimeMillis()
|
|
412
|
+
}
|
|
340
413
|
}
|
|
341
414
|
completion?.invoke(false)
|
|
342
415
|
}
|
|
@@ -362,7 +435,7 @@ class SegmentDispatcher private constructor() {
|
|
|
362
435
|
|
|
363
436
|
if (upload.contentType == "events") {
|
|
364
437
|
put("contentType", "events")
|
|
365
|
-
put("batchNumber",
|
|
438
|
+
put("batchNumber", upload.batchNumber)
|
|
366
439
|
put("isSampledIn", isSampledIn) // Server-side enforcement
|
|
367
440
|
} else {
|
|
368
441
|
put("kind", upload.contentType)
|
|
@@ -384,14 +457,14 @@ class SegmentDispatcher private constructor() {
|
|
|
384
457
|
DiagnosticLog.debugPresignResponse(response.code, null, null, durationMs)
|
|
385
458
|
|
|
386
459
|
if (response.code == 402) {
|
|
387
|
-
DiagnosticLog.
|
|
460
|
+
DiagnosticLog.caution("[SegmentDispatcher] presign: 402 Payment Required - billing blocked")
|
|
388
461
|
billingBlocked = true
|
|
389
462
|
return null
|
|
390
463
|
}
|
|
391
464
|
|
|
392
465
|
if (response.code != 200 || responseBody == null) {
|
|
393
466
|
val bodyPreview = responseBody?.take(300) ?: "null"
|
|
394
|
-
DiagnosticLog.
|
|
467
|
+
DiagnosticLog.caution("[SegmentDispatcher] presign failed: status=${response.code} body=$bodyPreview")
|
|
395
468
|
return null
|
|
396
469
|
}
|
|
397
470
|
|
|
@@ -410,17 +483,14 @@ class SegmentDispatcher private constructor() {
|
|
|
410
483
|
PresignResponse(presignedUrl, batchId)
|
|
411
484
|
} catch (e: Exception) {
|
|
412
485
|
val durationMs = (System.currentTimeMillis() - startTime).toDouble()
|
|
413
|
-
DiagnosticLog.
|
|
486
|
+
DiagnosticLog.trace("[SegmentDispatcher] presign exception (${durationMs.toLong()}ms): ${e.javaClass.simpleName}: ${e.message}")
|
|
414
487
|
DiagnosticLog.fault("[SegmentDispatcher] presign exception: ${e.message}")
|
|
415
488
|
null
|
|
416
489
|
}
|
|
417
490
|
}
|
|
418
491
|
|
|
419
|
-
private suspend fun uploadToS3(url: String, payload: ByteArray
|
|
420
|
-
val mediaType =
|
|
421
|
-
"video" -> "video/mp4".toMediaType()
|
|
422
|
-
else -> "application/gzip".toMediaType()
|
|
423
|
-
}
|
|
492
|
+
private suspend fun uploadToS3(url: String, payload: ByteArray): Boolean {
|
|
493
|
+
val mediaType = "application/gzip".toMediaType()
|
|
424
494
|
|
|
425
495
|
val request = Request.Builder()
|
|
426
496
|
.url(url)
|
|
@@ -435,14 +505,16 @@ class SegmentDispatcher private constructor() {
|
|
|
435
505
|
DiagnosticLog.debugUploadComplete("", response.code, durationMs, 0.0)
|
|
436
506
|
|
|
437
507
|
if (response.code in 200..299) {
|
|
438
|
-
|
|
508
|
+
recordUploadStats(durationMs, true, payload.size.toLong())
|
|
439
509
|
true
|
|
440
510
|
} else {
|
|
511
|
+
recordUploadStats(durationMs, false, payload.size.toLong())
|
|
441
512
|
false
|
|
442
513
|
}
|
|
443
514
|
} catch (e: Exception) {
|
|
444
|
-
DiagnosticLog.
|
|
515
|
+
DiagnosticLog.trace("[SegmentDispatcher] S3 upload exception: ${e.message}")
|
|
445
516
|
DiagnosticLog.fault("[SegmentDispatcher] S3 upload exception: ${e.message}")
|
|
517
|
+
recordUploadStats((System.currentTimeMillis() - startTime).toDouble(), false, payload.size.toLong())
|
|
446
518
|
false
|
|
447
519
|
}
|
|
448
520
|
}
|
|
@@ -454,6 +526,7 @@ class SegmentDispatcher private constructor() {
|
|
|
454
526
|
val body = JSONObject().apply {
|
|
455
527
|
put("actualSizeBytes", upload.payload.size)
|
|
456
528
|
put("timestamp", System.currentTimeMillis())
|
|
529
|
+
put("sdkTelemetry", buildSdkTelemetry(0))
|
|
457
530
|
|
|
458
531
|
if (upload.contentType == "events") {
|
|
459
532
|
put("batchId", batchId)
|
|
@@ -488,21 +561,20 @@ class SegmentDispatcher private constructor() {
|
|
|
488
561
|
rangeStart = 0,
|
|
489
562
|
rangeEnd = 0,
|
|
490
563
|
itemCount = eventCount,
|
|
491
|
-
attempt = 0
|
|
564
|
+
attempt = 0,
|
|
565
|
+
batchNumber = batchNum
|
|
492
566
|
)
|
|
493
567
|
|
|
494
568
|
val presignResponse = requestPresignedUrl(upload)
|
|
495
569
|
if (presignResponse == null) {
|
|
496
|
-
DiagnosticLog.notice("[SegmentDispatcher] ❌ requestPresignedUrl FAILED for ${upload.contentType}")
|
|
497
570
|
DiagnosticLog.caution("[SegmentDispatcher] requestPresignedUrl FAILED for ${upload.contentType}")
|
|
498
571
|
registerFailure()
|
|
499
572
|
scheduleRetryIfNeeded(upload, completion)
|
|
500
573
|
return
|
|
501
574
|
}
|
|
502
575
|
|
|
503
|
-
val s3ok = uploadToS3(presignResponse.presignedUrl, upload.payload
|
|
576
|
+
val s3ok = uploadToS3(presignResponse.presignedUrl, upload.payload)
|
|
504
577
|
if (!s3ok) {
|
|
505
|
-
DiagnosticLog.notice("[SegmentDispatcher] ❌ uploadToS3 FAILED for ${upload.contentType}")
|
|
506
578
|
DiagnosticLog.caution("[SegmentDispatcher] uploadToS3 FAILED for ${upload.contentType}")
|
|
507
579
|
registerFailure()
|
|
508
580
|
scheduleRetryIfNeeded(upload, completion)
|
|
@@ -513,7 +585,7 @@ class SegmentDispatcher private constructor() {
|
|
|
513
585
|
if (confirmOk) {
|
|
514
586
|
registerSuccess()
|
|
515
587
|
} else {
|
|
516
|
-
DiagnosticLog.caution("[SegmentDispatcher] confirmBatchComplete FAILED for ${upload.contentType}
|
|
588
|
+
DiagnosticLog.caution("[SegmentDispatcher] confirmBatchComplete FAILED for ${upload.contentType}")
|
|
517
589
|
registerFailure()
|
|
518
590
|
}
|
|
519
591
|
completion?.invoke(confirmOk)
|
|
@@ -521,7 +593,7 @@ class SegmentDispatcher private constructor() {
|
|
|
521
593
|
|
|
522
594
|
private fun buildRequest(url: String, body: JSONObject): Request {
|
|
523
595
|
// Log auth state before building request
|
|
524
|
-
DiagnosticLog.
|
|
596
|
+
DiagnosticLog.trace("[SegmentDispatcher] buildRequest: apiToken=${apiToken?.take(15) ?: "NULL"}, credential=${credential?.take(15) ?: "NULL"}, replayId=${currentReplayId?.take(20) ?: "NULL"}")
|
|
525
597
|
|
|
526
598
|
val requestBody = body.toString().toRequestBody("application/json".toMediaType())
|
|
527
599
|
|
|
@@ -541,6 +613,125 @@ class SegmentDispatcher private constructor() {
|
|
|
541
613
|
DiagnosticLog.debugNetworkRequest("POST", url, request.headers.toMultimap().mapValues { it.value.first() })
|
|
542
614
|
return request
|
|
543
615
|
}
|
|
616
|
+
|
|
617
|
+
private fun ingestFinalizeMetrics(metrics: Map<String, Any>?) {
|
|
618
|
+
val crashes = (metrics?.get("crashCount") as? Number)?.toInt() ?: return
|
|
619
|
+
metricsLock.withLock {
|
|
620
|
+
_crashCount = maxOf(_crashCount, crashes)
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private fun resetSessionTelemetry() {
|
|
625
|
+
metricsLock.withLock {
|
|
626
|
+
_uploadSuccessCount = 0
|
|
627
|
+
_uploadFailureCount = 0
|
|
628
|
+
_retryAttemptCount = 0
|
|
629
|
+
_circuitBreakerOpenCount = 0
|
|
630
|
+
_memoryEvictionCount = 0
|
|
631
|
+
_offlinePersistCount = 0
|
|
632
|
+
_sessionStartCount = 1
|
|
633
|
+
_crashCount = 0
|
|
634
|
+
_totalBytesUploaded = 0L
|
|
635
|
+
_totalBytesEvicted = 0L
|
|
636
|
+
_totalUploadDurationMs = 0.0
|
|
637
|
+
_uploadDurationSampleCount = 0
|
|
638
|
+
_lastUploadTime = null
|
|
639
|
+
_lastRetryTime = null
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
private fun recordUploadStats(durationMs: Double, success: Boolean, bytes: Long) {
|
|
644
|
+
metricsLock.withLock {
|
|
645
|
+
_uploadDurationSampleCount++
|
|
646
|
+
_totalUploadDurationMs += durationMs
|
|
647
|
+
if (success) {
|
|
648
|
+
_totalBytesUploaded += bytes
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
private fun buildSdkTelemetry(currentQueueDepth: Int): JSONObject {
|
|
654
|
+
val retryDepth = retryLock.withLock { retryQueue.size }
|
|
655
|
+
|
|
656
|
+
val (
|
|
657
|
+
successCount,
|
|
658
|
+
failureCount,
|
|
659
|
+
retryCount,
|
|
660
|
+
breakerOpenCount,
|
|
661
|
+
memoryEvictions,
|
|
662
|
+
offlinePersists,
|
|
663
|
+
starts,
|
|
664
|
+
crashes,
|
|
665
|
+
avgDurationMs,
|
|
666
|
+
lastUpload,
|
|
667
|
+
lastRetry,
|
|
668
|
+
uploadedBytes,
|
|
669
|
+
evictedBytes,
|
|
670
|
+
) = metricsLock.withLock {
|
|
671
|
+
val avg = if (_uploadDurationSampleCount > 0) {
|
|
672
|
+
_totalUploadDurationMs / _uploadDurationSampleCount.toDouble()
|
|
673
|
+
} else {
|
|
674
|
+
0.0
|
|
675
|
+
}
|
|
676
|
+
TelemetrySnapshot(
|
|
677
|
+
uploadSuccessCount = _uploadSuccessCount,
|
|
678
|
+
uploadFailureCount = _uploadFailureCount,
|
|
679
|
+
retryAttemptCount = _retryAttemptCount,
|
|
680
|
+
circuitBreakerOpenCount = _circuitBreakerOpenCount,
|
|
681
|
+
memoryEvictionCount = _memoryEvictionCount,
|
|
682
|
+
offlinePersistCount = _offlinePersistCount,
|
|
683
|
+
sessionStartCount = _sessionStartCount,
|
|
684
|
+
crashCount = _crashCount,
|
|
685
|
+
avgUploadDurationMs = avg,
|
|
686
|
+
lastUploadTime = _lastUploadTime,
|
|
687
|
+
lastRetryTime = _lastRetryTime,
|
|
688
|
+
totalBytesUploaded = _totalBytesUploaded,
|
|
689
|
+
totalBytesEvicted = _totalBytesEvicted,
|
|
690
|
+
)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
val totalUploads = successCount + failureCount
|
|
694
|
+
val successRate = if (totalUploads > 0) successCount.toDouble() / totalUploads.toDouble() else 1.0
|
|
695
|
+
|
|
696
|
+
return JSONObject().apply {
|
|
697
|
+
put("uploadSuccessCount", successCount)
|
|
698
|
+
put("uploadFailureCount", failureCount)
|
|
699
|
+
put("retryAttemptCount", retryCount)
|
|
700
|
+
put("circuitBreakerOpenCount", breakerOpenCount)
|
|
701
|
+
put("memoryEvictionCount", memoryEvictions)
|
|
702
|
+
put("offlinePersistCount", offlinePersists)
|
|
703
|
+
put("sessionStartCount", starts)
|
|
704
|
+
put("crashCount", crashes)
|
|
705
|
+
put("uploadSuccessRate", successRate)
|
|
706
|
+
put("avgUploadDurationMs", avgDurationMs)
|
|
707
|
+
put("currentQueueDepth", currentQueueDepth + retryDepth)
|
|
708
|
+
put("lastUploadTime", lastUpload ?: JSONObject.NULL)
|
|
709
|
+
put("lastRetryTime", lastRetry ?: JSONObject.NULL)
|
|
710
|
+
put("totalBytesUploaded", uploadedBytes)
|
|
711
|
+
put("totalBytesEvicted", evictedBytes)
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
fun sdkTelemetrySnapshot(currentQueueDepth: Int = 0): Map<String, Any?> {
|
|
716
|
+
val payload = buildSdkTelemetry(currentQueueDepth)
|
|
717
|
+
return mapOf(
|
|
718
|
+
"uploadSuccessCount" to payload.optInt("uploadSuccessCount", 0),
|
|
719
|
+
"uploadFailureCount" to payload.optInt("uploadFailureCount", 0),
|
|
720
|
+
"retryAttemptCount" to payload.optInt("retryAttemptCount", 0),
|
|
721
|
+
"circuitBreakerOpenCount" to payload.optInt("circuitBreakerOpenCount", 0),
|
|
722
|
+
"memoryEvictionCount" to payload.optInt("memoryEvictionCount", 0),
|
|
723
|
+
"offlinePersistCount" to payload.optInt("offlinePersistCount", 0),
|
|
724
|
+
"sessionStartCount" to payload.optInt("sessionStartCount", 0),
|
|
725
|
+
"crashCount" to payload.optInt("crashCount", 0),
|
|
726
|
+
"uploadSuccessRate" to payload.optDouble("uploadSuccessRate", 1.0),
|
|
727
|
+
"avgUploadDurationMs" to payload.optDouble("avgUploadDurationMs", 0.0),
|
|
728
|
+
"currentQueueDepth" to payload.optInt("currentQueueDepth", 0),
|
|
729
|
+
"lastUploadTime" to (payload.opt("lastUploadTime").takeUnless { it == JSONObject.NULL } as? Number)?.toLong(),
|
|
730
|
+
"lastRetryTime" to (payload.opt("lastRetryTime").takeUnless { it == JSONObject.NULL } as? Number)?.toLong(),
|
|
731
|
+
"totalBytesUploaded" to payload.optLong("totalBytesUploaded", 0),
|
|
732
|
+
"totalBytesEvicted" to payload.optLong("totalBytesEvicted", 0),
|
|
733
|
+
)
|
|
734
|
+
}
|
|
544
735
|
}
|
|
545
736
|
|
|
546
737
|
private data class PendingUpload(
|
|
@@ -550,10 +741,27 @@ private data class PendingUpload(
|
|
|
550
741
|
val rangeStart: Long,
|
|
551
742
|
val rangeEnd: Long,
|
|
552
743
|
val itemCount: Int,
|
|
553
|
-
val attempt: Int
|
|
744
|
+
val attempt: Int,
|
|
745
|
+
val batchNumber: Int = 0
|
|
554
746
|
)
|
|
555
747
|
|
|
556
748
|
private data class PresignResponse(
|
|
557
749
|
val presignedUrl: String,
|
|
558
750
|
val batchId: String
|
|
559
751
|
)
|
|
752
|
+
|
|
753
|
+
private data class TelemetrySnapshot(
|
|
754
|
+
val uploadSuccessCount: Int,
|
|
755
|
+
val uploadFailureCount: Int,
|
|
756
|
+
val retryAttemptCount: Int,
|
|
757
|
+
val circuitBreakerOpenCount: Int,
|
|
758
|
+
val memoryEvictionCount: Int,
|
|
759
|
+
val offlinePersistCount: Int,
|
|
760
|
+
val sessionStartCount: Int,
|
|
761
|
+
val crashCount: Int,
|
|
762
|
+
val avgUploadDurationMs: Double,
|
|
763
|
+
val lastUploadTime: Long?,
|
|
764
|
+
val lastRetryTime: Long?,
|
|
765
|
+
val totalBytesUploaded: Long,
|
|
766
|
+
val totalBytesEvicted: Long,
|
|
767
|
+
)
|