@grafana/faro-react-native 1.3.0 → 1.4.0
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 +176 -30
- package/android/build.gradle +20 -15
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashIds.kt +44 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashReporter.kt +134 -38
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashSessionStore.kt +433 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashTraceCache.kt +11 -2
- package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativeModule.kt +128 -17
- package/android/src/main/java/com/grafana/faro/reactnative/FaroSessionProcess.kt +88 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FrameMonitor.kt +34 -42
- package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashReporterBelowApi30Test.kt +21 -0
- package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashReporterTest.kt +59 -0
- package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashSessionStoreTest.kt +268 -0
- package/android/src/test/java/com/grafana/faro/reactnative/FaroCrashTraceCacheTest.kt +52 -0
- package/android/src/test/java/com/grafana/faro/reactnative/FaroSessionProcessTest.kt +127 -0
- package/dist/cjs/config/getRNInstrumentations.js +6 -8
- package/dist/cjs/config/getRNInstrumentations.js.map +1 -1
- package/dist/cjs/config/sampling.js.map +1 -1
- package/dist/cjs/config/types.js.map +1 -1
- package/dist/cjs/dataCollection/DataCollectionPolicy.js +1 -2
- package/dist/cjs/dataCollection/DataCollectionPolicy.js.map +1 -1
- package/dist/cjs/generated/faroRNPackageMeta.js +1 -1
- package/dist/cjs/generated/faroRNPackageMeta.js.map +1 -1
- package/dist/cjs/index.js +8 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/instrumentations/anr/instrumentation.js +10 -1
- package/dist/cjs/instrumentations/anr/instrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/appState/index.js +8 -2
- package/dist/cjs/instrumentations/appState/index.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js +19 -6
- package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js +331 -0
- package/dist/cjs/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +2 -2
- package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +2 -2
- package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/types.js.map +1 -1
- package/dist/cjs/instrumentations/errors/index.js +8 -3
- package/dist/cjs/instrumentations/errors/index.js.map +1 -1
- package/dist/cjs/instrumentations/frameMonitoring/instrumentation.js +20 -31
- package/dist/cjs/instrumentations/frameMonitoring/instrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/frameMonitoring/types.js.map +1 -1
- package/dist/cjs/instrumentations/performance/index.js +2 -6
- package/dist/cjs/instrumentations/performance/index.js.map +1 -1
- package/dist/cjs/instrumentations/session/FaroSessionActivityBoundary.js +48 -0
- package/dist/cjs/instrumentations/session/FaroSessionActivityBoundary.js.map +1 -0
- package/dist/cjs/instrumentations/session/directSessionActivity.js +37 -0
- package/dist/cjs/instrumentations/session/directSessionActivity.js.map +1 -0
- package/dist/cjs/instrumentations/session/index.js +214 -45
- package/dist/cjs/instrumentations/session/index.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionActivity.js +43 -0
- package/dist/cjs/instrumentations/session/sessionActivity.js.map +1 -0
- package/dist/cjs/instrumentations/session/sessionAttributes.js +8 -8
- package/dist/cjs/instrumentations/session/sessionAttributes.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js +199 -24
- package/dist/cjs/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionManager/VolatileSessionManager.js +31 -13
- package/dist/cjs/instrumentations/session/sessionManager/VolatileSessionManager.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionManager/persistentSessionRecord.js +72 -0
- package/dist/cjs/instrumentations/session/sessionManager/persistentSessionRecord.js.map +1 -0
- package/dist/cjs/instrumentations/session/sessionManager/sessionConstants.js +1 -1
- package/dist/cjs/instrumentations/session/sessionManager/sessionConstants.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionManager/sessionManagerUtils.js +124 -96
- package/dist/cjs/instrumentations/session/sessionManager/sessionManagerUtils.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionManager/types.js.map +1 -1
- package/dist/cjs/instrumentations/session/sessionProcess.js +96 -0
- package/dist/cjs/instrumentations/session/sessionProcess.js.map +1 -0
- package/dist/cjs/instrumentations/startup/index.js +2 -6
- package/dist/cjs/instrumentations/startup/index.js.map +1 -1
- package/dist/cjs/navigation/utils.js +3 -1
- package/dist/cjs/navigation/utils.js.map +1 -1
- package/dist/cjs/transports/fetch/index.js.map +1 -1
- package/dist/cjs/transports/fetch/transport.js +56 -25
- package/dist/cjs/transports/fetch/transport.js.map +1 -1
- package/dist/cjs/transports/fetch/types.js.map +1 -1
- package/dist/cjs/transports/offline/ConnectivityService.js +1 -1
- package/dist/cjs/transports/offline/ConnectivityService.js.map +1 -1
- package/dist/cjs/transports/offline/OfflineCache.js +2 -2
- package/dist/cjs/transports/offline/OfflineCache.js.map +1 -1
- package/dist/cjs/transports/offline/transport.js +3 -4
- package/dist/cjs/transports/offline/transport.js.map +1 -1
- package/dist/cjs/userPersistence/UserPersistence.js +1 -2
- package/dist/cjs/userPersistence/UserPersistence.js.map +1 -1
- package/dist/esm/config/getRNInstrumentations.js +6 -8
- package/dist/esm/config/getRNInstrumentations.js.map +1 -1
- package/dist/esm/config/sampling.js.map +1 -1
- package/dist/esm/config/types.js.map +1 -1
- package/dist/esm/dataCollection/DataCollectionPolicy.js +1 -2
- package/dist/esm/dataCollection/DataCollectionPolicy.js.map +1 -1
- package/dist/esm/generated/faroRNPackageMeta.js +1 -1
- package/dist/esm/generated/faroRNPackageMeta.js.map +1 -1
- package/dist/esm/index.js +4 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/instrumentations/anr/instrumentation.js +6 -1
- package/dist/esm/instrumentations/anr/instrumentation.js.map +1 -1
- package/dist/esm/instrumentations/appState/index.js +8 -2
- package/dist/esm/instrumentations/appState/index.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js +15 -4
- package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js +230 -0
- package/dist/esm/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +2 -2
- package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +2 -2
- package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/types.js.map +1 -1
- package/dist/esm/instrumentations/errors/index.js +8 -3
- package/dist/esm/instrumentations/errors/index.js.map +1 -1
- package/dist/esm/instrumentations/frameMonitoring/instrumentation.js +17 -32
- package/dist/esm/instrumentations/frameMonitoring/instrumentation.js.map +1 -1
- package/dist/esm/instrumentations/frameMonitoring/types.js.map +1 -1
- package/dist/esm/instrumentations/performance/index.js +2 -6
- package/dist/esm/instrumentations/performance/index.js.map +1 -1
- package/dist/esm/instrumentations/session/FaroSessionActivityBoundary.js +34 -0
- package/dist/esm/instrumentations/session/FaroSessionActivityBoundary.js.map +1 -0
- package/dist/esm/instrumentations/session/directSessionActivity.js +32 -0
- package/dist/esm/instrumentations/session/directSessionActivity.js.map +1 -0
- package/dist/esm/instrumentations/session/index.js +207 -46
- package/dist/esm/instrumentations/session/index.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionActivity.js +38 -0
- package/dist/esm/instrumentations/session/sessionActivity.js.map +1 -0
- package/dist/esm/instrumentations/session/sessionAttributes.js +7 -7
- package/dist/esm/instrumentations/session/sessionAttributes.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js +190 -27
- package/dist/esm/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionManager/VolatileSessionManager.js +31 -14
- package/dist/esm/instrumentations/session/sessionManager/VolatileSessionManager.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionManager/persistentSessionRecord.js +56 -0
- package/dist/esm/instrumentations/session/sessionManager/persistentSessionRecord.js.map +1 -0
- package/dist/esm/instrumentations/session/sessionManager/sessionConstants.js +1 -1
- package/dist/esm/instrumentations/session/sessionManager/sessionConstants.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionManager/sessionManagerUtils.js +120 -44
- package/dist/esm/instrumentations/session/sessionManager/sessionManagerUtils.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionManager/types.js.map +1 -1
- package/dist/esm/instrumentations/session/sessionProcess.js +89 -0
- package/dist/esm/instrumentations/session/sessionProcess.js.map +1 -0
- package/dist/esm/instrumentations/startup/index.js +2 -6
- package/dist/esm/instrumentations/startup/index.js.map +1 -1
- package/dist/esm/navigation/utils.js +2 -1
- package/dist/esm/navigation/utils.js.map +1 -1
- package/dist/esm/transports/fetch/index.js.map +1 -1
- package/dist/esm/transports/fetch/transport.js +38 -14
- package/dist/esm/transports/fetch/transport.js.map +1 -1
- package/dist/esm/transports/fetch/types.js.map +1 -1
- package/dist/esm/transports/offline/ConnectivityService.js +1 -1
- package/dist/esm/transports/offline/ConnectivityService.js.map +1 -1
- package/dist/esm/transports/offline/OfflineCache.js +2 -2
- package/dist/esm/transports/offline/OfflineCache.js.map +1 -1
- package/dist/esm/transports/offline/transport.js +3 -4
- package/dist/esm/transports/offline/transport.js.map +1 -1
- package/dist/esm/userPersistence/UserPersistence.js +1 -2
- package/dist/esm/userPersistence/UserPersistence.js.map +1 -1
- package/dist/types/config/getRNInstrumentations.d.ts +1 -2
- package/dist/types/config/sampling.d.ts +0 -3
- package/dist/types/config/types.d.ts +5 -3
- package/dist/types/dataCollection/DataCollectionPolicy.d.ts +1 -2
- package/dist/types/generated/faroRNPackageMeta.d.ts +1 -1
- package/dist/types/index.d.ts +4 -1
- package/dist/types/instrumentations/anr/instrumentation.d.ts +1 -1
- package/dist/types/instrumentations/appState/index.d.ts +1 -1
- package/dist/types/instrumentations/console/index.d.ts +1 -1
- package/dist/types/instrumentations/crashReporting/BaseCrashReportingInstrumentation.d.ts +14 -4
- package/dist/types/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.d.ts +1 -1
- package/dist/types/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.d.ts +27 -0
- package/dist/types/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.d.ts +3 -3
- package/dist/types/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.d.ts +3 -3
- package/dist/types/instrumentations/crashReporting/types.d.ts +6 -0
- package/dist/types/instrumentations/frameMonitoring/instrumentation.d.ts +3 -3
- package/dist/types/instrumentations/frameMonitoring/types.d.ts +4 -6
- package/dist/types/instrumentations/http/index.d.ts +1 -1
- package/dist/types/instrumentations/performance/index.d.ts +1 -3
- package/dist/types/instrumentations/session/FaroSessionActivityBoundary.d.ts +8 -0
- package/dist/types/instrumentations/session/directSessionActivity.d.ts +13 -0
- package/dist/types/instrumentations/session/index.d.ts +21 -2
- package/dist/types/instrumentations/session/sessionActivity.d.ts +9 -0
- package/dist/types/instrumentations/session/sessionAttributes.d.ts +6 -3
- package/dist/types/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.d.ts +16 -3
- package/dist/types/instrumentations/session/sessionManager/VolatileSessionManager.d.ts +9 -3
- package/dist/types/instrumentations/session/sessionManager/persistentSessionRecord.d.ts +4 -0
- package/dist/types/instrumentations/session/sessionManager/sessionManagerUtils.d.ts +19 -7
- package/dist/types/instrumentations/session/sessionManager/types.d.ts +6 -0
- package/dist/types/instrumentations/session/sessionProcess.d.ts +17 -0
- package/dist/types/instrumentations/startup/index.d.ts +3 -7
- package/dist/types/instrumentations/userActions/index.d.ts +1 -1
- package/dist/types/instrumentations/view/index.d.ts +1 -1
- package/dist/types/instrumentations/xhr/index.d.ts +1 -1
- package/dist/types/navigation/utils.d.ts +1 -0
- package/dist/types/testUtils/mockFaroReactNativeTracing.d.ts +1 -1
- package/dist/types/transports/console/transport.d.ts +1 -1
- package/dist/types/transports/fetch/index.d.ts +1 -1
- package/dist/types/transports/fetch/transport.d.ts +9 -2
- package/dist/types/transports/fetch/types.d.ts +5 -0
- package/dist/types/transports/offline/ConnectivityService.d.ts +1 -1
- package/dist/types/transports/offline/OfflineCache.d.ts +2 -2
- package/dist/types/transports/offline/transport.d.ts +4 -5
- package/dist/types/userPersistence/UserPersistence.d.ts +1 -2
- package/ios/FaroCrashReporter.swift +214 -21
- package/ios/FaroReactNative.swift +24 -0
- package/ios/FaroReactNativeModule.h +2 -1
- package/ios/FaroReactNativeModule.mm +121 -6
- package/ios/RefreshRateVitals.swift +34 -38
- package/package.json +8 -6
- package/src/config/getRNInstrumentations.ts +6 -8
- package/src/config/makeRNConfig.test.ts +6 -0
- package/src/config/sampling.ts +0 -3
- package/src/config/types.ts +5 -3
- package/src/dataCollection/DataCollectionPolicy.ts +1 -2
- package/src/generated/faroRNPackageMeta.ts +1 -1
- package/src/index.ts +8 -2
- package/src/instrumentations/anr/instrumentation.ts +8 -1
- package/src/instrumentations/appState/index.ts +10 -2
- package/src/instrumentations/crashReporting/BaseCrashReportingInstrumentation.ts +28 -8
- package/src/instrumentations/crashReporting/RecoveredCrashReportingInstrumentation.ts +277 -0
- package/src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts +2 -2
- package/src/instrumentations/crashReporting/instrumentation.test.ts +433 -5
- package/src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts +2 -2
- package/src/instrumentations/crashReporting/types.ts +7 -0
- package/src/instrumentations/errors/index.ts +12 -4
- package/src/instrumentations/errors/instrumentation.test.ts +35 -0
- package/src/instrumentations/frameMonitoring/instrumentation.test.ts +155 -0
- package/src/instrumentations/frameMonitoring/instrumentation.ts +19 -38
- package/src/instrumentations/frameMonitoring/types.ts +4 -6
- package/src/instrumentations/performance/index.ts +2 -6
- package/src/instrumentations/session/FaroSessionActivityBoundary.test.ts +60 -0
- package/src/instrumentations/session/FaroSessionActivityBoundary.tsx +41 -0
- package/src/instrumentations/session/beforeSendHook.test.ts +246 -0
- package/src/instrumentations/session/directSessionActivity.test.ts +158 -0
- package/src/instrumentations/session/directSessionActivity.ts +36 -0
- package/src/instrumentations/session/index.ts +269 -46
- package/src/instrumentations/session/persistentColdStart.test.ts +196 -0
- package/src/instrumentations/session/sessionActivity.test.ts +68 -0
- package/src/instrumentations/session/sessionActivity.ts +58 -0
- package/src/instrumentations/session/sessionAttributes.test.ts +37 -2
- package/src/instrumentations/session/sessionAttributes.ts +12 -6
- package/src/instrumentations/session/sessionCleanup.test.ts +410 -0
- package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.test.ts +552 -0
- package/src/instrumentations/session/sessionManager/MmkvPersistentSessionsManager.ts +249 -33
- package/src/instrumentations/session/sessionManager/VolatileSessionManager.ts +38 -18
- package/src/instrumentations/session/sessionManager/persistentSessionRecord.test.ts +103 -0
- package/src/instrumentations/session/sessionManager/persistentSessionRecord.ts +78 -0
- package/src/instrumentations/session/sessionManager/sampling.test.ts +1 -1
- package/src/instrumentations/session/sessionManager/sessionConstants.ts +1 -1
- package/src/instrumentations/session/sessionManager/sessionManagerUtils.test.ts +256 -18
- package/src/instrumentations/session/sessionManager/sessionManagerUtils.ts +180 -39
- package/src/instrumentations/session/sessionManager/types.ts +8 -0
- package/src/instrumentations/session/sessionManagerInstance.test.ts +65 -0
- package/src/instrumentations/session/sessionProcess.test.ts +136 -0
- package/src/instrumentations/session/sessionProcess.ts +114 -0
- package/src/instrumentations/session/startNewSession.test.ts +337 -0
- package/src/instrumentations/startup/index.ts +2 -6
- package/src/navigation/utils.ts +3 -1
- package/src/transports/fetch/index.ts +6 -1
- package/src/transports/fetch/transport.test.ts +86 -2
- package/src/transports/fetch/transport.ts +38 -16
- package/src/transports/fetch/types.ts +7 -0
- package/src/transports/offline/ConnectivityService.ts +1 -1
- package/src/transports/offline/OfflineCache.ts +2 -2
- package/src/transports/offline/transport.ts +3 -4
- package/src/userPersistence/UserPersistence.ts +1 -2
- package/dist/cjs/utils/throttle.js +0 -42
- package/dist/cjs/utils/throttle.js.map +0 -1
- package/dist/esm/utils/throttle.js +0 -34
- package/dist/esm/utils/throttle.js.map +0 -1
- package/dist/types/utils/throttle.d.ts +0 -9
- package/src/utils/throttle.test.ts +0 -141
- package/src/utils/throttle.ts +0 -34
|
@@ -27,7 +27,10 @@ internal object FaroCrashTraceCache {
|
|
|
27
27
|
* Returns a cached trace without clearing it so multiple ApplicationExitInfo
|
|
28
28
|
* rows for the same crash can share the UncaughtExceptionHandler stack.
|
|
29
29
|
*/
|
|
30
|
-
fun peekPendingCrashTrace(
|
|
30
|
+
fun peekPendingCrashTrace(
|
|
31
|
+
context: Context,
|
|
32
|
+
nowMs: Long = System.currentTimeMillis(),
|
|
33
|
+
): PendingTrace? {
|
|
31
34
|
val prefs = context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
32
35
|
val trace = prefs.getString(KEY_TRACE, null)?.trim().orEmpty()
|
|
33
36
|
val cachedTimestamp = prefs.getLong(KEY_TIMESTAMP, 0L)
|
|
@@ -35,6 +38,13 @@ internal object FaroCrashTraceCache {
|
|
|
35
38
|
if (trace.isEmpty() || cachedTimestamp == 0L) {
|
|
36
39
|
return null
|
|
37
40
|
}
|
|
41
|
+
if (
|
|
42
|
+
cachedTimestamp > nowMs ||
|
|
43
|
+
nowMs - cachedTimestamp > FaroCrashSessionStore.MAX_CONTEXT_AGE_MS
|
|
44
|
+
) {
|
|
45
|
+
clearPendingCrashTrace(context)
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
38
48
|
|
|
39
49
|
return PendingTrace(trace, cachedTimestamp)
|
|
40
50
|
}
|
|
@@ -45,7 +55,6 @@ internal object FaroCrashTraceCache {
|
|
|
45
55
|
}
|
|
46
56
|
val delta = kotlin.math.abs(exitTimestampMs - pending.timestampMs)
|
|
47
57
|
if (delta > MAX_TIMESTAMP_DELTA_MS) {
|
|
48
|
-
clearPendingCrashTrace(context)
|
|
49
58
|
return ""
|
|
50
59
|
}
|
|
51
60
|
return pending.trace
|
|
@@ -39,9 +39,39 @@ class FaroReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
private var anrTracker: ANRTracker? = null
|
|
42
|
+
private val sessionPersistenceOwner = Any()
|
|
42
43
|
|
|
43
44
|
override fun getName(): String = NAME
|
|
44
45
|
|
|
46
|
+
/** Stable process name used to isolate persisted session chains. */
|
|
47
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
48
|
+
fun getSessionProcessIdentifier(): String? {
|
|
49
|
+
return FaroSessionProcess.identifier()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Whether this module is running in the application's declared main process. */
|
|
53
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
54
|
+
fun isMainSessionProcess(): Boolean? {
|
|
55
|
+
return FaroSessionProcess.isMainProcess(reactApplicationContext.applicationContext)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Allows only one React Native runtime in this process to write session state. */
|
|
59
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
60
|
+
fun claimSessionPersistence(): Boolean {
|
|
61
|
+
return FaroSessionProcess.claimPersistence(sessionPersistenceOwner)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Releases session persistence when this React Native runtime is torn down. */
|
|
65
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
66
|
+
fun releaseSessionPersistence(): Boolean {
|
|
67
|
+
return FaroSessionProcess.releasePersistence(sessionPersistenceOwner)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override fun invalidate() {
|
|
71
|
+
FaroSessionProcess.releasePersistence(sessionPersistenceOwner)
|
|
72
|
+
super.invalidate()
|
|
73
|
+
}
|
|
74
|
+
|
|
45
75
|
/**
|
|
46
76
|
* Gets app startup duration in milliseconds using Android OS APIs
|
|
47
77
|
*
|
|
@@ -96,9 +126,9 @@ class FaroReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
96
126
|
*/
|
|
97
127
|
@ReactMethod
|
|
98
128
|
fun startFrameMonitoring(config: ReadableMap) {
|
|
99
|
-
val targetFps = if (config.hasKey("targetFps")) config.getDouble("targetFps") else
|
|
100
|
-
val frozenFrameThresholdMs = if (config.hasKey("frozenFrameThresholdMs")) config.getDouble("frozenFrameThresholdMs") else
|
|
101
|
-
val normalizedRefreshRate = if (config.hasKey("normalizedRefreshRate")) config.getDouble("normalizedRefreshRate") else
|
|
129
|
+
val targetFps = if (config.hasKey("targetFps")) config.getDouble("targetFps") else FrameMonitor.DEFAULT_TARGET_FPS
|
|
130
|
+
val frozenFrameThresholdMs = if (config.hasKey("frozenFrameThresholdMs")) config.getDouble("frozenFrameThresholdMs") else FrameMonitor.DEFAULT_FROZEN_FRAME_THRESHOLD_MS
|
|
131
|
+
val normalizedRefreshRate = if (config.hasKey("normalizedRefreshRate")) config.getDouble("normalizedRefreshRate") else FrameMonitor.DEFAULT_NORMALIZED_REFRESH_RATE
|
|
102
132
|
|
|
103
133
|
FrameMonitor.configure(
|
|
104
134
|
targetFps = targetFps,
|
|
@@ -106,17 +136,8 @@ class FaroReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
106
136
|
normalizedRefreshRate = normalizedRefreshRate
|
|
107
137
|
)
|
|
108
138
|
|
|
109
|
-
//
|
|
139
|
+
// Refresh rate events only; slow/frozen frames are read via getFrameMetrics polling
|
|
110
140
|
FrameMonitor.setCallbacks(
|
|
111
|
-
onSlowFrames = { count -> sendEvent("onSlowFrames", count) },
|
|
112
|
-
onFrozenFrame = { count, durationMs ->
|
|
113
|
-
// Send frozen frame event with count and duration
|
|
114
|
-
val data = WritableNativeMap().apply {
|
|
115
|
-
putInt("count", count)
|
|
116
|
-
putDouble("durationMs", durationMs)
|
|
117
|
-
}
|
|
118
|
-
sendEvent("onFrozenFrame", data)
|
|
119
|
-
},
|
|
120
141
|
onRefreshRate = { rate -> sendEvent("onRefreshRate", rate) }
|
|
121
142
|
)
|
|
122
143
|
|
|
@@ -154,11 +175,12 @@ class FaroReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
154
175
|
*/
|
|
155
176
|
@ReactMethod
|
|
156
177
|
fun getFrameMetrics(promise: Promise) {
|
|
178
|
+
val frozenMetrics = FrameMonitor.getAndResetFrozenMetrics()
|
|
157
179
|
val metrics = WritableNativeMap().apply {
|
|
158
180
|
putDouble("refreshRate", FrameMonitor.getRefreshRate())
|
|
159
181
|
putInt("slowFrames", FrameMonitor.getAndResetSlowFrames())
|
|
160
|
-
putInt("frozenFrames",
|
|
161
|
-
putDouble("frozenDurationMs",
|
|
182
|
+
putInt("frozenFrames", frozenMetrics.count)
|
|
183
|
+
putDouble("frozenDurationMs", frozenMetrics.durationMs)
|
|
162
184
|
}
|
|
163
185
|
promise.resolve(metrics)
|
|
164
186
|
}
|
|
@@ -298,10 +320,39 @@ class FaroReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
298
320
|
*/
|
|
299
321
|
@ReactMethod
|
|
300
322
|
fun getCrashReport(promise: Promise) {
|
|
323
|
+
resolveCrashReports(promise, acknowledgeImmediately = true)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Returns recovered crashes without marking them as processed. JavaScript
|
|
328
|
+
* must acknowledge each report after it has been handled.
|
|
329
|
+
*/
|
|
330
|
+
@ReactMethod
|
|
331
|
+
fun getPendingCrashReports(promise: Promise) {
|
|
332
|
+
resolveCrashReports(promise, acknowledgeImmediately = false)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private fun resolveCrashReports(
|
|
336
|
+
promise: Promise,
|
|
337
|
+
acknowledgeImmediately: Boolean,
|
|
338
|
+
) {
|
|
301
339
|
val crashReports = FaroCrashReporter.getCrashReports(reactApplicationContext)
|
|
302
|
-
|
|
340
|
+
|
|
303
341
|
if (crashReports != null) {
|
|
304
|
-
|
|
342
|
+
if (acknowledgeImmediately) {
|
|
343
|
+
// Preserve eager acknowledgement for older JavaScript bundles
|
|
344
|
+
// that use the legacy bridge and cannot acknowledge per report.
|
|
345
|
+
val reportIds = crashReports.mapNotNull { report ->
|
|
346
|
+
try {
|
|
347
|
+
org.json.JSONObject(report).optString("reportId").trim().takeIf(String::isNotEmpty)
|
|
348
|
+
} catch (_: Exception) {
|
|
349
|
+
null
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
FaroCrashReporter.acknowledgeCrashReports(reactApplicationContext, reportIds)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
val writableArray = Arguments.createArray()
|
|
305
356
|
for (report in crashReports) {
|
|
306
357
|
writableArray.pushString(report)
|
|
307
358
|
}
|
|
@@ -311,6 +362,59 @@ class FaroReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
311
362
|
}
|
|
312
363
|
}
|
|
313
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Persists the active Faro session independently of optional sticky-session storage.
|
|
367
|
+
* The synchronous write makes the context available even if the process crashes
|
|
368
|
+
* immediately after JavaScript observes a session change.
|
|
369
|
+
*/
|
|
370
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
371
|
+
fun recordCrashSessionContext(sessionContext: ReadableMap): Boolean {
|
|
372
|
+
return try {
|
|
373
|
+
val sessionId = sessionContext.getOptionalString("sessionId") ?: return false
|
|
374
|
+
val activatedAtMs = if (sessionContext.hasKey("activatedAt") && !sessionContext.isNull("activatedAt")) {
|
|
375
|
+
sessionContext.getDouble("activatedAt").toLong()
|
|
376
|
+
} else {
|
|
377
|
+
System.currentTimeMillis()
|
|
378
|
+
}
|
|
379
|
+
val isSampled = if (sessionContext.hasKey("isSampled") && !sessionContext.isNull("isSampled")) {
|
|
380
|
+
sessionContext.getBoolean("isSampled")
|
|
381
|
+
} else {
|
|
382
|
+
null
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
FaroUncaughtExceptionHandler.install(reactApplicationContext)
|
|
386
|
+
FaroCrashSessionStore.recordSessionContext(
|
|
387
|
+
context = reactApplicationContext,
|
|
388
|
+
sessionId = sessionId,
|
|
389
|
+
activatedAtMs = activatedAtMs,
|
|
390
|
+
isSampled = isSampled,
|
|
391
|
+
appVersion = sessionContext.getOptionalString("appVersion"),
|
|
392
|
+
appRelease = sessionContext.getOptionalString("appRelease"),
|
|
393
|
+
appBundleId = sessionContext.getOptionalString("appBundleId"),
|
|
394
|
+
)
|
|
395
|
+
} catch (_: Exception) {
|
|
396
|
+
false
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Acknowledges only reports that JavaScript has finished handling.
|
|
402
|
+
*/
|
|
403
|
+
@ReactMethod
|
|
404
|
+
fun acknowledgeCrashReports(reportIds: ReadableArray, promise: Promise) {
|
|
405
|
+
val ids = mutableListOf<String>()
|
|
406
|
+
for (index in 0 until reportIds.size()) {
|
|
407
|
+
if (!reportIds.isNull(index)) {
|
|
408
|
+
reportIds.getString(index)?.trim()?.takeIf(String::isNotEmpty)?.let(ids::add)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (FaroCrashReporter.acknowledgeCrashReports(reactApplicationContext, ids)) {
|
|
412
|
+
promise.resolve(null)
|
|
413
|
+
} else {
|
|
414
|
+
promise.reject("E_CRASH_ACK_FAILED", "Failed to persist recovered crash acknowledgement")
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
314
418
|
/**
|
|
315
419
|
* Required for NativeEventEmitter on modern React Native versions.
|
|
316
420
|
*/
|
|
@@ -336,4 +440,11 @@ class FaroReactNativeModule(reactContext: ReactApplicationContext) :
|
|
|
336
440
|
// Ignore errors when sending events
|
|
337
441
|
}
|
|
338
442
|
}
|
|
443
|
+
|
|
444
|
+
private fun ReadableMap.getOptionalString(key: String): String? {
|
|
445
|
+
if (!hasKey(key) || isNull(key)) {
|
|
446
|
+
return null
|
|
447
|
+
}
|
|
448
|
+
return getString(key)?.trim()?.takeIf(String::isNotEmpty)
|
|
449
|
+
}
|
|
339
450
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
package com.grafana.faro.reactnative
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import java.io.File
|
|
7
|
+
import java.lang.ref.WeakReference
|
|
8
|
+
|
|
9
|
+
/** Process identity and single-writer ownership for persisted session state. */
|
|
10
|
+
internal object FaroSessionProcess {
|
|
11
|
+
private val persistenceLock = Any()
|
|
12
|
+
private var persistenceOwner: WeakReference<Any>? = null
|
|
13
|
+
|
|
14
|
+
fun identifier(): String? =
|
|
15
|
+
identifier(
|
|
16
|
+
sdkInt = Build.VERSION.SDK_INT,
|
|
17
|
+
currentProcessName = { Application.getProcessName() },
|
|
18
|
+
legacyProcessName = { File("/proc/self/cmdline").readText() },
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
internal fun identifier(
|
|
22
|
+
sdkInt: Int,
|
|
23
|
+
currentProcessName: () -> String?,
|
|
24
|
+
legacyProcessName: () -> String?,
|
|
25
|
+
): String? {
|
|
26
|
+
val currentName = try {
|
|
27
|
+
if (sdkInt >= Build.VERSION_CODES.P) {
|
|
28
|
+
currentProcessName()
|
|
29
|
+
} else {
|
|
30
|
+
legacyProcessName()?.substringBefore('\u0000')
|
|
31
|
+
}
|
|
32
|
+
} catch (_: Exception) {
|
|
33
|
+
null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return normalizeIdentifier(currentName)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fun isMainProcess(context: Context): Boolean? {
|
|
40
|
+
return isMainProcess(
|
|
41
|
+
currentName = identifier(),
|
|
42
|
+
declaredMainName = context.applicationInfo.processName,
|
|
43
|
+
packageName = context.packageName,
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fun claimPersistence(owner: Any): Boolean =
|
|
48
|
+
synchronized(persistenceLock) {
|
|
49
|
+
val currentOwner = persistenceOwner?.get()
|
|
50
|
+
if (currentOwner != null && currentOwner !== owner) {
|
|
51
|
+
false
|
|
52
|
+
} else {
|
|
53
|
+
persistenceOwner = WeakReference(owner)
|
|
54
|
+
true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fun releasePersistence(owner: Any): Boolean =
|
|
59
|
+
synchronized(persistenceLock) {
|
|
60
|
+
if (persistenceOwner?.get() !== owner) {
|
|
61
|
+
false
|
|
62
|
+
} else {
|
|
63
|
+
persistenceOwner = null
|
|
64
|
+
true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
internal fun normalizeIdentifier(currentName: String?): String? =
|
|
69
|
+
currentName?.trim()?.takeIf { it.isNotEmpty() }
|
|
70
|
+
|
|
71
|
+
internal fun isMainProcess(
|
|
72
|
+
currentName: String?,
|
|
73
|
+
declaredMainName: String?,
|
|
74
|
+
packageName: String?,
|
|
75
|
+
): Boolean? {
|
|
76
|
+
val normalizedCurrentName = normalizeIdentifier(currentName) ?: return null
|
|
77
|
+
val normalizedMainName = normalizeIdentifier(declaredMainName)
|
|
78
|
+
?: normalizeIdentifier(packageName)
|
|
79
|
+
?: return null
|
|
80
|
+
return normalizedCurrentName == normalizedMainName
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
internal fun resetPersistenceClaimForTest() {
|
|
84
|
+
synchronized(persistenceLock) {
|
|
85
|
+
persistenceOwner = null
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -21,20 +21,27 @@ object FrameMonitor {
|
|
|
21
21
|
private const val NANOSECONDS_IN_MILLISECOND = 1_000_000L
|
|
22
22
|
|
|
23
23
|
// Throttle refresh rate emits to avoid flooding the JS bridge (was ~60/sec).
|
|
24
|
-
// Aligns with refreshRatePollingInterval (30s) on iOS
|
|
24
|
+
// Aligns with refreshRatePollingInterval (30s) on iOS.
|
|
25
25
|
private const val REFRESH_RATE_EMIT_INTERVAL_MS = 30_000L
|
|
26
26
|
|
|
27
|
+
const val DEFAULT_TARGET_FPS = 60.0
|
|
28
|
+
const val DEFAULT_FROZEN_FRAME_THRESHOLD_MS = 700.0
|
|
29
|
+
const val DEFAULT_NORMALIZED_REFRESH_RATE = 60.0
|
|
30
|
+
|
|
31
|
+
data class FrozenFrameMetrics(val count: Int, val durationMs: Double)
|
|
32
|
+
|
|
27
33
|
// Configuration
|
|
28
|
-
private var targetFps: Double =
|
|
29
|
-
private var frozenFrameThresholdNs: Long =
|
|
30
|
-
|
|
34
|
+
private var targetFps: Double = DEFAULT_TARGET_FPS
|
|
35
|
+
private var frozenFrameThresholdNs: Long =
|
|
36
|
+
(DEFAULT_FROZEN_FRAME_THRESHOLD_MS * NANOSECONDS_IN_MILLISECOND).toLong()
|
|
37
|
+
private var normalizedRefreshRate: Double = DEFAULT_NORMALIZED_REFRESH_RATE
|
|
31
38
|
|
|
32
39
|
// State
|
|
33
40
|
private var lastFrameTimeNanos: Long = 0
|
|
34
41
|
private var lastRefreshRate: Double = 0.0
|
|
35
42
|
private var lastRefreshRateEmitTimeMs: Long = 0
|
|
36
43
|
private val slowFrameEventCount = AtomicInteger(0)
|
|
37
|
-
private
|
|
44
|
+
private var frozenFrameCount: Int = 0
|
|
38
45
|
private var frozenFrameDurationMs: Double = 0.0
|
|
39
46
|
private val isMonitoring = AtomicBoolean(false)
|
|
40
47
|
|
|
@@ -54,13 +61,13 @@ object FrameMonitor {
|
|
|
54
61
|
* Configure monitoring parameters.
|
|
55
62
|
*
|
|
56
63
|
* @param targetFps Target FPS for slow frame detection (default 60)
|
|
57
|
-
* @param frozenFrameThresholdMs Threshold in ms for frozen frames (default
|
|
64
|
+
* @param frozenFrameThresholdMs Threshold in ms for frozen frames (default 700)
|
|
58
65
|
* @param normalizedRefreshRate Normalized rate for high-refresh displays (default 60)
|
|
59
66
|
*/
|
|
60
67
|
fun configure(
|
|
61
|
-
targetFps: Double =
|
|
62
|
-
frozenFrameThresholdMs: Double =
|
|
63
|
-
normalizedRefreshRate: Double =
|
|
68
|
+
targetFps: Double = DEFAULT_TARGET_FPS,
|
|
69
|
+
frozenFrameThresholdMs: Double = DEFAULT_FROZEN_FRAME_THRESHOLD_MS,
|
|
70
|
+
normalizedRefreshRate: Double = DEFAULT_NORMALIZED_REFRESH_RATE
|
|
64
71
|
) {
|
|
65
72
|
this.targetFps = targetFps
|
|
66
73
|
this.frozenFrameThresholdNs = (frozenFrameThresholdMs * NANOSECONDS_IN_MILLISECOND).toLong()
|
|
@@ -91,7 +98,7 @@ object FrameMonitor {
|
|
|
91
98
|
lastFrameTimeNanos = 0
|
|
92
99
|
lastRefreshRateEmitTimeMs = 0
|
|
93
100
|
slowFrameEventCount.set(0)
|
|
94
|
-
frozenFrameCount
|
|
101
|
+
frozenFrameCount = 0
|
|
95
102
|
frozenFrameDurationMs = 0.0
|
|
96
103
|
inSlowFrameEvent = false
|
|
97
104
|
slowFrameEventStartTimeNanos = 0
|
|
@@ -131,7 +138,7 @@ object FrameMonitor {
|
|
|
131
138
|
// Reset state
|
|
132
139
|
lastFrameTimeNanos = 0
|
|
133
140
|
slowFrameEventCount.set(0)
|
|
134
|
-
frozenFrameCount
|
|
141
|
+
frozenFrameCount = 0
|
|
135
142
|
frozenFrameDurationMs = 0.0
|
|
136
143
|
}
|
|
137
144
|
|
|
@@ -151,20 +158,22 @@ object FrameMonitor {
|
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
/**
|
|
154
|
-
* Get and reset frozen frame count.
|
|
155
|
-
*/
|
|
156
|
-
fun getAndResetFrozenFrames(): Int {
|
|
157
|
-
return frozenFrameCount.getAndSet(0)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Get and reset frozen frame duration in milliseconds.
|
|
161
|
+
* Get and reset frozen frame count and duration atomically.
|
|
162
162
|
*/
|
|
163
163
|
@Synchronized
|
|
164
|
-
fun
|
|
164
|
+
fun getAndResetFrozenMetrics(): FrozenFrameMetrics {
|
|
165
|
+
val count = frozenFrameCount
|
|
165
166
|
val duration = frozenFrameDurationMs
|
|
167
|
+
frozenFrameCount = 0
|
|
166
168
|
frozenFrameDurationMs = 0.0
|
|
167
|
-
return duration
|
|
169
|
+
return FrozenFrameMetrics(count, duration)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@Synchronized
|
|
173
|
+
private fun recordFrozenFrame(durationMs: Double) {
|
|
174
|
+
frozenFrameCount += 1
|
|
175
|
+
frozenFrameDurationMs += durationMs
|
|
176
|
+
onFrozenFrame?.invoke(frozenFrameCount, durationMs)
|
|
168
177
|
}
|
|
169
178
|
|
|
170
179
|
/**
|
|
@@ -202,10 +211,7 @@ object FrameMonitor {
|
|
|
202
211
|
// A slow frame "event" is a period of consecutive frames below target FPS
|
|
203
212
|
// This groups consecutive slow frames to report meaningful jank, not microsecond variations
|
|
204
213
|
val isSlow = fps < targetFps
|
|
205
|
-
|
|
206
|
-
android.util.Log.d(TAG, "[Faro DEBUG ANDROID] 🐌 Slow frame detected: %.1f FPS (target: %.1f)".format(fps, targetFps))
|
|
207
|
-
}
|
|
208
|
-
|
|
214
|
+
|
|
209
215
|
if (isSlow) {
|
|
210
216
|
if (!inSlowFrameEvent) {
|
|
211
217
|
// Start new slow frame event
|
|
@@ -216,18 +222,11 @@ object FrameMonitor {
|
|
|
216
222
|
// Frame is good - check if we should end the current slow frame event
|
|
217
223
|
if (inSlowFrameEvent) {
|
|
218
224
|
val eventDurationNs = frameTimeNanos - slowFrameEventStartTimeNanos
|
|
219
|
-
|
|
220
|
-
val thresholdMs = slowFrameEventMinDurationNs / NANOSECONDS_IN_MILLISECOND
|
|
221
|
-
|
|
225
|
+
|
|
222
226
|
// Only count as a slow frame event if it lasted long enough to be user-perceptible
|
|
223
227
|
// This filters out single-frame dips that don't affect user experience
|
|
224
228
|
if (eventDurationNs >= slowFrameEventMinDurationNs) {
|
|
225
|
-
|
|
226
|
-
// 🔍 TEMP DEBUG LOG - Remove after analysis
|
|
227
|
-
android.util.Log.d(TAG, "[Faro DEBUG ANDROID] ✅ COUNTED as event (${eventDurationMs}ms)! Total events now: $newCount")
|
|
228
|
-
} else {
|
|
229
|
-
// 🔍 TEMP DEBUG LOG - Remove after analysis
|
|
230
|
-
android.util.Log.d(TAG, "[Faro DEBUG ANDROID] ❌ NOT counted (${eventDurationMs}ms is too short). Total events still: ${slowFrameEventCount.get()}")
|
|
229
|
+
slowFrameEventCount.incrementAndGet()
|
|
231
230
|
}
|
|
232
231
|
|
|
233
232
|
inSlowFrameEvent = false
|
|
@@ -237,15 +236,8 @@ object FrameMonitor {
|
|
|
237
236
|
|
|
238
237
|
// Check for frozen frames (frame duration exceeds threshold)
|
|
239
238
|
if (frameDuration > frozenFrameThresholdNs) {
|
|
240
|
-
val count = frozenFrameCount.incrementAndGet()
|
|
241
239
|
val durationMs = frameDuration.toDouble() / NANOSECONDS_IN_MILLISECOND
|
|
242
|
-
|
|
243
|
-
// Track duration (synchronized to avoid race conditions)
|
|
244
|
-
synchronized(this) {
|
|
245
|
-
frozenFrameDurationMs += durationMs
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
onFrozenFrame?.invoke(count, durationMs)
|
|
240
|
+
recordFrozenFrame(durationMs)
|
|
249
241
|
}
|
|
250
242
|
|
|
251
243
|
lastFrameTimeNanos = frameTimeNanos
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package com.grafana.faro.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.test.core.app.ApplicationProvider
|
|
5
|
+
import org.junit.Assert.assertNull
|
|
6
|
+
import org.junit.Test
|
|
7
|
+
import org.junit.runner.RunWith
|
|
8
|
+
import org.robolectric.RobolectricTestRunner
|
|
9
|
+
import org.robolectric.annotation.Config
|
|
10
|
+
|
|
11
|
+
@RunWith(RobolectricTestRunner::class)
|
|
12
|
+
@Config(manifest = Config.NONE, sdk = [29])
|
|
13
|
+
class FaroCrashReporterBelowApi30Test {
|
|
14
|
+
|
|
15
|
+
@Test
|
|
16
|
+
fun getCrashReports_isNoOpBeforeApi30() {
|
|
17
|
+
val context = ApplicationProvider.getApplicationContext<Context>()
|
|
18
|
+
|
|
19
|
+
assertNull(FaroCrashReporter.getCrashReports(context))
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
package com.grafana.faro.reactnative
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertEquals
|
|
4
|
+
import org.junit.Assert.assertFalse
|
|
5
|
+
import org.junit.Assert.assertNotEquals
|
|
6
|
+
import org.junit.Assert.assertTrue
|
|
7
|
+
import org.junit.Test
|
|
8
|
+
|
|
9
|
+
class FaroCrashReporterTest {
|
|
10
|
+
|
|
11
|
+
@Test
|
|
12
|
+
fun reportId_isStableForSameProcessCrash() {
|
|
13
|
+
val first = FaroCrashIds.reportId(
|
|
14
|
+
packageName = "com.example.app",
|
|
15
|
+
timestampMs = 1_000L,
|
|
16
|
+
pid = 101,
|
|
17
|
+
processName = "com.example.app",
|
|
18
|
+
)
|
|
19
|
+
val second = FaroCrashIds.reportId(
|
|
20
|
+
packageName = "com.example.app",
|
|
21
|
+
timestampMs = 1_000L,
|
|
22
|
+
pid = 101,
|
|
23
|
+
processName = "com.example.app",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
assertEquals(first, second)
|
|
27
|
+
assertTrue(first.startsWith("v1:"))
|
|
28
|
+
assertEquals(67, first.length)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Test
|
|
32
|
+
fun reportId_distinguishesProcessesAtSameTimestamp() {
|
|
33
|
+
val first = FaroCrashIds.reportId(
|
|
34
|
+
packageName = "com.example.app",
|
|
35
|
+
timestampMs = 1_000L,
|
|
36
|
+
pid = 101,
|
|
37
|
+
processName = "com.example.app",
|
|
38
|
+
)
|
|
39
|
+
val second = FaroCrashIds.reportId(
|
|
40
|
+
packageName = "com.example.app",
|
|
41
|
+
timestampMs = 1_000L,
|
|
42
|
+
pid = 202,
|
|
43
|
+
processName = "com.example.app:worker",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
assertNotEquals(first, second)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Test
|
|
50
|
+
fun replayWindow_includesExactSevenDayBoundary() {
|
|
51
|
+
val nowMs = 1_000_000_000L
|
|
52
|
+
val sevenDays = FaroCrashSessionStore.MAX_CONTEXT_AGE_MS
|
|
53
|
+
|
|
54
|
+
assertTrue(FaroCrashReporter.isWithinReplayWindow(nowMs - sevenDays, nowMs))
|
|
55
|
+
assertFalse(FaroCrashReporter.isWithinReplayWindow(nowMs - sevenDays - 1L, nowMs))
|
|
56
|
+
assertFalse(FaroCrashReporter.isWithinReplayWindow(0L, nowMs))
|
|
57
|
+
assertFalse(FaroCrashReporter.isWithinReplayWindow(nowMs + 1L, nowMs))
|
|
58
|
+
}
|
|
59
|
+
}
|