Skip to content

Commit 71221fa

Browse files
committed
README
1 parent 925e332 commit 71221fa

1 file changed

Lines changed: 223 additions & 7 deletions

File tree

README.md

Lines changed: 223 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,239 @@
22

33
Low overhead monitoring of important performance metrics for React Native apps like JS FPS, UI FPS, CPU usage, and memory usage.
44

5+
## Installation
6+
7+
```bash
8+
bun add react-native-performance-toolkit react-native-nitro-modules
9+
```
10+
511
## Requirements
612

713
- React Native v0.76.0 or higher
814
- Node 18.0.0 or higher
15+
- Reanimated v4 or higher
916

10-
> [!IMPORTANT]
11-
> To Support `Nitro Views` you need to install React Native version v0.78.0 or higher.
17+
## Usage
1218

13-
## Installation
19+
#### Very simple usage - get value once
1420

15-
```bash
16-
bun add react-native-performance-toolkit react-native-nitro-modules
21+
```tsx
22+
import {
23+
getJsFps,
24+
getUiFps,
25+
getCpuUsage,
26+
getMemoryUsage,
27+
getDeviceMaxRefreshRate,
28+
getDeviceCurrentRefreshRate,
29+
} from 'react-native-performance-toolkit'
30+
31+
const jsFps = getJsFps()
32+
const uiFps = getUiFps()
33+
const cpuUsage = getCpuUsage()
34+
const memoryUsage = getMemoryUsage()
35+
const maxRefreshRate = getDeviceMaxRefreshRate()
36+
const currentRefreshRate = getDeviceCurrentRefreshRate()
37+
38+
console.log('JS FPS:', jsFps)
39+
console.log('UI FPS:', uiFps)
40+
console.log('CPU Usage:', cpuUsage)
41+
console.log('Memory Usage:', memoryUsage)
42+
console.log('Max Refresh Rate:', maxRefreshRate, 'Hz')
43+
console.log('Current Refresh Rate:', currentRefreshRate, 'Hz')
44+
```
45+
46+
#### Subscribe to changes
47+
48+
```tsx
49+
import { onFpsJsChange } from 'react-native-performance-toolkit'
50+
51+
const unsubscribe = onFpsJsChange((fps) => {
52+
console.log('JS FPS changed:', fps)
53+
})
54+
55+
// later
56+
unsubscribe()
57+
```
58+
59+
#### React Hooks - JS Thread only
60+
61+
Please be aware that this hook will not update if your JS thread is blocked (0 FPS) because updates are happening only that very same thread.
62+
63+
```tsx
64+
import { useFpsJs } from 'react-native-performance-toolkit'
65+
66+
const SomeComponent = () => {
67+
const jsFps = useFpsJs()
68+
return <Text>JS FPS: {jsFps}</Text>
69+
}
70+
```
71+
72+
#### Reanimated Hooks - UI Thread
73+
74+
To avoid issue with not showing 0 FPS it's recommended to use Reanimated based hooks or pre-made components. That's will ensure that value is updated even if the JS thread is blocked.
75+
76+
```tsx
77+
import { TextInput } from 'react-native'
78+
import { useFpsJsSharedValue } from 'react-native-performance-toolkit'
79+
import Animated, { useAnimatedReaction } from 'react-native-reanimated'
80+
81+
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
82+
83+
const SomeComponent = () => {
84+
const inputRef = useAnimatedRef<TextInput>()
85+
const uiFps = useFpsJsSharedValue()
86+
87+
useAnimatedReaction(
88+
() => uiFps.value.toString(),
89+
(value) => {
90+
setNativeProps(inputRef, { text: value })
91+
}
92+
)
93+
94+
return <AnimatedTextInput ref={inputRef} />
95+
}
96+
```
97+
98+
#### Pre-made Reanimated components - UI Thread
99+
100+
For better DX library provides pre-made Reanimated components that runs solely on UI thread.
101+
102+
```tsx
103+
import { UIThreadReanimatedCounter } from 'react-native-performance-toolkit'
104+
105+
const SomeComponent = () => {
106+
return (
107+
<>
108+
<UIThreadReanimatedCounter label="JS FPS" type="js" />
109+
<UIThreadReanimatedCounter label="UI FPS" type="ui" />
110+
<UIThreadReanimatedCounter label="CPU" type="cpu" />
111+
<UIThreadReanimatedCounter label="RAM" type="memory" />
112+
</>
113+
)
114+
}
115+
```
116+
117+
#### Direct buffer access (advanced usage, experimental)
118+
119+
Some advanced usage might require direct buffer access. For example you might want to use this library in custom native component or you might want to use it in worklet thread. This is experimental and might be changed in the future.
120+
121+
```tsx
122+
import {
123+
getJsFpsBuffer,
124+
getUiFpsBuffer,
125+
getCpuUsageBuffer,
126+
getMemoryUsageBuffer,
127+
} from 'react-native-performance-toolkit'
128+
129+
const jsFpsBuffer = getJsFpsBuffer()
130+
const uiFpsBuffer = getUiFpsBuffer()
131+
const cpuUsageBuffer = getCpuUsageBuffer()
132+
const memoryUsageBuffer = getMemoryUsageBuffer()
133+
134+
const getValueFromBuffer = (buffer: ArrayBuffer) => {
135+
const view = new DataView(buffer)
136+
return view.getInt32(0, true) // true = littleEndian
137+
}
138+
139+
console.log('JS FPS:', getValueFromBuffer(jsFpsBuffer))
140+
console.log('UI FPS:', getValueFromBuffer(uiFpsBuffer))
141+
console.log('CPU Usage:', getValueFromBuffer(cpuUsageBuffer))
142+
console.log('Memory Usage:', getValueFromBuffer(memoryUsageBuffer))
143+
```
144+
145+
### Access from worklets (advanced usage)
146+
147+
You can also access value from any worklet thread, but to do that you need use (Nitro Modules unboxing function)[https://nitro.margelo.com/docs/worklets]. For more detailed implementation look for [source code of UI Reanimated hooks like `useFpsJsSharedValue`](https://github.com/Nodonisko/react-native-performance-toolkit/blob/main/src/hooks/uiThreadHooks.ts).
148+
149+
```tsx
150+
import { useCallback } from 'react'
151+
import {
152+
BoxedJsFpsTracking,
153+
BoxedPerformanceToolkit,
154+
} from 'react-native-performance-toolkit'
155+
156+
// ...
157+
158+
const updateFps = useCallback(() => {
159+
'worklet'
160+
const unboxedJsFps = BoxedJsFpsTracking.unbox()
161+
const unboxedPerformanceToolkit = BoxedPerformanceToolkit.unbox()
162+
163+
let buffer = unboxedJsFps.getJsFpsBuffer()
164+
165+
const view = new DataView(buffer)
166+
const value = view.getInt32(0, true)
167+
168+
console.log('JS FPS:', value)
169+
170+
// update shared value for example
171+
fpsValue.value = value
172+
}, [])
17173
```
18174

19-
## Credits
175+
## API Reference
176+
177+
- **Simple getters**
178+
- `getJsFps(): number` - Returns current JS FPS (0-60)
179+
- `getUiFps(): number` - Returns current UI FPS (0-30/60/90/120/...)
180+
- `getCpuUsage(): number` - Returns CPU usage percentage in Linux format
181+
- `getMemoryUsage(): number` - Returns memory usage in bytes
182+
- `getDeviceMaxRefreshRate(): number` - Returns device's maximum supported refresh rate (e.g., 120 Hz on ProMotion devices)
183+
- `getDeviceCurrentRefreshRate(): number` - Returns device's current active refresh rate (may be lower than max on adaptive refresh rate displays)
184+
185+
- **Subscription functions**
186+
- `onFpsJsChange(callback: (fps: number) => void): () => void` - Subscribe to JS FPS changes
187+
- `onFpsUiChange(callback: (fps: number) => void): () => void` - Subscribe to UI FPS changes
188+
- `onCpuChange(callback: (value: number) => void): () => void` - Subscribe to CPU usage changes
189+
- `onMemoryChange(callback: (value: number) => void): () => void` - Subscribe to memory usage changes
190+
191+
- **React Hooks (JS Thread)**
192+
- `useFpsJs(): number` - Hook that returns current JS FPS
193+
- `useFpsUi(): number` - Hook that returns current UI FPS
194+
- `useCpuUsage(): number` - Hook that returns current CPU usage
195+
- `useMemoryUsage(): number` - Hook that returns current memory usage
196+
197+
- **React Components (runs on UI Thread)**
198+
- `<JSFpsCounter />` - Pre-made component displaying JS FPS
199+
- `<UIFpsCounter />` - Pre-made component displaying UI FPS
200+
- `<CpuUsageCounter />` - Pre-made component displaying CPU usage
201+
- `<MemoryUsageCounter />` - Pre-made component displaying memory usage
202+
203+
- **Buffer-based API**
204+
- `getJsFpsBuffer(): ArrayBuffer` - Returns ArrayBuffer with JS FPS data
205+
- `getUiFpsBuffer(): ArrayBuffer` - Returns ArrayBuffer with UI FPS data
206+
- `getCpuUsageBuffer(): ArrayBuffer` - Returns ArrayBuffer with CPU usage data
207+
- `getMemoryUsageBuffer(): ArrayBuffer` - Returns ArrayBuffer with memory usage data
208+
209+
- **Advanced (Nitro Modules)**
210+
- `BoxedJsFpsTracking` - Direct boxed Nitro module instance for worklet usage
211+
- `getJsFpsBuffer(): ArrayBuffer`
212+
- `BoxedPerformanceToolkit` - Direct boxed Nitro module instance for worklet usage
213+
- `getUiFpsBuffer(): ArrayBuffer`
214+
- `getCpuUsageBuffer(): ArrayBuffer`
215+
- `getMemoryUsageBuffer(): ArrayBuffer`
216+
- `getDeviceMaxRefreshRate(): number`
217+
- `getDeviceCurrentRefreshRate(): number`
218+
219+
## Architecture
220+
221+
#### Low overhead tracking
222+
223+
On Android library is reading values from virtual files like `/proc/stat` for CPU usage and `/proc/smaps_rollup` for memory usage. This is very low overhead and doesn't require any additional permissions.
224+
225+
On iOS library is reading values from `task_vm_info`/`rusage` direct kernel call. This is also super very low overhead.
226+
227+
#### Device Refresh Rate
228+
229+
The library provides two methods for getting refresh rate information:
230+
231+
- **Max Refresh Rate**: Returns the maximum supported refresh rate of the device
232+
- Android: Uses `Display.supportedModes` to find the highest available refresh rate
233+
- iOS: Uses `UIScreen.main.maximumFramesPerSecond`
20234

21-
Bootstrapped with [create-nitro-module](https://github.com/patrickkabwe/create-nitro-module).
235+
- **Current Refresh Rate**: Returns the currently active refresh rate
236+
- Android: Uses `Display.getRefreshRate()` - useful for devices with adaptive refresh rate (e.g., 60/90/120Hz switching)
237+
- iOS: Uses `CADisplayLink.preferredFramesPerSecond` - useful for ProMotion displays that dynamically adjust
22238

23239
## Contributing
24240

0 commit comments

Comments
 (0)