@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.
- package/AnsightReactNative.podspec +20 -0
- package/LICENSE +200 -0
- package/README.md +436 -0
- package/android/build.gradle +70 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/CMakeLists.txt +16 -0
- package/android/src/main/cpp/ansight_react_native_memory.cpp +57 -0
- package/android/src/main/kotlin/ai/ansight/reactnative/AnsightReactNativeModule.kt +1353 -0
- package/android/src/main/kotlin/ai/ansight/reactnative/AnsightReactNativePackage.kt +14 -0
- package/android/src/main/kotlin/ai/ansight/reactnative/ReactNativeMemoryProfiler.kt +167 -0
- package/index.d.ts +633 -0
- package/index.js +2303 -0
- package/ios/AnsightReactNative.swift +1635 -0
- package/ios/AnsightReactNativeMemorySampler.mm +130 -0
- package/ios/AnsightReactNativeModule.m +167 -0
- package/package.json +50 -0
- package/react-native.config.js +12 -0
- package/tsconfig.json +12 -0
|
@@ -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
|