@goliapkg/sentori-react-native 0.8.5 → 0.9.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/android/src/main/java/com/sentori/SentoriMobileVitals.kt +100 -0
- package/android/src/main/java/com/sentori/SentoriModule.kt +18 -0
- package/ios/SentoriMobileVitals.swift +104 -0
- package/ios/SentoriModule.swift +17 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -1
- package/lib/init.d.ts.map +1 -1
- package/lib/init.js +21 -1
- package/lib/init.js.map +1 -1
- package/lib/mobile-vitals.d.ts +35 -0
- package/lib/mobile-vitals.d.ts.map +1 -0
- package/lib/mobile-vitals.js +89 -0
- package/lib/mobile-vitals.js.map +1 -0
- package/lib/native.d.ts +11 -0
- package/lib/native.d.ts.map +1 -1
- package/lib/native.js +37 -0
- package/lib/native.js.map +1 -1
- package/lib/navigation.d.ts.map +1 -1
- package/lib/navigation.js +14 -2
- package/lib/navigation.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +12 -0
- package/src/init.ts +21 -1
- package/src/mobile-vitals.ts +114 -0
- package/src/native.ts +60 -0
- package/src/navigation.ts +16 -2
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
package com.sentori
|
|
2
|
+
|
|
3
|
+
import android.os.Process
|
|
4
|
+
import android.os.SystemClock
|
|
5
|
+
import android.view.Choreographer
|
|
6
|
+
import java.util.concurrent.atomic.AtomicInteger
|
|
7
|
+
import java.util.concurrent.atomic.AtomicLong
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* v0.9.4 #1 — Mobile Vitals.
|
|
11
|
+
*
|
|
12
|
+
* Cold start uses `Process.getStartElapsedRealtime()` (API 24+) —
|
|
13
|
+
* the time the system started the process clock, anchored to boot
|
|
14
|
+
* time. Subtract from current `SystemClock.elapsedRealtime()` at JS
|
|
15
|
+
* bridge ready to get the user-perceived launch budget.
|
|
16
|
+
*
|
|
17
|
+
* Frame counters hook `Choreographer.postFrameCallback` and compare
|
|
18
|
+
* deltas. 16.67ms = slow, 700ms = frozen — same thresholds as iOS
|
|
19
|
+
* for parity. The choreographer callback runs on the UI thread so we
|
|
20
|
+
* don't need extra synchronization for the AtomicInteger counters.
|
|
21
|
+
*/
|
|
22
|
+
object SentoriMobileVitals {
|
|
23
|
+
|
|
24
|
+
private const val SLOW_FRAME_NS: Long = 16_670_000 // 16.67 ms
|
|
25
|
+
private const val FROZEN_FRAME_NS: Long = 700_000_000 // 700 ms
|
|
26
|
+
|
|
27
|
+
private val jsBridgeReadyAt = AtomicLong(0)
|
|
28
|
+
private val coldStartMs = AtomicLong(-1)
|
|
29
|
+
|
|
30
|
+
private val slowFrames = AtomicInteger(0)
|
|
31
|
+
private val frozenFrames = AtomicInteger(0)
|
|
32
|
+
private var lastFrameNs: Long = 0L
|
|
33
|
+
@Volatile private var frameWatchRunning = false
|
|
34
|
+
private var callback: Choreographer.FrameCallback? = null
|
|
35
|
+
|
|
36
|
+
/** Called when JS init runs. Uses Process start elapsed realtime
|
|
37
|
+
* as the anchor; available API 24+. */
|
|
38
|
+
@JvmStatic
|
|
39
|
+
fun markJsBridgeReady() {
|
|
40
|
+
if (jsBridgeReadyAt.get() != 0L) return
|
|
41
|
+
val now = SystemClock.elapsedRealtime()
|
|
42
|
+
jsBridgeReadyAt.set(now)
|
|
43
|
+
try {
|
|
44
|
+
val processStart = Process.getStartElapsedRealtime()
|
|
45
|
+
coldStartMs.set(now - processStart)
|
|
46
|
+
} catch (_: Throwable) {
|
|
47
|
+
// API < 24 — leave -1 sentinel
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@JvmStatic
|
|
52
|
+
fun getColdStartMs(): Long? {
|
|
53
|
+
val v = coldStartMs.get()
|
|
54
|
+
return if (v < 0) null else v
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@JvmStatic
|
|
58
|
+
fun startFrameWatch() {
|
|
59
|
+
if (frameWatchRunning) return
|
|
60
|
+
frameWatchRunning = true
|
|
61
|
+
val cb = object : Choreographer.FrameCallback {
|
|
62
|
+
override fun doFrame(frameTimeNanos: Long) {
|
|
63
|
+
if (!frameWatchRunning) return
|
|
64
|
+
if (lastFrameNs != 0L) {
|
|
65
|
+
val delta = frameTimeNanos - lastFrameNs
|
|
66
|
+
if (delta >= FROZEN_FRAME_NS) {
|
|
67
|
+
frozenFrames.incrementAndGet()
|
|
68
|
+
} else if (delta >= SLOW_FRAME_NS) {
|
|
69
|
+
slowFrames.incrementAndGet()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
lastFrameNs = frameTimeNanos
|
|
73
|
+
Choreographer.getInstance().postFrameCallback(this)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
callback = cb
|
|
77
|
+
// Choreographer must be subscribed on the UI thread. Caller
|
|
78
|
+
// is expected to run this on the main thread (SentoriModule
|
|
79
|
+
// OnCreate runs on the main thread for Expo Modules).
|
|
80
|
+
Choreographer.getInstance().postFrameCallback(cb)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@JvmStatic
|
|
84
|
+
fun stopFrameWatch() {
|
|
85
|
+
frameWatchRunning = false
|
|
86
|
+
callback?.let { Choreographer.getInstance().removeFrameCallback(it) }
|
|
87
|
+
callback = null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@JvmStatic
|
|
91
|
+
fun getFrameCounters(): Map<String, Int> {
|
|
92
|
+
return mapOf("slow" to slowFrames.get(), "frozen" to frozenFrames.get())
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@JvmStatic
|
|
96
|
+
fun resetFrameCounters() {
|
|
97
|
+
slowFrames.set(0)
|
|
98
|
+
frozenFrames.set(0)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -18,6 +18,24 @@ class SentoriModule : Module() {
|
|
|
18
18
|
OnCreate {
|
|
19
19
|
val ctx = appContext.reactContext ?: return@OnCreate
|
|
20
20
|
SentoriCrashHandler.register(ctx)
|
|
21
|
+
// v0.9.4 #1 — start frame watch. Cold-start is captured
|
|
22
|
+
// anchored to Process.getStartElapsedRealtime so no
|
|
23
|
+
// separate registerColdStartAnchor() call is needed.
|
|
24
|
+
SentoriMobileVitals.startFrameWatch()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// v0.9.4 #1 — Mobile Vitals exposure.
|
|
28
|
+
Function("markJsBridgeReady") {
|
|
29
|
+
SentoriMobileVitals.markJsBridgeReady()
|
|
30
|
+
}
|
|
31
|
+
Function("getColdStartMs") {
|
|
32
|
+
SentoriMobileVitals.getColdStartMs()
|
|
33
|
+
}
|
|
34
|
+
Function("getFrameCounters") {
|
|
35
|
+
SentoriMobileVitals.getFrameCounters()
|
|
36
|
+
}
|
|
37
|
+
Function("resetFrameCounters") {
|
|
38
|
+
SentoriMobileVitals.resetFrameCounters()
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
Function("setConfig") { config: Map<String, Any?> ->
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import QuartzCore
|
|
4
|
+
|
|
5
|
+
/// v0.9.4 #1 — Mobile Vitals: cold start measurement + slow/frozen
|
|
6
|
+
/// frame counters.
|
|
7
|
+
///
|
|
8
|
+
/// Cold start is read at `applicationDidFinishLaunching` time. We use
|
|
9
|
+
/// `mach_absolute_time` because it's monotonic + survives clock
|
|
10
|
+
/// adjustments. The JS bridge reads via `getColdStartMs()` once at
|
|
11
|
+
/// `sentori.init` and the value rides along with the first event.
|
|
12
|
+
///
|
|
13
|
+
/// Frame counters hook `CADisplayLink` on the main run loop. The
|
|
14
|
+
/// callback compares actual vs expected timestamp; > 16.67ms = slow
|
|
15
|
+
/// (one missed VSync at 60fps), > 700ms = frozen (per Sentry's
|
|
16
|
+
/// definition for parity). Counters reset on navigation transition
|
|
17
|
+
/// — `resetFrameCounters()` from JS side.
|
|
18
|
+
@objc public final class SentoriMobileVitals: NSObject {
|
|
19
|
+
|
|
20
|
+
private static var coldStartCapturedAt: UInt64 = 0
|
|
21
|
+
private static var jsBridgeReadyAt: UInt64 = 0
|
|
22
|
+
private static var coldStartMs: Double? = nil
|
|
23
|
+
|
|
24
|
+
private static var slowFrames: Int = 0
|
|
25
|
+
private static var frozenFrames: Int = 0
|
|
26
|
+
private static var displayLink: CADisplayLink? = nil
|
|
27
|
+
private static var lastFrameTimestamp: CFTimeInterval = 0
|
|
28
|
+
|
|
29
|
+
private static let SLOW_FRAME_MS: Double = 16.67
|
|
30
|
+
private static let FROZEN_FRAME_MS: Double = 700.0
|
|
31
|
+
|
|
32
|
+
/// Call from app delegate or earliest reachable point. Stores
|
|
33
|
+
/// the cold-start anchor. Safe to call multiple times (only the
|
|
34
|
+
/// first effective time wins).
|
|
35
|
+
@objc public static func registerColdStartAnchor() {
|
|
36
|
+
if coldStartCapturedAt == 0 {
|
|
37
|
+
coldStartCapturedAt = mach_absolute_time()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Called by the bridge when JS init() runs. The delta from the
|
|
42
|
+
/// app-delegate anchor → here is the cold-start budget the
|
|
43
|
+
/// user perceived.
|
|
44
|
+
@objc public static func markJsBridgeReady() {
|
|
45
|
+
if jsBridgeReadyAt != 0 { return }
|
|
46
|
+
jsBridgeReadyAt = mach_absolute_time()
|
|
47
|
+
if coldStartCapturedAt > 0 {
|
|
48
|
+
var info = mach_timebase_info()
|
|
49
|
+
mach_timebase_info(&info)
|
|
50
|
+
let elapsed = (jsBridgeReadyAt - coldStartCapturedAt) * UInt64(info.numer) / UInt64(info.denom)
|
|
51
|
+
// ns → ms
|
|
52
|
+
coldStartMs = Double(elapsed) / 1_000_000.0
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@objc public static func getColdStartMs() -> NSNumber? {
|
|
57
|
+
if let ms = coldStartMs {
|
|
58
|
+
return NSNumber(value: ms)
|
|
59
|
+
}
|
|
60
|
+
return nil
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Start frame budget watch. Idempotent. Hooks CADisplayLink on
|
|
64
|
+
/// the main run loop's common modes so it ticks even during
|
|
65
|
+
/// scroll views.
|
|
66
|
+
@objc public static func startFrameWatch() {
|
|
67
|
+
if displayLink != nil { return }
|
|
68
|
+
DispatchQueue.main.async {
|
|
69
|
+
let link = CADisplayLink(target: self, selector: #selector(onFrame(_:)))
|
|
70
|
+
link.add(to: .main, forMode: .common)
|
|
71
|
+
displayLink = link
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@objc public static func stopFrameWatch() {
|
|
76
|
+
displayLink?.invalidate()
|
|
77
|
+
displayLink = nil
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@objc public static func getFrameCounters() -> NSDictionary? {
|
|
81
|
+
return [
|
|
82
|
+
"slow": slowFrames,
|
|
83
|
+
"frozen": frozenFrames,
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@objc public static func resetFrameCounters() {
|
|
88
|
+
slowFrames = 0
|
|
89
|
+
frozenFrames = 0
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@objc private static func onFrame(_ link: CADisplayLink) {
|
|
93
|
+
let now = link.timestamp
|
|
94
|
+
if lastFrameTimestamp != 0 {
|
|
95
|
+
let deltaMs = (now - lastFrameTimestamp) * 1000.0
|
|
96
|
+
if deltaMs >= FROZEN_FRAME_MS {
|
|
97
|
+
frozenFrames += 1
|
|
98
|
+
} else if deltaMs >= SLOW_FRAME_MS {
|
|
99
|
+
slowFrames += 1
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
lastFrameTimestamp = now
|
|
103
|
+
}
|
|
104
|
+
}
|
package/ios/SentoriModule.swift
CHANGED
|
@@ -14,6 +14,23 @@ public class SentoriModule: Module {
|
|
|
14
14
|
|
|
15
15
|
OnCreate {
|
|
16
16
|
SentoriCrashHandler.register()
|
|
17
|
+
// v0.9.4 #1 — capture cold-start anchor + start frame watch.
|
|
18
|
+
SentoriMobileVitals.registerColdStartAnchor()
|
|
19
|
+
SentoriMobileVitals.startFrameWatch()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// v0.9.4 #1 — Mobile Vitals exposure.
|
|
23
|
+
Function("markJsBridgeReady") {
|
|
24
|
+
SentoriMobileVitals.markJsBridgeReady()
|
|
25
|
+
}
|
|
26
|
+
Function("getColdStartMs") { () -> Double? in
|
|
27
|
+
return SentoriMobileVitals.getColdStartMs()?.doubleValue
|
|
28
|
+
}
|
|
29
|
+
Function("getFrameCounters") { () -> [String: Any]? in
|
|
30
|
+
return SentoriMobileVitals.getFrameCounters() as? [String: Any]
|
|
31
|
+
}
|
|
32
|
+
Function("resetFrameCounters") {
|
|
33
|
+
SentoriMobileVitals.resetFrameCounters()
|
|
17
34
|
}
|
|
18
35
|
|
|
19
36
|
Function("setConfig") { (config: [String: Any]) in
|
package/lib/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ErrorBoundary } from './error-boundary';
|
|
|
2
2
|
import { type FeedbackButtonHandle, type FeedbackButtonProps } from './feedback-widget';
|
|
3
3
|
import { clearMaskQuery, registerMaskQuery } from './mask';
|
|
4
4
|
import { measureFn } from './measure';
|
|
5
|
+
import { getColdStartMs, markTimeToFullDisplay } from './mobile-vitals';
|
|
5
6
|
import { bindState, recordState, unbindState } from './state-snapshots';
|
|
6
7
|
import { startMoment } from '@goliapkg/sentori-core';
|
|
7
8
|
import { flushMetrics, recordMetric } from './metrics';
|
|
@@ -31,6 +32,8 @@ export declare const sentori: {
|
|
|
31
32
|
bindState: typeof bindState;
|
|
32
33
|
recordState: typeof recordState;
|
|
33
34
|
unbindState: typeof unbindState;
|
|
35
|
+
markTimeToFullDisplay: typeof markTimeToFullDisplay;
|
|
36
|
+
getColdStartMs: typeof getColdStartMs;
|
|
34
37
|
setFeatureFlag: (name: string, value: string) => void;
|
|
35
38
|
clearFeatureFlag: (name: string) => void;
|
|
36
39
|
clearAllFeatureFlags: () => void;
|
|
@@ -54,6 +57,7 @@ export { clearAllFeatureFlags, clearFeatureFlag, getFeatureFlags, setFeatureFlag
|
|
|
54
57
|
export { clearMaskQuery, registerMaskQuery } from './mask';
|
|
55
58
|
export { flushMetrics, recordMetric } from './metrics';
|
|
56
59
|
export { measureFn } from './measure';
|
|
60
|
+
export { getColdStartMs, markTimeToFullDisplay, type TimeToFullDisplayHandle, } from './mobile-vitals';
|
|
57
61
|
export { MomentHandle, type MomentProperties, startMoment } from '@goliapkg/sentori-core';
|
|
58
62
|
export { bindState, recordState, type StateSnapshot, unbindState, } from './state-snapshots';
|
|
59
63
|
export { RageTapCapture } from './rage-tap';
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAkB,KAAK,oBAAoB,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAOxG,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAO5C,eAAO,MAAM,OAAO;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAkB,KAAK,oBAAoB,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAOxG,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EACL,cAAc,EACd,qBAAqB,EAEtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAO5C,eAAO,MAAM,OAAO;;;;;;;;;;aA8F0B,CAAC;eAAmB,CAAC;YAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;CAhEnF,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,OAAO,GACR,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxG,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EACL,SAAS,EACT,WAAW,EACX,KAAK,aAAa,EAClB,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAE1E,YAAY,EACV,KAAK,EACL,YAAY,EACZ,KAAK,EACL,UAAU,EACV,cAAc,EACd,MAAM,EACN,QAAQ,EACR,GAAG,EACH,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,QAAQ,GACT,MAAM,SAAS,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { FeedbackButton } from './feedback-widget';
|
|
|
6
6
|
import { clearAllFeatureFlags, clearFeatureFlag, getFeatureFlags, setFeatureFlag, } from './feature-flags';
|
|
7
7
|
import { clearMaskQuery, registerMaskQuery } from './mask';
|
|
8
8
|
import { measureFn } from './measure';
|
|
9
|
+
import { getColdStartMs, markTimeToFullDisplay, } from './mobile-vitals';
|
|
9
10
|
import { bindState, recordState, unbindState } from './state-snapshots';
|
|
10
11
|
import { startMoment } from '@goliapkg/sentori-core';
|
|
11
12
|
import { flushMetrics, recordMetric } from './metrics';
|
|
@@ -27,6 +28,8 @@ export const sentori = {
|
|
|
27
28
|
bindState,
|
|
28
29
|
recordState,
|
|
29
30
|
unbindState,
|
|
31
|
+
markTimeToFullDisplay,
|
|
32
|
+
getColdStartMs,
|
|
30
33
|
setFeatureFlag,
|
|
31
34
|
clearFeatureFlag,
|
|
32
35
|
clearAllFeatureFlags,
|
|
@@ -50,6 +53,7 @@ export { clearAllFeatureFlags, clearFeatureFlag, getFeatureFlags, setFeatureFlag
|
|
|
50
53
|
export { clearMaskQuery, registerMaskQuery } from './mask';
|
|
51
54
|
export { flushMetrics, recordMetric } from './metrics';
|
|
52
55
|
export { measureFn } from './measure';
|
|
56
|
+
export { getColdStartMs, markTimeToFullDisplay, } from './mobile-vitals';
|
|
53
57
|
export { MomentHandle, startMoment } from '@goliapkg/sentori-core';
|
|
54
58
|
export { bindState, recordState, unbindState, } from './state-snapshots';
|
|
55
59
|
export { RageTapCapture } from './rage-tap';
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,OAAO,GACR,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAuD,MAAM,mBAAmB,CAAC;AACxG,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAE3B,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,IAAI;IACJ,aAAa;IACb,OAAO;IACP,OAAO;IACP,YAAY;IACZ,gBAAgB;IAChB,WAAW;IACX,gBAAgB;IAChB,YAAY;IACZ,YAAY;IACZ,SAAS;IACT,WAAW;IACX,SAAS;IACT,WAAW;IACX,WAAW;IACX,cAAc;IACd,gBAAgB;IAChB,oBAAoB;IACpB,eAAe;IACf,aAAa;IACb,cAAc;IACd,cAAc;IACd,iBAAiB;IACjB,cAAc;IACd,YAAY;IACZ,UAAU;IACV,kBAAkB;CACnB,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,OAAO,GACR,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAuD,MAAM,mBAAmB,CAAC;AACxG,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAyB,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EACL,SAAS,EACT,WAAW,EAEX,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAA0B,kBAAkB,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAeA,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAQnF,OAAO,KAAK,EAAkB,cAAc,EAA2B,MAAM,SAAS,CAAC;AAIvF,MAAM,MAAM,WAAW,GAAG;IACxB,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,OAAO,CAAC,EAAE;QACR,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,OAAO,CAAC,EACJ,OAAO,GACP;YACE;;qEAEyD;YACzD,OAAO,CAAC,EAAE,OAAO,CAAC;SACnB,CAAC;QACN;;8DAEsD;QACtD,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB;;;;;;;uBAOe;QACf,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB;;;6CAGqC;QACrC,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB;;;;6CAIqC;QACrC,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;QACrC;;;sEAG8D;QAC9D,gBAAgB,CAAC,EAAE;YACjB,OAAO,EAAE,OAAO,CAAC;YACjB,qBAAqB,CAAC,EAAE,CACtB,IAAI,EAAE,OAAO,sBAAsB,EAAE,eAAe,KAElD,OAAO,sBAAsB,EAAE,iBAAiB,GAChD,OAAO,CAAC,OAAO,sBAAsB,EAAE,iBAAiB,CAAC,CAAC;YAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,SAAS,CAAC,EAAE,MAAM,CAAC;SACpB,CAAC;KACH,CAAC;IACF;;;;;gEAK4D;IAC5D,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;QACvB,MAAM,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;KACxB,CAAC;CACH,CAAC;AAIF,eAAO,MAAM,IAAI,GAAI,SAAS,WAAW,KAAG,IA8I3C,CAAC;AAiBF,YAAY,EAAE,cAAc,EAAE,CAAC"}
|
package/lib/init.js
CHANGED
|
@@ -6,7 +6,9 @@ import { installNetworkHandler } from './handlers/network';
|
|
|
6
6
|
import { getBundleInfo } from './bundle-info';
|
|
7
7
|
import { markLaunchCompleted, runLaunchCrashGuard, } from './launch-crash-guard';
|
|
8
8
|
import { startMetricsTimer } from './metrics';
|
|
9
|
-
import { drainNativePending, setNativeConfig } from './native';
|
|
9
|
+
import { drainNativePending, markNativeJsBridgeReady, setNativeConfig } from './native';
|
|
10
|
+
import { getColdStartMs } from './mobile-vitals';
|
|
11
|
+
import { startSpan } from '@goliapkg/sentori-core';
|
|
10
12
|
import { startNetworkTypeWatch } from './netinfo';
|
|
11
13
|
import { startPreCrashSentinel } from './pre-crash-sentinel';
|
|
12
14
|
import { startSession } from './session-tracker';
|
|
@@ -47,6 +49,24 @@ export const init = (options) => {
|
|
|
47
49
|
release: options.release,
|
|
48
50
|
environment: env,
|
|
49
51
|
});
|
|
52
|
+
// v0.9.4 #1 — finalize cold-start measurement. iOS uses the
|
|
53
|
+
// delta from `applicationDidFinishLaunching` to this call;
|
|
54
|
+
// Android uses Process.getStartElapsedRealtime() so the value is
|
|
55
|
+
// computed at this point and cached.
|
|
56
|
+
markNativeJsBridgeReady();
|
|
57
|
+
// Emit a one-off cold-start span. Server aggregates these per
|
|
58
|
+
// release for the Mobile Vitals dashboard. No-op when native
|
|
59
|
+
// module isn't linked.
|
|
60
|
+
const coldMs = getColdStartMs();
|
|
61
|
+
if (coldMs !== null && coldMs > 0 && coldMs < 60_000) {
|
|
62
|
+
const span = startSpan('sentori.cold_start', {
|
|
63
|
+
name: 'cold-start',
|
|
64
|
+
parent: null,
|
|
65
|
+
startNowMs: Date.now() - coldMs,
|
|
66
|
+
tags: { 'vital.kind': 'cold_start' },
|
|
67
|
+
});
|
|
68
|
+
span.finish({ status: 'ok' });
|
|
69
|
+
}
|
|
50
70
|
startTransport();
|
|
51
71
|
// v0.8.0-c — start watching network class. No-op if NetInfo isn't
|
|
52
72
|
// installed; events just won't carry `device.networkType` in that
|
package/lib/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Read the native-side cold start measurement once. Cached. Returns
|
|
2
|
+
* null when the native module isn't linked (Expo Go / tests). */
|
|
3
|
+
export declare function getColdStartMs(): null | number;
|
|
4
|
+
/**
|
|
5
|
+
* v0.9.4 #1 — Time-To-Full-Display marker. Host calls this at the
|
|
6
|
+
* point the screen is functionally "ready" (data fetched, images
|
|
7
|
+
* loaded, etc.) so the dashboard can show real perceived load time
|
|
8
|
+
* vs auto-detected TTID.
|
|
9
|
+
*
|
|
10
|
+
* const h = sentori.markTimeToFullDisplay('Home');
|
|
11
|
+
* // ...data fetched, images rendered...
|
|
12
|
+
* h.end();
|
|
13
|
+
*
|
|
14
|
+
* If `.end()` is never called, the handle's span is finished as
|
|
15
|
+
* `cancelled` at the next `markTimeToFullDisplay` call (one TTFD in
|
|
16
|
+
* flight at a time is the typical case).
|
|
17
|
+
*/
|
|
18
|
+
export type TimeToFullDisplayHandle = {
|
|
19
|
+
end: (opts?: {
|
|
20
|
+
status?: 'cancelled' | 'error' | 'ok';
|
|
21
|
+
}) => void;
|
|
22
|
+
cancel: () => void;
|
|
23
|
+
};
|
|
24
|
+
export declare function markTimeToFullDisplay(route: string): TimeToFullDisplayHandle;
|
|
25
|
+
/** v0.9.4 #1 — read the per-screen slow/frozen frame counts since
|
|
26
|
+
* the most recent navigation transition. Native module reads
|
|
27
|
+
* CADisplayLink / Choreographer counters; returns null when not
|
|
28
|
+
* linked. */
|
|
29
|
+
export declare function getFrameCounters(): null | {
|
|
30
|
+
slow: number;
|
|
31
|
+
frozen: number;
|
|
32
|
+
};
|
|
33
|
+
/** Test-only. */
|
|
34
|
+
export declare function __resetMobileVitalsForTests(): void;
|
|
35
|
+
//# sourceMappingURL=mobile-vitals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile-vitals.d.ts","sourceRoot":"","sources":["../src/mobile-vitals.ts"],"names":[],"mappings":"AA6BA;kEACkE;AAClE,wBAAgB,cAAc,IAAI,IAAI,GAAG,MAAM,CAS9C;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,CAAC;IAChE,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAOF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB,CAqB5E;AAED;;;cAGc;AACd,wBAAgB,gBAAgB,IAAI,IAAI,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAY1E;AAED,iBAAiB;AACjB,wBAAgB,2BAA2B,IAAI,IAAI,CAKlD"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// v0.9.4 #1 — Mobile Vitals.
|
|
2
|
+
//
|
|
3
|
+
// Three measurements, three call paths, one server schema:
|
|
4
|
+
//
|
|
5
|
+
// • Cold start: native side measures at app launch (iOS
|
|
6
|
+
// mach_absolute_time / Android Process.getStartElapsedRealtime)
|
|
7
|
+
// and exposes via the bundled native module — read once on JS
|
|
8
|
+
// side at init() and ride along on the first event.
|
|
9
|
+
// • TTID (Time-To-Initial-Display): automatic via
|
|
10
|
+
// useTraceNavigation extension — span from navigation.dispatch
|
|
11
|
+
// to first frame after route mount.
|
|
12
|
+
// • TTFD (Time-To-Full-Display): manual. Host calls
|
|
13
|
+
// sentori.markTimeToFullDisplay('Home').end() when the screen's
|
|
14
|
+
// data has loaded.
|
|
15
|
+
// • Slow / frozen frame counts: native side hooks CADisplayLink /
|
|
16
|
+
// Choreographer.FrameCallback; counters flush per navigation
|
|
17
|
+
// span.
|
|
18
|
+
//
|
|
19
|
+
// JS-side first: TTFD API + bundle-level vital captures. Native
|
|
20
|
+
// pieces ship as a separate native module method `getColdStartMs`
|
|
21
|
+
// + `getFrameCounts()` — graceful no-op if not linked.
|
|
22
|
+
import { startSpan } from '@goliapkg/sentori-core';
|
|
23
|
+
import { getNativeColdStartMs } from './native';
|
|
24
|
+
let _coldStartMs = null;
|
|
25
|
+
let _coldStartCaptured = false;
|
|
26
|
+
/** Read the native-side cold start measurement once. Cached. Returns
|
|
27
|
+
* null when the native module isn't linked (Expo Go / tests). */
|
|
28
|
+
export function getColdStartMs() {
|
|
29
|
+
if (_coldStartCaptured)
|
|
30
|
+
return _coldStartMs;
|
|
31
|
+
_coldStartCaptured = true;
|
|
32
|
+
try {
|
|
33
|
+
_coldStartMs = getNativeColdStartMs();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
_coldStartMs = null;
|
|
37
|
+
}
|
|
38
|
+
return _coldStartMs;
|
|
39
|
+
}
|
|
40
|
+
let _activeTtfd = null;
|
|
41
|
+
export function markTimeToFullDisplay(route) {
|
|
42
|
+
if (_activeTtfd && _activeTtfd.route !== route) {
|
|
43
|
+
_activeTtfd.finish('cancelled');
|
|
44
|
+
}
|
|
45
|
+
const span = startSpan('react.navigation.ttfd', {
|
|
46
|
+
name: route,
|
|
47
|
+
tags: { 'nav.route': route, 'vital.kind': 'ttfd' },
|
|
48
|
+
});
|
|
49
|
+
let finished = false;
|
|
50
|
+
const finish = (status) => {
|
|
51
|
+
if (finished)
|
|
52
|
+
return;
|
|
53
|
+
finished = true;
|
|
54
|
+
span.finish({ status });
|
|
55
|
+
if (_activeTtfd && _activeTtfd.route === route)
|
|
56
|
+
_activeTtfd = null;
|
|
57
|
+
};
|
|
58
|
+
_activeTtfd = { finish, route };
|
|
59
|
+
return {
|
|
60
|
+
cancel: () => finish('cancelled'),
|
|
61
|
+
end: (opts) => finish(opts?.status ?? 'ok'),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** v0.9.4 #1 — read the per-screen slow/frozen frame counts since
|
|
65
|
+
* the most recent navigation transition. Native module reads
|
|
66
|
+
* CADisplayLink / Choreographer counters; returns null when not
|
|
67
|
+
* linked. */
|
|
68
|
+
export function getFrameCounters() {
|
|
69
|
+
try {
|
|
70
|
+
// native binding lazily required in native.ts
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
72
|
+
const nativeMod = require('./native');
|
|
73
|
+
if (typeof nativeMod.getNativeFrameCounters !== 'function')
|
|
74
|
+
return null;
|
|
75
|
+
return nativeMod.getNativeFrameCounters();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Test-only. */
|
|
82
|
+
export function __resetMobileVitalsForTests() {
|
|
83
|
+
if (_activeTtfd)
|
|
84
|
+
_activeTtfd.finish('cancelled');
|
|
85
|
+
_activeTtfd = null;
|
|
86
|
+
_coldStartCaptured = false;
|
|
87
|
+
_coldStartMs = null;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=mobile-vitals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile-vitals.js","sourceRoot":"","sources":["../src/mobile-vitals.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,EAAE;AACF,2DAA2D;AAC3D,EAAE;AACF,0DAA0D;AAC1D,oEAAoE;AACpE,kEAAkE;AAClE,wDAAwD;AACxD,oDAAoD;AACpD,mEAAmE;AACnE,wCAAwC;AACxC,sDAAsD;AACtD,oEAAoE;AACpE,uBAAuB;AACvB,oEAAoE;AACpE,iEAAiE;AACjE,YAAY;AACZ,EAAE;AACF,gEAAgE;AAChE,kEAAkE;AAClE,uDAAuD;AAEvD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAEhD,IAAI,YAAY,GAAkB,IAAI,CAAC;AACvC,IAAI,kBAAkB,GAAG,KAAK,CAAC;AAE/B;kEACkE;AAClE,MAAM,UAAU,cAAc;IAC5B,IAAI,kBAAkB;QAAE,OAAO,YAAY,CAAC;IAC5C,kBAAkB,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC;QACH,YAAY,GAAG,oBAAoB,EAAE,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,YAAY,GAAG,IAAI,CAAC;IACtB,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAqBD,IAAI,WAAW,GAGX,IAAI,CAAC;AAET,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,IAAI,WAAW,IAAI,WAAW,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QAC/C,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;IACD,MAAM,IAAI,GAAG,SAAS,CAAC,uBAAuB,EAAE;QAC9C,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE;KACnD,CAAC,CAAC;IACH,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,MAAM,GAAG,CAAC,MAAoC,EAAQ,EAAE;QAC5D,IAAI,QAAQ;YAAE,OAAO;QACrB,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QACxB,IAAI,WAAW,IAAI,WAAW,CAAC,KAAK,KAAK,KAAK;YAAE,WAAW,GAAG,IAAI,CAAC;IACrE,CAAC,CAAC;IACF,WAAW,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAChC,OAAO;QACL,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC;QACjC,GAAG,EAAE,CAAC,IAAgD,EAAE,EAAE,CACxD,MAAM,CAAC,IAAI,EAAE,MAAM,IAAI,IAAI,CAAC;KAC/B,CAAC;AACJ,CAAC;AAED;;;cAGc;AACd,MAAM,UAAU,gBAAgB;IAC9B,IAAI,CAAC;QACH,8CAA8C;QAC9C,iEAAiE;QACjE,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAEnC,CAAC;QACF,IAAI,OAAO,SAAS,CAAC,sBAAsB,KAAK,UAAU;YAAE,OAAO,IAAI,CAAC;QACxE,OAAO,SAAS,CAAC,sBAAsB,EAAE,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,iBAAiB;AACjB,MAAM,UAAU,2BAA2B;IACzC,IAAI,WAAW;QAAE,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACjD,WAAW,GAAG,IAAI,CAAC;IACnB,kBAAkB,GAAG,KAAK,CAAC;IAC3B,YAAY,GAAG,IAAI,CAAC;AACtB,CAAC"}
|
package/lib/native.d.ts
CHANGED
|
@@ -37,6 +37,17 @@ export declare function startAnrWatchdog(options?: {
|
|
|
37
37
|
timeoutMs?: number;
|
|
38
38
|
}): void;
|
|
39
39
|
export declare function stopAnrWatchdog(): void;
|
|
40
|
+
/** v0.9.4 #1 — finalize cold-start measurement. Idempotent. */
|
|
41
|
+
export declare function markNativeJsBridgeReady(): void;
|
|
42
|
+
/** v0.9.4 #1 — read cold start ms once. null when native unavailable. */
|
|
43
|
+
export declare function getNativeColdStartMs(): null | number;
|
|
44
|
+
/** v0.9.4 #1 — read slow/frozen frame counters since last reset. */
|
|
45
|
+
export declare function getNativeFrameCounters(): null | {
|
|
46
|
+
frozen: number;
|
|
47
|
+
slow: number;
|
|
48
|
+
};
|
|
49
|
+
/** v0.9.4 #1 — reset frame counters on navigation transition. */
|
|
50
|
+
export declare function resetNativeFrameCounters(): void;
|
|
40
51
|
/**
|
|
41
52
|
* v0.7.3 — drives the native screenshot path. JS side passes the
|
|
42
53
|
* current list of mask `nativeID`s (read from the consumer's
|
package/lib/native.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA6EH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd,GAAG,IAAI,CAMP;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAMzC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE;IACzC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GAAG,IAAI,CAMP;AAED,wBAAgB,eAAe,IAAI,IAAI,CAMtC;AAED,+DAA+D;AAC/D,wBAAgB,uBAAuB,IAAI,IAAI,CAM9C;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,IAAI,IAAI,GAAG,MAAM,CAOpD;AAED,oEAAoE;AACpE,wBAAgB,sBAAsB,IAAI,IAAI,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAMhF;AAED,iEAAiE;AACjE,wBAAgB,wBAAwB,IAAI,IAAI,CAM/C;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,+BAA+B,CACnD,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,IAAI,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAQvD"}
|
package/lib/native.js
CHANGED
|
@@ -80,6 +80,43 @@ export function stopAnrWatchdog() {
|
|
|
80
80
|
// ignore
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
+
/** v0.9.4 #1 — finalize cold-start measurement. Idempotent. */
|
|
84
|
+
export function markNativeJsBridgeReady() {
|
|
85
|
+
try {
|
|
86
|
+
native()?.markJsBridgeReady?.();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// ignore
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** v0.9.4 #1 — read cold start ms once. null when native unavailable. */
|
|
93
|
+
export function getNativeColdStartMs() {
|
|
94
|
+
try {
|
|
95
|
+
const v = native()?.getColdStartMs?.();
|
|
96
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : null;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** v0.9.4 #1 — read slow/frozen frame counters since last reset. */
|
|
103
|
+
export function getNativeFrameCounters() {
|
|
104
|
+
try {
|
|
105
|
+
return native()?.getFrameCounters?.() ?? null;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** v0.9.4 #1 — reset frame counters on navigation transition. */
|
|
112
|
+
export function resetNativeFrameCounters() {
|
|
113
|
+
try {
|
|
114
|
+
native()?.resetFrameCounters?.();
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// ignore
|
|
118
|
+
}
|
|
119
|
+
}
|
|
83
120
|
/**
|
|
84
121
|
* v0.7.3 — drives the native screenshot path. JS side passes the
|
|
85
122
|
* current list of mask `nativeID`s (read from the consumer's
|
package/lib/native.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"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"}
|
package/lib/navigation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAgCA;;kDAEkD;AAClD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAClE,eAAe,EAAE,MAAM;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CACrD,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,iBAAiB,GAAG,IAAI,CA2FzE"}
|
package/lib/navigation.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
// `startSpan(op, { parent: activeSpan() })`.
|
|
22
22
|
import { useEffect, useRef } from 'react';
|
|
23
23
|
import { setActiveSpan, startSpan } from '@goliapkg/sentori-core';
|
|
24
|
+
import { getNativeFrameCounters, resetNativeFrameCounters, } from './native';
|
|
24
25
|
import { captureStep } from './trail';
|
|
25
26
|
/**
|
|
26
27
|
* Subscribe to react-navigation state changes and emit a
|
|
@@ -85,11 +86,22 @@ export function useTraceNavigation(navigationRef) {
|
|
|
85
86
|
if (!span)
|
|
86
87
|
return null;
|
|
87
88
|
const dwellMs = enteredAt !== null ? Math.max(0, Date.now() - enteredAt) : null;
|
|
89
|
+
// v0.9.4 #1 — drain native frame counters at screen-leave.
|
|
90
|
+
// Empty/null when native module not linked; tags omitted.
|
|
91
|
+
const fc = getNativeFrameCounters();
|
|
92
|
+
const finishTags = {};
|
|
93
|
+
if (dwellMs !== null)
|
|
94
|
+
finishTags['nav.dwell_ms'] = String(dwellMs);
|
|
95
|
+
if (fc) {
|
|
96
|
+
finishTags['vital.slow_frames'] = String(fc.slow);
|
|
97
|
+
finishTags['vital.frozen_frames'] = String(fc.frozen);
|
|
98
|
+
}
|
|
88
99
|
span.finish({
|
|
89
100
|
status: 'ok',
|
|
90
|
-
|
|
91
|
-
tags: dwellMs !== null ? { 'nav.dwell_ms': String(dwellMs) } : undefined,
|
|
101
|
+
tags: Object.keys(finishTags).length > 0 ? finishTags : undefined,
|
|
92
102
|
});
|
|
103
|
+
// Reset counters for the next screen.
|
|
104
|
+
resetNativeFrameCounters();
|
|
93
105
|
return dwellMs;
|
|
94
106
|
};
|
|
95
107
|
// Open a span for the initial screen so its requests are grouped
|
package/lib/navigation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,oEAAoE;AACpE,0DAA0D;AAC1D,qEAAqE;AACrE,iEAAiE;AACjE,EAAE;AACF,yDAAyD;AACzD,8DAA8D;AAC9D,mEAAmE;AACnE,4CAA4C;AAC5C,EAAE;AACF,oEAAoE;AACpE,gEAAgE;AAChE,mEAAmE;AACnE,kEAAkE;AAClE,EAAE;AACF,kEAAkE;AAClE,qEAAqE;AACrE,sEAAsE;AACtE,gEAAgE;AAChE,6CAA6C;AAE7C,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE1C,OAAO,EAAE,aAAa,EAAE,SAAS,EAAmB,MAAM,wBAAwB,CAAC;AAEnF,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAUtC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAAC,aAAgC;IACjE,oCAAoC;IACpC,MAAM,YAAY,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IACjD,oEAAoE;IACpE,kEAAkE;IAClE,MAAM,qBAAqB,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IAC1D,kEAAkE;IAClE,0CAA0C;IAC1C,MAAM,WAAW,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAEpD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,aAAa,CAAC,WAAW,KAAK,UAAU;YAAE,OAAO;QAC5D,IAAI,OAAO,aAAa,CAAC,eAAe,KAAK,UAAU;YAAE,OAAO;QAEhE,iEAAiE;QACjE,4DAA4D;QAC5D,mDAAmD;QACnD,MAAM,cAAc,GAAG,CACrB,IAAmB,EACnB,EAAU,EACV,WAA0B,EAC1B,EAAE;YACF,MAAM,IAAI,GAAG,SAAS,CAAC,kBAAkB,EAAE;gBACzC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE;gBACnC,MAAM,EAAE,IAAI;gBACZ,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;aAC/C,CAAC,CAAC;YACH,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;YAC3B,aAAa,CAAC,IAAI,CAAC,CAAC;YACpB,YAAY,CAAC,OAAO,GAAG,EAAE,CAAC;YAC1B,qBAAqB,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3C,0DAA0D;YAC1D,yDAAyD;YACzD,6DAA6D;YAC7D,kEAAkE;YAClE,wDAAwD;YACxD,WAAW,CAAC,UAAU,EAAE,EAAE,EAAE;gBAC1B,UAAU,EAAE;oBACV,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS;oBACrE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE;oBACtC,IAAI,EAAE,YAAY;iBACnB;aACF,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,MAAM,uBAAuB,GAAG,GAAkB,EAAE;YAClD,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC;YACjC,MAAM,SAAS,GAAG,qBAAqB,CAAC,OAAO,CAAC;YAChD,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,MAAM,OAAO,GAAG,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAChF,
|
|
1
|
+
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goliapkg/sentori-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Sentori SDK for React Native \u2014 JS-layer error capture, native crash handlers (iOS / Android), batched transport, fetch + react-navigation tracing.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://sentori.golia.jp",
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,11 @@ import {
|
|
|
18
18
|
} from './feature-flags';
|
|
19
19
|
import { clearMaskQuery, registerMaskQuery } from './mask';
|
|
20
20
|
import { measureFn } from './measure';
|
|
21
|
+
import {
|
|
22
|
+
getColdStartMs,
|
|
23
|
+
markTimeToFullDisplay,
|
|
24
|
+
type TimeToFullDisplayHandle,
|
|
25
|
+
} from './mobile-vitals';
|
|
21
26
|
import { bindState, recordState, unbindState } from './state-snapshots';
|
|
22
27
|
import { startMoment } from '@goliapkg/sentori-core';
|
|
23
28
|
import { flushMetrics, recordMetric } from './metrics';
|
|
@@ -44,6 +49,8 @@ export const sentori = {
|
|
|
44
49
|
bindState,
|
|
45
50
|
recordState,
|
|
46
51
|
unbindState,
|
|
52
|
+
markTimeToFullDisplay,
|
|
53
|
+
getColdStartMs,
|
|
47
54
|
setFeatureFlag,
|
|
48
55
|
clearFeatureFlag,
|
|
49
56
|
clearAllFeatureFlags,
|
|
@@ -81,6 +88,11 @@ export {
|
|
|
81
88
|
export { clearMaskQuery, registerMaskQuery } from './mask';
|
|
82
89
|
export { flushMetrics, recordMetric } from './metrics';
|
|
83
90
|
export { measureFn } from './measure';
|
|
91
|
+
export {
|
|
92
|
+
getColdStartMs,
|
|
93
|
+
markTimeToFullDisplay,
|
|
94
|
+
type TimeToFullDisplayHandle,
|
|
95
|
+
} from './mobile-vitals';
|
|
84
96
|
export { MomentHandle, type MomentProperties, startMoment } from '@goliapkg/sentori-core';
|
|
85
97
|
export {
|
|
86
98
|
bindState,
|
package/src/init.ts
CHANGED
|
@@ -9,7 +9,9 @@ import {
|
|
|
9
9
|
runLaunchCrashGuard,
|
|
10
10
|
} from './launch-crash-guard';
|
|
11
11
|
import { startMetricsTimer } from './metrics';
|
|
12
|
-
import { drainNativePending, setNativeConfig } from './native';
|
|
12
|
+
import { drainNativePending, markNativeJsBridgeReady, setNativeConfig } from './native';
|
|
13
|
+
import { getColdStartMs } from './mobile-vitals';
|
|
14
|
+
import { startSpan } from '@goliapkg/sentori-core';
|
|
13
15
|
import { startNetworkTypeWatch } from './netinfo';
|
|
14
16
|
import { startPreCrashSentinel, type PreCrashChannel } from './pre-crash-sentinel';
|
|
15
17
|
import { startSession } from './session-tracker';
|
|
@@ -142,6 +144,24 @@ export const init = (options: InitOptions): void => {
|
|
|
142
144
|
release: options.release,
|
|
143
145
|
environment: env,
|
|
144
146
|
});
|
|
147
|
+
// v0.9.4 #1 — finalize cold-start measurement. iOS uses the
|
|
148
|
+
// delta from `applicationDidFinishLaunching` to this call;
|
|
149
|
+
// Android uses Process.getStartElapsedRealtime() so the value is
|
|
150
|
+
// computed at this point and cached.
|
|
151
|
+
markNativeJsBridgeReady();
|
|
152
|
+
// Emit a one-off cold-start span. Server aggregates these per
|
|
153
|
+
// release for the Mobile Vitals dashboard. No-op when native
|
|
154
|
+
// module isn't linked.
|
|
155
|
+
const coldMs = getColdStartMs();
|
|
156
|
+
if (coldMs !== null && coldMs > 0 && coldMs < 60_000) {
|
|
157
|
+
const span = startSpan('sentori.cold_start', {
|
|
158
|
+
name: 'cold-start',
|
|
159
|
+
parent: null,
|
|
160
|
+
startNowMs: Date.now() - coldMs,
|
|
161
|
+
tags: { 'vital.kind': 'cold_start' },
|
|
162
|
+
});
|
|
163
|
+
span.finish({ status: 'ok' });
|
|
164
|
+
}
|
|
145
165
|
|
|
146
166
|
startTransport();
|
|
147
167
|
// v0.8.0-c — start watching network class. No-op if NetInfo isn't
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// v0.9.4 #1 — Mobile Vitals.
|
|
2
|
+
//
|
|
3
|
+
// Three measurements, three call paths, one server schema:
|
|
4
|
+
//
|
|
5
|
+
// • Cold start: native side measures at app launch (iOS
|
|
6
|
+
// mach_absolute_time / Android Process.getStartElapsedRealtime)
|
|
7
|
+
// and exposes via the bundled native module — read once on JS
|
|
8
|
+
// side at init() and ride along on the first event.
|
|
9
|
+
// • TTID (Time-To-Initial-Display): automatic via
|
|
10
|
+
// useTraceNavigation extension — span from navigation.dispatch
|
|
11
|
+
// to first frame after route mount.
|
|
12
|
+
// • TTFD (Time-To-Full-Display): manual. Host calls
|
|
13
|
+
// sentori.markTimeToFullDisplay('Home').end() when the screen's
|
|
14
|
+
// data has loaded.
|
|
15
|
+
// • Slow / frozen frame counts: native side hooks CADisplayLink /
|
|
16
|
+
// Choreographer.FrameCallback; counters flush per navigation
|
|
17
|
+
// span.
|
|
18
|
+
//
|
|
19
|
+
// JS-side first: TTFD API + bundle-level vital captures. Native
|
|
20
|
+
// pieces ship as a separate native module method `getColdStartMs`
|
|
21
|
+
// + `getFrameCounts()` — graceful no-op if not linked.
|
|
22
|
+
|
|
23
|
+
import { startSpan } from '@goliapkg/sentori-core';
|
|
24
|
+
|
|
25
|
+
import { getNativeColdStartMs } from './native';
|
|
26
|
+
|
|
27
|
+
let _coldStartMs: null | number = null;
|
|
28
|
+
let _coldStartCaptured = false;
|
|
29
|
+
|
|
30
|
+
/** Read the native-side cold start measurement once. Cached. Returns
|
|
31
|
+
* null when the native module isn't linked (Expo Go / tests). */
|
|
32
|
+
export function getColdStartMs(): null | number {
|
|
33
|
+
if (_coldStartCaptured) return _coldStartMs;
|
|
34
|
+
_coldStartCaptured = true;
|
|
35
|
+
try {
|
|
36
|
+
_coldStartMs = getNativeColdStartMs();
|
|
37
|
+
} catch {
|
|
38
|
+
_coldStartMs = null;
|
|
39
|
+
}
|
|
40
|
+
return _coldStartMs;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* v0.9.4 #1 — Time-To-Full-Display marker. Host calls this at the
|
|
45
|
+
* point the screen is functionally "ready" (data fetched, images
|
|
46
|
+
* loaded, etc.) so the dashboard can show real perceived load time
|
|
47
|
+
* vs auto-detected TTID.
|
|
48
|
+
*
|
|
49
|
+
* const h = sentori.markTimeToFullDisplay('Home');
|
|
50
|
+
* // ...data fetched, images rendered...
|
|
51
|
+
* h.end();
|
|
52
|
+
*
|
|
53
|
+
* If `.end()` is never called, the handle's span is finished as
|
|
54
|
+
* `cancelled` at the next `markTimeToFullDisplay` call (one TTFD in
|
|
55
|
+
* flight at a time is the typical case).
|
|
56
|
+
*/
|
|
57
|
+
export type TimeToFullDisplayHandle = {
|
|
58
|
+
end: (opts?: { status?: 'cancelled' | 'error' | 'ok' }) => void;
|
|
59
|
+
cancel: () => void;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
let _activeTtfd: null | {
|
|
63
|
+
finish: (status: 'cancelled' | 'error' | 'ok') => void;
|
|
64
|
+
route: string;
|
|
65
|
+
} = null;
|
|
66
|
+
|
|
67
|
+
export function markTimeToFullDisplay(route: string): TimeToFullDisplayHandle {
|
|
68
|
+
if (_activeTtfd && _activeTtfd.route !== route) {
|
|
69
|
+
_activeTtfd.finish('cancelled');
|
|
70
|
+
}
|
|
71
|
+
const span = startSpan('react.navigation.ttfd', {
|
|
72
|
+
name: route,
|
|
73
|
+
tags: { 'nav.route': route, 'vital.kind': 'ttfd' },
|
|
74
|
+
});
|
|
75
|
+
let finished = false;
|
|
76
|
+
const finish = (status: 'cancelled' | 'error' | 'ok'): void => {
|
|
77
|
+
if (finished) return;
|
|
78
|
+
finished = true;
|
|
79
|
+
span.finish({ status });
|
|
80
|
+
if (_activeTtfd && _activeTtfd.route === route) _activeTtfd = null;
|
|
81
|
+
};
|
|
82
|
+
_activeTtfd = { finish, route };
|
|
83
|
+
return {
|
|
84
|
+
cancel: () => finish('cancelled'),
|
|
85
|
+
end: (opts?: { status?: 'cancelled' | 'error' | 'ok' }) =>
|
|
86
|
+
finish(opts?.status ?? 'ok'),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** v0.9.4 #1 — read the per-screen slow/frozen frame counts since
|
|
91
|
+
* the most recent navigation transition. Native module reads
|
|
92
|
+
* CADisplayLink / Choreographer counters; returns null when not
|
|
93
|
+
* linked. */
|
|
94
|
+
export function getFrameCounters(): null | { slow: number; frozen: number } {
|
|
95
|
+
try {
|
|
96
|
+
// native binding lazily required in native.ts
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
98
|
+
const nativeMod = require('./native') as {
|
|
99
|
+
getNativeFrameCounters?: () => null | { frozen: number; slow: number };
|
|
100
|
+
};
|
|
101
|
+
if (typeof nativeMod.getNativeFrameCounters !== 'function') return null;
|
|
102
|
+
return nativeMod.getNativeFrameCounters();
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Test-only. */
|
|
109
|
+
export function __resetMobileVitalsForTests(): void {
|
|
110
|
+
if (_activeTtfd) _activeTtfd.finish('cancelled');
|
|
111
|
+
_activeTtfd = null;
|
|
112
|
+
_coldStartCaptured = false;
|
|
113
|
+
_coldStartMs = null;
|
|
114
|
+
}
|
package/src/native.ts
CHANGED
|
@@ -11,6 +11,29 @@ type SentoriNativeModule = {
|
|
|
11
11
|
release: string
|
|
12
12
|
token: string
|
|
13
13
|
}) => void
|
|
14
|
+
/**
|
|
15
|
+
* v0.9.4 #1 — cold start measurement. iOS:
|
|
16
|
+
* `mach_absolute_time` from `applicationDidFinishLaunching` to first
|
|
17
|
+
* JS bridge ready. Android: `Process.getStartElapsedRealtime()`.
|
|
18
|
+
* Returns null when native side hasn't captured yet.
|
|
19
|
+
*/
|
|
20
|
+
getColdStartMs?: () => null | number
|
|
21
|
+
/**
|
|
22
|
+
* v0.9.4 #1 — call once at JS init() to finalize the cold-start
|
|
23
|
+
* measurement. iOS subtracts from the app-delegate anchor;
|
|
24
|
+
* Android uses Process.getStartElapsedRealtime() so the call is
|
|
25
|
+
* idempotent if missed.
|
|
26
|
+
*/
|
|
27
|
+
markJsBridgeReady?: () => void
|
|
28
|
+
/**
|
|
29
|
+
* v0.9.4 #1 — slow/frozen frame counters since the most recent
|
|
30
|
+
* navigation transition. Native side hooks `CADisplayLink` (iOS)
|
|
31
|
+
* / `Choreographer.FrameCallback` (Android). Frame > 16.67ms =
|
|
32
|
+
* slow; > 700ms = frozen.
|
|
33
|
+
*/
|
|
34
|
+
getFrameCounters?: () => null | { frozen: number; slow: number }
|
|
35
|
+
/** Reset counters on navigation transition (called by useTraceNavigation). */
|
|
36
|
+
resetFrameCounters?: () => void
|
|
14
37
|
/**
|
|
15
38
|
* v0.7.3 — JS-triggered screenshot with consumer-supplied mask IDs.
|
|
16
39
|
* `maskedIds` are RN `nativeID` strings; native walks the view
|
|
@@ -127,6 +150,43 @@ export function stopAnrWatchdog(): void {
|
|
|
127
150
|
}
|
|
128
151
|
}
|
|
129
152
|
|
|
153
|
+
/** v0.9.4 #1 — finalize cold-start measurement. Idempotent. */
|
|
154
|
+
export function markNativeJsBridgeReady(): void {
|
|
155
|
+
try {
|
|
156
|
+
native()?.markJsBridgeReady?.()
|
|
157
|
+
} catch {
|
|
158
|
+
// ignore
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** v0.9.4 #1 — read cold start ms once. null when native unavailable. */
|
|
163
|
+
export function getNativeColdStartMs(): null | number {
|
|
164
|
+
try {
|
|
165
|
+
const v = native()?.getColdStartMs?.()
|
|
166
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : null
|
|
167
|
+
} catch {
|
|
168
|
+
return null
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** v0.9.4 #1 — read slow/frozen frame counters since last reset. */
|
|
173
|
+
export function getNativeFrameCounters(): null | { frozen: number; slow: number } {
|
|
174
|
+
try {
|
|
175
|
+
return native()?.getFrameCounters?.() ?? null
|
|
176
|
+
} catch {
|
|
177
|
+
return null
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** v0.9.4 #1 — reset frame counters on navigation transition. */
|
|
182
|
+
export function resetNativeFrameCounters(): void {
|
|
183
|
+
try {
|
|
184
|
+
native()?.resetFrameCounters?.()
|
|
185
|
+
} catch {
|
|
186
|
+
// ignore
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
130
190
|
/**
|
|
131
191
|
* v0.7.3 — drives the native screenshot path. JS side passes the
|
|
132
192
|
* current list of mask `nativeID`s (read from the consumer's
|
package/src/navigation.ts
CHANGED
|
@@ -24,6 +24,10 @@ import { useEffect, useRef } from 'react';
|
|
|
24
24
|
|
|
25
25
|
import { setActiveSpan, startSpan, type SpanHandle } from '@goliapkg/sentori-core';
|
|
26
26
|
|
|
27
|
+
import {
|
|
28
|
+
getNativeFrameCounters,
|
|
29
|
+
resetNativeFrameCounters,
|
|
30
|
+
} from './native';
|
|
27
31
|
import { captureStep } from './trail';
|
|
28
32
|
|
|
29
33
|
/** Minimal contract: anything with `addListener('state', cb)` and
|
|
@@ -101,11 +105,21 @@ export function useTraceNavigation(navigationRef: NavigationRefLike): void {
|
|
|
101
105
|
const enteredAt = lastRouteEnteredAtRef.current;
|
|
102
106
|
if (!span) return null;
|
|
103
107
|
const dwellMs = enteredAt !== null ? Math.max(0, Date.now() - enteredAt) : null;
|
|
108
|
+
// v0.9.4 #1 — drain native frame counters at screen-leave.
|
|
109
|
+
// Empty/null when native module not linked; tags omitted.
|
|
110
|
+
const fc = getNativeFrameCounters();
|
|
111
|
+
const finishTags: Record<string, string> = {};
|
|
112
|
+
if (dwellMs !== null) finishTags['nav.dwell_ms'] = String(dwellMs);
|
|
113
|
+
if (fc) {
|
|
114
|
+
finishTags['vital.slow_frames'] = String(fc.slow);
|
|
115
|
+
finishTags['vital.frozen_frames'] = String(fc.frozen);
|
|
116
|
+
}
|
|
104
117
|
span.finish({
|
|
105
118
|
status: 'ok',
|
|
106
|
-
|
|
107
|
-
tags: dwellMs !== null ? { 'nav.dwell_ms': String(dwellMs) } : undefined,
|
|
119
|
+
tags: Object.keys(finishTags).length > 0 ? finishTags : undefined,
|
|
108
120
|
});
|
|
121
|
+
// Reset counters for the next screen.
|
|
122
|
+
resetNativeFrameCounters();
|
|
109
123
|
return dwellMs;
|
|
110
124
|
};
|
|
111
125
|
|