@ansight/react-native 1.0.2-preview.1

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.
@@ -0,0 +1,14 @@
1
+ package ai.ansight.reactnative
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class AnsightReactNativePackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
10
+ listOf(AnsightReactNativeModule(reactContext))
11
+
12
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
13
+ emptyList()
14
+ }
@@ -0,0 +1,167 @@
1
+ package ai.ansight.reactnative
2
+
3
+ import ai.ansight.runtime.AnsightChannel
4
+ import ai.ansight.runtime.AnsightMetricSampler
5
+ import ai.ansight.runtime.AnsightMetricStream
6
+ import ai.ansight.runtime.AnsightRuntime
7
+ import com.facebook.react.bridge.ReactApplicationContext
8
+ import com.facebook.react.bridge.ReadableMap
9
+ import com.facebook.react.bridge.ReadableType
10
+ import java.util.concurrent.atomic.AtomicBoolean
11
+ import java.util.concurrent.atomic.AtomicLong
12
+
13
+ internal data class ReactNativeMemoryProfilingOptions(
14
+ val enabled: Boolean = true,
15
+ val jsHeapUsed: Boolean = true,
16
+ val jsHeapTotal: Boolean = true,
17
+ ) {
18
+ fun toMap(): Map<String, Any> = mapOf(
19
+ "enabled" to enabled,
20
+ "jsHeapUsed" to jsHeapUsed,
21
+ "jsHeapTotal" to jsHeapTotal,
22
+ )
23
+
24
+ companion object {
25
+ val Defaults = ReactNativeMemoryProfilingOptions()
26
+ val Disabled = ReactNativeMemoryProfilingOptions(enabled = false, jsHeapUsed = false, jsHeapTotal = false)
27
+ }
28
+ }
29
+
30
+ internal object ReactNativeMemoryChannels {
31
+ const val JsHeapUsed = 32
32
+ const val JsHeapTotal = 33
33
+
34
+ val jsHeapUsedChannel = AnsightChannel(
35
+ id = JsHeapUsed,
36
+ name = "React Native JS heap used",
37
+ colorHex = "#61DAFB",
38
+ unit = "bytes",
39
+ type = "memory",
40
+ source = "reactNative",
41
+ group = "React Native",
42
+ kind = "react_native_js_heap_used",
43
+ )
44
+
45
+ val jsHeapTotalChannel = AnsightChannel(
46
+ id = JsHeapTotal,
47
+ name = "React Native JS heap total",
48
+ colorHex = "#0A84FF",
49
+ unit = "bytes",
50
+ type = "memory",
51
+ source = "reactNative",
52
+ group = "React Native",
53
+ kind = "react_native_js_heap_total",
54
+ )
55
+ }
56
+
57
+ internal class ReactNativeMemoryProfiler(
58
+ private val reactContext: ReactApplicationContext,
59
+ ) {
60
+ private val hasSample = AtomicBoolean(false)
61
+ private val refreshScheduled = AtomicBoolean(false)
62
+ private val jsHeapUsedBytes = AtomicLong(0L)
63
+ private val jsHeapTotalBytes = AtomicLong(0L)
64
+
65
+ fun register(options: ReactNativeMemoryProfilingOptions) {
66
+ if (!options.enabled || !ReactNativeMemoryNative.isAvailable) {
67
+ return
68
+ }
69
+
70
+ if (options.jsHeapUsed) {
71
+ AnsightRuntime.registerMetricStream(
72
+ AnsightMetricStream(ReactNativeMemoryChannels.jsHeapUsedChannel, AnsightMetricSampler { sampleJsHeapUsedBytes() }),
73
+ )
74
+ }
75
+ if (options.jsHeapTotal) {
76
+ AnsightRuntime.registerMetricStream(
77
+ AnsightMetricStream(ReactNativeMemoryChannels.jsHeapTotalChannel, AnsightMetricSampler { sampleJsHeapTotalBytes() }),
78
+ )
79
+ }
80
+ requestRefresh()
81
+ }
82
+
83
+ private fun sampleJsHeapUsedBytes(): Long? {
84
+ requestRefresh()
85
+ return jsHeapUsedBytes.get().takeIf { hasSample.get() && it > 0L }
86
+ }
87
+
88
+ private fun sampleJsHeapTotalBytes(): Long? {
89
+ requestRefresh()
90
+ return jsHeapTotalBytes.get().takeIf { hasSample.get() && it > 0L }
91
+ }
92
+
93
+ private fun requestRefresh() {
94
+ if (!reactContext.hasActiveReactInstance() || !ReactNativeMemoryNative.isAvailable) {
95
+ return
96
+ }
97
+ if (!refreshScheduled.compareAndSet(false, true)) {
98
+ return
99
+ }
100
+
101
+ val queued = reactContext.runOnJSQueueThread {
102
+ try {
103
+ val holder = reactContext.javaScriptContextHolder ?: return@runOnJSQueueThread
104
+ val runtimePointer = synchronized(holder) { holder.get() }
105
+ if (runtimePointer == 0L) {
106
+ return@runOnJSQueueThread
107
+ }
108
+
109
+ val values = ReactNativeMemoryNative.readHeapInfo(runtimePointer) ?: return@runOnJSQueueThread
110
+ if (values.size >= 2 && values[0] > 0L) {
111
+ jsHeapUsedBytes.set(values[0])
112
+ jsHeapTotalBytes.set(values[1].coerceAtLeast(values[0]))
113
+ hasSample.set(true)
114
+ }
115
+ } finally {
116
+ refreshScheduled.set(false)
117
+ }
118
+ }
119
+
120
+ if (!queued) {
121
+ refreshScheduled.set(false)
122
+ }
123
+ }
124
+ }
125
+
126
+ internal object ReactNativeMemoryNative {
127
+ val isAvailable: Boolean = runCatching {
128
+ System.loadLibrary("ansightreactnativememory")
129
+ }.isSuccess
130
+
131
+ external fun readHeapInfo(runtimePointer: Long): LongArray?
132
+ }
133
+
134
+ internal fun reactNativeMemoryProfilingOptions(map: ReadableMap?): ReactNativeMemoryProfilingOptions {
135
+ if (map?.hasKey("reactNativeMemory") == true) {
136
+ if (map.isNull("reactNativeMemory")) {
137
+ return ReactNativeMemoryProfilingOptions.Disabled
138
+ }
139
+ if (map.getType("reactNativeMemory") == ReadableType.Boolean) {
140
+ return if (map.getBoolean("reactNativeMemory")) {
141
+ ReactNativeMemoryProfilingOptions.Defaults
142
+ } else {
143
+ ReactNativeMemoryProfilingOptions.Disabled
144
+ }
145
+ }
146
+ }
147
+
148
+ val options = if (map?.hasKey("reactNativeMemory") == true &&
149
+ !map.isNull("reactNativeMemory") &&
150
+ map.getType("reactNativeMemory") == ReadableType.Map
151
+ ) {
152
+ map.getMap("reactNativeMemory")
153
+ } else {
154
+ null
155
+ }
156
+
157
+ val enabled = options?.booleanOption("enabled", true) ?: true
158
+ val jsHeap = options?.booleanOption("jsHeap", true) ?: true
159
+ return ReactNativeMemoryProfilingOptions(
160
+ enabled = enabled,
161
+ jsHeapUsed = options?.booleanOption("jsHeapUsed", jsHeap) ?: jsHeap,
162
+ jsHeapTotal = options?.booleanOption("jsHeapTotal", jsHeap) ?: jsHeap,
163
+ )
164
+ }
165
+
166
+ private fun ReadableMap.booleanOption(name: String, default: Boolean): Boolean =
167
+ if (hasKey(name) && !isNull(name) && getType(name) == ReadableType.Boolean) getBoolean(name) else default