react-native-inapp-inspector 1.1.21 → 1.1.22
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/dist/commonjs/components/AnalyticsDetail.js +5 -3
- package/dist/commonjs/components/AnalyticsEventCard.d.ts +0 -1
- package/dist/commonjs/components/AnalyticsEventCard.js +85 -96
- package/dist/commonjs/components/Inspector/BundleTab.js +486 -651
- package/dist/commonjs/components/Inspector/PerformanceTab.js +185 -244
- package/dist/commonjs/components/Inspector/ReduxDetail.js +2 -2
- package/dist/commonjs/components/Inspector/ReduxTab.js +3 -3
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +115 -0
- package/dist/commonjs/customHooks/bundleAnalyzer.js +561 -0
- package/dist/commonjs/customHooks/performanceTracker.d.ts +84 -0
- package/dist/commonjs/customHooks/performanceTracker.js +541 -0
- package/dist/commonjs/helpers/index.d.ts +15 -0
- package/dist/commonjs/helpers/index.js +112 -1
- package/dist/commonjs/i18n/locales/en.json +225 -2
- package/dist/commonjs/styles/AppColors.d.ts +110 -0
- package/dist/commonjs/styles/AppColors.js +114 -0
- package/dist/esm/components/AnalyticsDetail.js +5 -3
- package/dist/esm/components/AnalyticsEventCard.d.ts +0 -1
- package/dist/esm/components/AnalyticsEventCard.js +85 -95
- package/dist/esm/components/Inspector/BundleTab.js +489 -654
- package/dist/esm/components/Inspector/PerformanceTab.js +185 -244
- package/dist/esm/components/Inspector/ReduxDetail.js +2 -2
- package/dist/esm/components/Inspector/ReduxTab.js +3 -3
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/bundleAnalyzer.d.ts +115 -0
- package/dist/esm/customHooks/bundleAnalyzer.js +554 -0
- package/dist/esm/customHooks/performanceTracker.d.ts +84 -0
- package/dist/esm/customHooks/performanceTracker.js +537 -0
- package/dist/esm/helpers/index.d.ts +15 -0
- package/dist/esm/helpers/index.js +107 -0
- package/dist/esm/i18n/locales/en.json +225 -2
- package/dist/esm/styles/AppColors.d.ts +110 -0
- package/dist/esm/styles/AppColors.js +114 -0
- package/package.json +1 -1
|
@@ -6,160 +6,11 @@ import HighlightText from '../HighlightText';
|
|
|
6
6
|
import EndOfListFooter from '../EndOfListFooter';
|
|
7
7
|
import { AppColors } from '../../styles/AppColors';
|
|
8
8
|
import { AppFonts } from '../../styles/AppFonts';
|
|
9
|
+
import { useTranslation } from '../../i18n';
|
|
10
|
+
import { getRuntimeDiagnostics } from '../../helpers';
|
|
9
11
|
import { SearchIcon, CircleXIcon, PerformanceIcon } from '../NetworkIcons';
|
|
10
|
-
const INITIAL_PERFORMANCE_EVENTS = [
|
|
11
|
-
{
|
|
12
|
-
id: 'perf-1',
|
|
13
|
-
timestamp: Date.now() - 48000,
|
|
14
|
-
type: 'fps_drop',
|
|
15
|
-
category: 'navigation',
|
|
16
|
-
fps: 38,
|
|
17
|
-
durationMs: 26.3,
|
|
18
|
-
label: 'Main Thread Spike during Navigation',
|
|
19
|
-
detail: 'Screen transition to ProductDetailScreen triggered heavy layout reconciliation and simultaneous component mounts.',
|
|
20
|
-
source: 'src/navigation/RootNavigator.tsx',
|
|
21
|
-
breakdown: { jsTimeMs: 18.2, uiTimeMs: 8.1, bridgeLatencyMs: 1.2 },
|
|
22
|
-
heapDeltaKb: 640,
|
|
23
|
-
advice: 'Defer non-critical offscreen hooks with InteractionManager.runAfterInteractions to preserve 60 FPS.',
|
|
24
|
-
severity: 'warning',
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
id: 'perf-2',
|
|
28
|
-
timestamp: Date.now() - 41000,
|
|
29
|
-
type: 'slow_render',
|
|
30
|
-
category: 'render',
|
|
31
|
-
fps: 42,
|
|
32
|
-
durationMs: 23.8,
|
|
33
|
-
label: 'FlatList Virtualization Re-render Pass',
|
|
34
|
-
detail: 'FlatList rendered 25 items simultaneously on orientation change without memoized row component.',
|
|
35
|
-
source: 'src/components/Inspector/NetworkTab.tsx',
|
|
36
|
-
breakdown: { jsTimeMs: 16.4, uiTimeMs: 7.4 },
|
|
37
|
-
heapDeltaKb: 380,
|
|
38
|
-
advice: 'Implement getItemLayout and React.memo(LogCard) to skip redundant diffing passes.',
|
|
39
|
-
severity: 'warning',
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
id: 'perf-3',
|
|
43
|
-
timestamp: Date.now() - 35000,
|
|
44
|
-
type: 'transition',
|
|
45
|
-
category: 'navigation',
|
|
46
|
-
fps: 59,
|
|
47
|
-
durationMs: 16.9,
|
|
48
|
-
label: 'Native Modal Slide-Up Transition',
|
|
49
|
-
detail: 'Hardware accelerated native driver animated transform running smoothly at sustained 60 FPS.',
|
|
50
|
-
source: 'src/components/Inspector/MainScreen.tsx',
|
|
51
|
-
breakdown: { jsTimeMs: 2.1, uiTimeMs: 14.8 },
|
|
52
|
-
heapDeltaKb: 120,
|
|
53
|
-
advice: 'Using nativeDriver: true successfully prevents JS thread blocking during animations.',
|
|
54
|
-
severity: 'optimal',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: 'perf-4',
|
|
58
|
-
timestamp: Date.now() - 28000,
|
|
59
|
-
type: 'memory',
|
|
60
|
-
category: 'memory',
|
|
61
|
-
fps: 60,
|
|
62
|
-
durationMs: 16.6,
|
|
63
|
-
label: 'Hermes Generational Garbage Collection',
|
|
64
|
-
detail: 'Minor generational GC cycle scavenged 4.2 MB ephemeral heap objects with sub-millisecond thread pause.',
|
|
65
|
-
source: 'Hermes VM Garbage Collector',
|
|
66
|
-
breakdown: { jsTimeMs: 3.1, uiTimeMs: 0.2 },
|
|
67
|
-
heapDeltaKb: -4280,
|
|
68
|
-
advice: 'Hermes generational garbage collector is operating within optimal sub-5ms limits.',
|
|
69
|
-
severity: 'optimal',
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
id: 'perf-5',
|
|
73
|
-
timestamp: Date.now() - 22000,
|
|
74
|
-
type: 'network',
|
|
75
|
-
category: 'io',
|
|
76
|
-
fps: 48,
|
|
77
|
-
durationMs: 20.8,
|
|
78
|
-
label: 'Large JSON Payload Deserialization',
|
|
79
|
-
detail: '50-item API response parse overhead in network adapter (185 KB JSON raw string).',
|
|
80
|
-
source: 'src/customHooks/networkLogger.ts',
|
|
81
|
-
breakdown: { jsTimeMs: 15.6, uiTimeMs: 5.2, bridgeLatencyMs: 2.1 },
|
|
82
|
-
heapDeltaKb: 890,
|
|
83
|
-
advice: 'Consider paginating API payloads or streaming responses if payload size exceeds 250 KB.',
|
|
84
|
-
severity: 'warning',
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
id: 'perf-6',
|
|
88
|
-
timestamp: Date.now() - 17000,
|
|
89
|
-
type: 'slow_render',
|
|
90
|
-
category: 'render',
|
|
91
|
-
fps: 52,
|
|
92
|
-
durationMs: 19.2,
|
|
93
|
-
label: 'Image Bitmap Decode & Rasterization',
|
|
94
|
-
detail: 'Retina raster decode for banner_dark.png (1200×630px raster buffer allocation).',
|
|
95
|
-
source: 'src/components/Inspector/BundleTab.tsx',
|
|
96
|
-
breakdown: { jsTimeMs: 3.4, uiTimeMs: 15.8 },
|
|
97
|
-
heapDeltaKb: 1450,
|
|
98
|
-
advice: 'Downscale asset dimensions or convert to WebP to reduce decode latency by ~65%.',
|
|
99
|
-
severity: 'warning',
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
id: 'perf-7',
|
|
103
|
-
timestamp: Date.now() - 12000,
|
|
104
|
-
type: 'bridge',
|
|
105
|
-
category: 'bridge',
|
|
106
|
-
fps: 60,
|
|
107
|
-
durationMs: 16.6,
|
|
108
|
-
label: 'Native TurboModule JSI Invocation',
|
|
109
|
-
detail: 'AsyncStorage / MMKV preferences transaction read across 32 configuration keys.',
|
|
110
|
-
source: 'src/helpers/settingsStore.ts',
|
|
111
|
-
breakdown: { jsTimeMs: 1.8, uiTimeMs: 0.8, bridgeLatencyMs: 0.4 },
|
|
112
|
-
heapDeltaKb: 45,
|
|
113
|
-
advice: 'Direct C++ JSI Turbomodule bindings completely bypass legacy JSON bridge serialization overhead.',
|
|
114
|
-
severity: 'optimal',
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
id: 'perf-8',
|
|
118
|
-
timestamp: Date.now() - 8000,
|
|
119
|
-
type: 'slow_render',
|
|
120
|
-
category: 'render',
|
|
121
|
-
fps: 60,
|
|
122
|
-
durationMs: 16.6,
|
|
123
|
-
label: 'Redux Action State Tree Diffing',
|
|
124
|
-
detail: 'Redux dispatch pass evaluated 6 reducer slices and emitted state notification in 4.8ms.',
|
|
125
|
-
source: 'src/components/Inspector/ReduxTab.tsx',
|
|
126
|
-
breakdown: { jsTimeMs: 4.8, uiTimeMs: 1.2 },
|
|
127
|
-
heapDeltaKb: 180,
|
|
128
|
-
advice: 'State tree immutability preserved. Memoized selectors prevented redundant component renders.',
|
|
129
|
-
severity: 'optimal',
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
id: 'perf-9',
|
|
133
|
-
timestamp: Date.now() - 4000,
|
|
134
|
-
type: 'touch',
|
|
135
|
-
category: 'render',
|
|
136
|
-
fps: 60,
|
|
137
|
-
durationMs: 16.6,
|
|
138
|
-
label: 'Touch-to-Render Event Latency',
|
|
139
|
-
detail: 'Gesture responder dispatched tap event to TabBar button with immediate 60 FPS response.',
|
|
140
|
-
source: 'src/components/Inspector/TabBar.tsx',
|
|
141
|
-
breakdown: { jsTimeMs: 4.2, uiTimeMs: 2.1 },
|
|
142
|
-
heapDeltaKb: 30,
|
|
143
|
-
advice: 'Touch responder latency is well within standard 16.67ms frame budget.',
|
|
144
|
-
severity: 'optimal',
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
id: 'perf-10',
|
|
148
|
-
timestamp: Date.now() - 1500,
|
|
149
|
-
type: 'transition',
|
|
150
|
-
category: 'render',
|
|
151
|
-
fps: 60,
|
|
152
|
-
durationMs: 16.6,
|
|
153
|
-
label: 'C++ Yoga Flexbox Layout Pass',
|
|
154
|
-
detail: 'Inspector UI multi-tab card layout recalculation and font metrics pass in C++ Yoga engine.',
|
|
155
|
-
source: 'Yoga Flexbox Layout Engine',
|
|
156
|
-
breakdown: { jsTimeMs: 2.8, uiTimeMs: 3.4 },
|
|
157
|
-
heapDeltaKb: 65,
|
|
158
|
-
advice: 'Flexbox layout constraints are cached and computed efficiently with zero reflow penalties.',
|
|
159
|
-
severity: 'optimal',
|
|
160
|
-
},
|
|
161
|
-
];
|
|
162
12
|
const PerformanceTab = React.memo(() => {
|
|
13
|
+
const { t } = useTranslation();
|
|
163
14
|
// ─── Real-Time FPS Tracking ────────────────────────────────────────────────
|
|
164
15
|
const [isRecording, setIsRecording] = useState(true);
|
|
165
16
|
const [currentFps, setCurrentFps] = useState(60);
|
|
@@ -184,7 +35,7 @@ const PerformanceTab = React.memo(() => {
|
|
|
184
35
|
60, 59, 60, 60, 58, 60, 59, 60, 60, 60, 57, 60, 59, 60, 60, 58, 60, 60, 59, 60,
|
|
185
36
|
]);
|
|
186
37
|
// Performance Log Events
|
|
187
|
-
const [events, setEvents] = useState(
|
|
38
|
+
const [events, setEvents] = useState([]);
|
|
188
39
|
const [filterCategory, setFilterCategory] = useState('ALL');
|
|
189
40
|
const [search, setSearch] = useState('');
|
|
190
41
|
// ─── Live Frame Measurement Loop (1-Second Batch Interval) ────────────────
|
|
@@ -229,21 +80,22 @@ const PerformanceTab = React.memo(() => {
|
|
|
229
80
|
});
|
|
230
81
|
// Trigger log if noticeable frame drop
|
|
231
82
|
if (measuredFps < 50) {
|
|
83
|
+
const durationStr = (1000 / measuredFps).toFixed(1);
|
|
232
84
|
const newEvent = {
|
|
233
85
|
id: `drop-${Date.now()}`,
|
|
234
86
|
timestamp: Date.now(),
|
|
235
87
|
type: 'fps_drop',
|
|
236
88
|
category: 'render',
|
|
237
89
|
fps: measuredFps,
|
|
238
|
-
durationMs: Number(
|
|
239
|
-
label:
|
|
240
|
-
detail:
|
|
241
|
-
source: '
|
|
90
|
+
durationMs: Number(durationStr),
|
|
91
|
+
label: t('performance.liveFpsDip', { fps: measuredFps }),
|
|
92
|
+
detail: t('performance.liveFpsDipDetail', { duration: durationStr }),
|
|
93
|
+
source: t('performance.uiRenderThread'),
|
|
242
94
|
breakdown: {
|
|
243
95
|
jsTimeMs: Number(((1000 / measuredFps) * 0.65).toFixed(1)),
|
|
244
96
|
uiTimeMs: Number(((1000 / measuredFps) * 0.35).toFixed(1)),
|
|
245
97
|
},
|
|
246
|
-
advice: '
|
|
98
|
+
advice: t('performance.liveFpsDipAdvice'),
|
|
247
99
|
severity: measuredFps < 30 ? 'critical' : 'warning',
|
|
248
100
|
};
|
|
249
101
|
setEvents(prev => [newEvent, ...prev.slice(0, 49)]);
|
|
@@ -285,12 +137,76 @@ const PerformanceTab = React.memo(() => {
|
|
|
285
137
|
}, [events, search, filterCategory]);
|
|
286
138
|
// Overall Performance Score
|
|
287
139
|
const performanceScore = useMemo(() => {
|
|
288
|
-
if (avgFps >= 58)
|
|
289
|
-
return {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
140
|
+
if (avgFps >= 58) {
|
|
141
|
+
return {
|
|
142
|
+
score: '98/100',
|
|
143
|
+
grade: t('performance.excellent'),
|
|
144
|
+
color: AppColors.emerald600,
|
|
145
|
+
bg: AppColors.greenBg,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (avgFps >= 50) {
|
|
149
|
+
return {
|
|
150
|
+
score: '84/100',
|
|
151
|
+
grade: t('performance.good'),
|
|
152
|
+
color: AppColors.firebaseOrange,
|
|
153
|
+
bg: AppColors.amberBg,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
score: '58/100',
|
|
158
|
+
grade: t('performance.needsOptimization'),
|
|
159
|
+
color: AppColors.errorColor,
|
|
160
|
+
bg: AppColors.errorCardBg,
|
|
161
|
+
};
|
|
162
|
+
}, [avgFps, t]);
|
|
163
|
+
// ─── Dynamic Runtime Environment & Engine Diagnostics ──────────────────────
|
|
164
|
+
const runtimeDiagnostics = useMemo(() => {
|
|
165
|
+
const raw = getRuntimeDiagnostics();
|
|
166
|
+
const engineNameMap = {
|
|
167
|
+
hermes: t('performance.hermesAot'),
|
|
168
|
+
v8: t('performance.v8Jit'),
|
|
169
|
+
jsc: t('performance.jscEngine'),
|
|
170
|
+
};
|
|
171
|
+
const engineSubMap = {
|
|
172
|
+
hermes: t('performance.bytecodeCompiled'),
|
|
173
|
+
v8: t('performance.jitEngine'),
|
|
174
|
+
jsc: t('performance.webkitEngine'),
|
|
175
|
+
};
|
|
176
|
+
const archNameMap = {
|
|
177
|
+
fabric: t('performance.fabricJsi'),
|
|
178
|
+
paper: t('performance.paperBridge'),
|
|
179
|
+
};
|
|
180
|
+
const archSubMap = {
|
|
181
|
+
fabric: t('performance.directCppBindings'),
|
|
182
|
+
paper: t('performance.asyncJsonBridge'),
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
jsEngineName: engineNameMap[raw.engineType],
|
|
186
|
+
jsEngineSub: engineSubMap[raw.engineType],
|
|
187
|
+
uiArchName: archNameMap[raw.archType],
|
|
188
|
+
uiArchSub: archSubMap[raw.archType],
|
|
189
|
+
usedHeapMb: raw.usedHeapMb,
|
|
190
|
+
totalAllocMb: raw.totalAllocMb,
|
|
191
|
+
};
|
|
192
|
+
}, [t]);
|
|
193
|
+
// ─── Dynamic 16.67ms Frame Budget Utilization ─────────────────────────────
|
|
194
|
+
const frameBudgetStats = useMemo(() => {
|
|
195
|
+
const frameDuration = avgFps > 0 ? 1000 / avgFps : 16.67;
|
|
196
|
+
const jsTime = Number(Math.max(1.0, Math.min(frameDuration * 0.45, 2.0 + jsLagMs)).toFixed(1));
|
|
197
|
+
const layoutTime = Number(Math.max(1.0, frameDuration * 0.2).toFixed(1));
|
|
198
|
+
const renderTime = Number(Math.max(1.0, frameDuration * 0.25).toFixed(1));
|
|
199
|
+
const totalUsed = jsTime + layoutTime + renderTime;
|
|
200
|
+
const freeTime = Math.max(0, Number((16.67 - totalUsed).toFixed(1)));
|
|
201
|
+
const headroomPercent = Math.max(0, Math.min(100, Math.round((freeTime / 16.67) * 100)));
|
|
202
|
+
return {
|
|
203
|
+
jsTime,
|
|
204
|
+
layoutTime,
|
|
205
|
+
renderTime,
|
|
206
|
+
freeTime,
|
|
207
|
+
headroomPercent,
|
|
208
|
+
};
|
|
209
|
+
}, [avgFps, jsLagMs]);
|
|
294
210
|
const clearEvents = () => {
|
|
295
211
|
setEvents([]);
|
|
296
212
|
};
|
|
@@ -299,7 +215,7 @@ const PerformanceTab = React.memo(() => {
|
|
|
299
215
|
<View style={perfStyles.headerBar}>
|
|
300
216
|
<View style={perfStyles.searchBox}>
|
|
301
217
|
<SearchIcon color={AppColors.grayTextWeak} size={15}/>
|
|
302
|
-
<TextInput placeholder=
|
|
218
|
+
<TextInput placeholder={t('performance.searchPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={perfStyles.searchInput} autoCapitalize="none" autoCorrect={false}/>
|
|
303
219
|
{search ? (<TouchableScale onPress={() => setSearch('')}>
|
|
304
220
|
<CircleXIcon color={AppColors.grayTextWeak} size={16}/>
|
|
305
221
|
</TouchableScale>) : null}
|
|
@@ -312,13 +228,13 @@ const PerformanceTab = React.memo(() => {
|
|
|
312
228
|
]}>
|
|
313
229
|
<View style={[
|
|
314
230
|
perfStyles.recordDot,
|
|
315
|
-
{ backgroundColor: isRecording ?
|
|
231
|
+
{ backgroundColor: isRecording ? AppColors.emerald500 : AppColors.amber500 },
|
|
316
232
|
]}/>
|
|
317
233
|
<Text style={[
|
|
318
234
|
perfStyles.recordBtnText,
|
|
319
|
-
{ color: isRecording ?
|
|
235
|
+
{ color: isRecording ? AppColors.emerald600 : AppColors.amber700 },
|
|
320
236
|
]}>
|
|
321
|
-
{isRecording ? '
|
|
237
|
+
{isRecording ? t('performance.live') : t('redux.paused')}
|
|
322
238
|
</Text>
|
|
323
239
|
</TouchableScale>
|
|
324
240
|
|
|
@@ -330,10 +246,10 @@ const PerformanceTab = React.memo(() => {
|
|
|
330
246
|
jankyFrameCount,
|
|
331
247
|
jsLagMs,
|
|
332
248
|
platform: Platform.OS,
|
|
333
|
-
engine:
|
|
249
|
+
engine: runtimeDiagnostics.jsEngineName,
|
|
334
250
|
totalEvents: events.length,
|
|
335
251
|
events,
|
|
336
|
-
})} label=
|
|
252
|
+
})} label={t('common.copyJson')}/>
|
|
337
253
|
</View>
|
|
338
254
|
</View>
|
|
339
255
|
|
|
@@ -341,34 +257,34 @@ const PerformanceTab = React.memo(() => {
|
|
|
341
257
|
<View style={perfStyles.filterRow}>
|
|
342
258
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 6, paddingHorizontal: 12, paddingVertical: 4 }}>
|
|
343
259
|
{[
|
|
344
|
-
{ key: 'ALL', label: '
|
|
260
|
+
{ key: 'ALL', label: t('performance.allLogs'), icon: '⚡', count: events.length },
|
|
345
261
|
{
|
|
346
262
|
key: 'JANKY',
|
|
347
|
-
label: '
|
|
263
|
+
label: t('performance.jankySlow'),
|
|
348
264
|
icon: '⚠️',
|
|
349
265
|
count: events.filter(e => e.severity === 'warning' || e.severity === 'critical').length,
|
|
350
266
|
},
|
|
351
267
|
{
|
|
352
268
|
key: 'NAVIGATION',
|
|
353
|
-
label: '
|
|
269
|
+
label: t('performance.navigation'),
|
|
354
270
|
icon: '🗺️',
|
|
355
271
|
count: events.filter(e => e.category === 'navigation').length,
|
|
356
272
|
},
|
|
357
273
|
{
|
|
358
274
|
key: 'RENDER',
|
|
359
|
-
label: '
|
|
275
|
+
label: t('performance.components'),
|
|
360
276
|
icon: '⚛️',
|
|
361
277
|
count: events.filter(e => e.category === 'render').length,
|
|
362
278
|
},
|
|
363
279
|
{
|
|
364
280
|
key: 'MEMORY',
|
|
365
|
-
label: '
|
|
281
|
+
label: t('performance.memoryGc'),
|
|
366
282
|
icon: '🧠',
|
|
367
283
|
count: events.filter(e => e.category === 'memory').length,
|
|
368
284
|
},
|
|
369
285
|
{
|
|
370
286
|
key: 'IO',
|
|
371
|
-
label: '
|
|
287
|
+
label: t('performance.networkIo'),
|
|
372
288
|
icon: '🌐',
|
|
373
289
|
count: events.filter(e => e.category === 'io' || e.category === 'bridge').length,
|
|
374
290
|
},
|
|
@@ -396,7 +312,7 @@ const PerformanceTab = React.memo(() => {
|
|
|
396
312
|
<View style={perfStyles.metricCardHeader}>
|
|
397
313
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
398
314
|
<PerformanceIcon color={AppColors.purple} size={18}/>
|
|
399
|
-
<Text style={perfStyles.metricCardTitle}>
|
|
315
|
+
<Text style={perfStyles.metricCardTitle}>{t('performance.liveFps')}</Text>
|
|
400
316
|
</View>
|
|
401
317
|
<View style={[
|
|
402
318
|
perfStyles.scorePill,
|
|
@@ -418,36 +334,36 @@ const PerformanceTab = React.memo(() => {
|
|
|
418
334
|
perfStyles.fpsBigNumber,
|
|
419
335
|
{
|
|
420
336
|
color: currentFps >= 55
|
|
421
|
-
?
|
|
337
|
+
? AppColors.emerald600
|
|
422
338
|
: currentFps >= 40
|
|
423
|
-
?
|
|
424
|
-
:
|
|
339
|
+
? AppColors.firebaseOrange
|
|
340
|
+
: AppColors.errorColor,
|
|
425
341
|
},
|
|
426
342
|
]}>
|
|
427
343
|
{currentFps}
|
|
428
344
|
</Text>
|
|
429
|
-
<Text style={perfStyles.fpsUnit}>
|
|
345
|
+
<Text style={perfStyles.fpsUnit}>{t('performance.fpsUnit')}</Text>
|
|
430
346
|
</View>
|
|
431
347
|
|
|
432
348
|
<View style={perfStyles.fpsStatsGrid}>
|
|
433
349
|
<View style={perfStyles.statItem}>
|
|
434
|
-
<Text style={perfStyles.statLabel}>
|
|
350
|
+
<Text style={perfStyles.statLabel}>{t('performance.avgFrameTime')}</Text>
|
|
435
351
|
<Text style={perfStyles.statValue}>{avgFps}</Text>
|
|
436
352
|
</View>
|
|
437
353
|
<View style={perfStyles.statItem}>
|
|
438
|
-
<Text style={perfStyles.statLabel}>
|
|
439
|
-
<Text style={[perfStyles.statValue, minFps < 50 && { color:
|
|
354
|
+
<Text style={perfStyles.statLabel}>{t('performance.peakTime')}</Text>
|
|
355
|
+
<Text style={[perfStyles.statValue, minFps < 50 && { color: AppColors.firebaseOrange }]}>
|
|
440
356
|
{minFps}
|
|
441
357
|
</Text>
|
|
442
358
|
</View>
|
|
443
359
|
<View style={perfStyles.statItem}>
|
|
444
|
-
<Text style={perfStyles.statLabel}>
|
|
445
|
-
<Text style={[perfStyles.statValue, jsLagMs > 5 && { color:
|
|
360
|
+
<Text style={perfStyles.statLabel}>{t('performance.jsLag')}</Text>
|
|
361
|
+
<Text style={[perfStyles.statValue, jsLagMs > 5 && { color: AppColors.firebaseOrange }]}>
|
|
446
362
|
{jsLagMs}ms
|
|
447
363
|
</Text>
|
|
448
364
|
</View>
|
|
449
365
|
<View style={perfStyles.statItem}>
|
|
450
|
-
<Text style={perfStyles.statLabel}>
|
|
366
|
+
<Text style={perfStyles.statLabel}>{t('performance.jankRate')}</Text>
|
|
451
367
|
<Text style={perfStyles.statValue}>
|
|
452
368
|
{totalFrames > 0
|
|
453
369
|
? `${((jankyFrameCount / Math.max(1, totalFrames / 60)) * 100).toFixed(0)}%`
|
|
@@ -467,50 +383,73 @@ const PerformanceTab = React.memo(() => {
|
|
|
467
383
|
perfStyles.sparklineBar,
|
|
468
384
|
{
|
|
469
385
|
height: barHeight,
|
|
470
|
-
backgroundColor: isLow ?
|
|
386
|
+
backgroundColor: isLow ? AppColors.errorColor : AppColors.emerald500,
|
|
471
387
|
},
|
|
472
388
|
]}/>
|
|
473
389
|
</View>);
|
|
474
390
|
})}
|
|
475
391
|
</View>
|
|
476
392
|
<View style={perfStyles.sparklineFooter}>
|
|
477
|
-
<Text style={perfStyles.sparklineFooterText}>
|
|
478
|
-
<Text style={perfStyles.sparklineFooterTarget}>
|
|
393
|
+
<Text style={perfStyles.sparklineFooterText}>{t('performance.realtimeWindow')}</Text>
|
|
394
|
+
<Text style={perfStyles.sparklineFooterTarget}>{t('performance.fpsTarget')}</Text>
|
|
479
395
|
</View>
|
|
480
396
|
</View>
|
|
481
397
|
|
|
482
398
|
{/* ── 2. FRAME BUDGET BREAKDOWN CARD ── */}
|
|
483
399
|
<View style={perfStyles.budgetCard}>
|
|
484
400
|
<View style={perfStyles.budgetHeader}>
|
|
485
|
-
<Text style={perfStyles.budgetTitle}>
|
|
486
|
-
<Text style={perfStyles.budgetHeadroom}
|
|
401
|
+
<Text style={perfStyles.budgetTitle}>{t('performance.budgetUsage')}</Text>
|
|
402
|
+
<Text style={perfStyles.budgetHeadroom}>
|
|
403
|
+
{t('performance.budgetHeadroom', {
|
|
404
|
+
time: frameBudgetStats.freeTime,
|
|
405
|
+
percent: frameBudgetStats.headroomPercent,
|
|
406
|
+
})}
|
|
407
|
+
</Text>
|
|
487
408
|
</View>
|
|
488
409
|
|
|
489
410
|
{/* Multi-colored segmented frame budget bar */}
|
|
490
411
|
<View style={perfStyles.budgetBarContainer}>
|
|
491
|
-
<View style={[
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
412
|
+
<View style={[
|
|
413
|
+
perfStyles.budgetBarSegment,
|
|
414
|
+
{ flex: Math.max(0.5, frameBudgetStats.jsTime), backgroundColor: AppColors.sky400 },
|
|
415
|
+
]}/>
|
|
416
|
+
<View style={[
|
|
417
|
+
perfStyles.budgetBarSegment,
|
|
418
|
+
{ flex: Math.max(0.5, frameBudgetStats.layoutTime), backgroundColor: AppColors.indigo500 },
|
|
419
|
+
]}/>
|
|
420
|
+
<View style={[
|
|
421
|
+
perfStyles.budgetBarSegment,
|
|
422
|
+
{ flex: Math.max(0.5, frameBudgetStats.renderTime), backgroundColor: AppColors.pink400 },
|
|
423
|
+
]}/>
|
|
424
|
+
<View style={[
|
|
425
|
+
perfStyles.budgetBarSegment,
|
|
426
|
+
{ flex: Math.max(0.5, frameBudgetStats.freeTime), backgroundColor: AppColors.emerald400 },
|
|
427
|
+
]}/>
|
|
495
428
|
</View>
|
|
496
429
|
|
|
497
430
|
<View style={perfStyles.budgetLegendGrid}>
|
|
498
431
|
<View style={perfStyles.legendItem}>
|
|
499
|
-
<View style={[perfStyles.legendDot, { backgroundColor:
|
|
500
|
-
<Text style={perfStyles.legendText}>
|
|
432
|
+
<View style={[perfStyles.legendDot, { backgroundColor: AppColors.sky400 }]}/>
|
|
433
|
+
<Text style={perfStyles.legendText}>
|
|
434
|
+
{t('performance.jsExec', { time: frameBudgetStats.jsTime })}
|
|
435
|
+
</Text>
|
|
501
436
|
</View>
|
|
502
437
|
<View style={perfStyles.legendItem}>
|
|
503
|
-
<View style={[perfStyles.legendDot, { backgroundColor:
|
|
504
|
-
<Text style={perfStyles.legendText}>
|
|
438
|
+
<View style={[perfStyles.legendDot, { backgroundColor: AppColors.indigo500 }]}/>
|
|
439
|
+
<Text style={perfStyles.legendText}>
|
|
440
|
+
{t('performance.yogaLayoutTime', { time: frameBudgetStats.layoutTime })}
|
|
441
|
+
</Text>
|
|
505
442
|
</View>
|
|
506
443
|
<View style={perfStyles.legendItem}>
|
|
507
|
-
<View style={[perfStyles.legendDot, { backgroundColor:
|
|
508
|
-
<Text style={perfStyles.legendText}>
|
|
444
|
+
<View style={[perfStyles.legendDot, { backgroundColor: AppColors.pink400 }]}/>
|
|
445
|
+
<Text style={perfStyles.legendText}>
|
|
446
|
+
{t('performance.uiRenderTime', { time: frameBudgetStats.renderTime })}
|
|
447
|
+
</Text>
|
|
509
448
|
</View>
|
|
510
449
|
<View style={perfStyles.legendItem}>
|
|
511
|
-
<View style={[perfStyles.legendDot, { backgroundColor:
|
|
512
|
-
<Text style={[perfStyles.legendText, { color:
|
|
513
|
-
|
|
450
|
+
<View style={[perfStyles.legendDot, { backgroundColor: AppColors.emerald400 }]}/>
|
|
451
|
+
<Text style={[perfStyles.legendText, { color: AppColors.emerald600, fontFamily: AppFonts.interBold }]}>
|
|
452
|
+
{t('performance.freeTime', { time: frameBudgetStats.freeTime })}
|
|
514
453
|
</Text>
|
|
515
454
|
</View>
|
|
516
455
|
</View>
|
|
@@ -519,49 +458,51 @@ const PerformanceTab = React.memo(() => {
|
|
|
519
458
|
{/* ── 3. RUNTIME ENGINE & MEMORY SUMMARY ── */}
|
|
520
459
|
<View style={perfStyles.engineSummaryGrid}>
|
|
521
460
|
<View style={perfStyles.engineSummaryCard}>
|
|
522
|
-
<Text style={perfStyles.engineSummaryLabel}>
|
|
523
|
-
<Text style={perfStyles.engineSummaryValue}>
|
|
524
|
-
<Text style={perfStyles.engineSummarySub}>
|
|
461
|
+
<Text style={perfStyles.engineSummaryLabel}>{t('performance.jsEngine')}</Text>
|
|
462
|
+
<Text style={perfStyles.engineSummaryValue}>{runtimeDiagnostics.jsEngineName}</Text>
|
|
463
|
+
<Text style={perfStyles.engineSummarySub}>{runtimeDiagnostics.jsEngineSub}</Text>
|
|
525
464
|
</View>
|
|
526
465
|
<View style={perfStyles.engineSummaryCard}>
|
|
527
|
-
<Text style={perfStyles.engineSummaryLabel}>
|
|
528
|
-
<Text style={perfStyles.engineSummaryValue}>
|
|
529
|
-
<Text style={perfStyles.engineSummarySub}>
|
|
466
|
+
<Text style={perfStyles.engineSummaryLabel}>{t('performance.uiRenderThread')}</Text>
|
|
467
|
+
<Text style={perfStyles.engineSummaryValue}>{runtimeDiagnostics.uiArchName}</Text>
|
|
468
|
+
<Text style={perfStyles.engineSummarySub}>{runtimeDiagnostics.uiArchSub}</Text>
|
|
530
469
|
</View>
|
|
531
470
|
<View style={perfStyles.engineSummaryCard}>
|
|
532
|
-
<Text style={perfStyles.engineSummaryLabel}>
|
|
533
|
-
<Text style={perfStyles.engineSummaryValue}>
|
|
534
|
-
<Text style={perfStyles.engineSummarySub}>
|
|
471
|
+
<Text style={perfStyles.engineSummaryLabel}>{t('performance.tabMemory')}</Text>
|
|
472
|
+
<Text style={perfStyles.engineSummaryValue}>{runtimeDiagnostics.usedHeapMb} MB</Text>
|
|
473
|
+
<Text style={perfStyles.engineSummarySub}>
|
|
474
|
+
{t('performance.heapAllocatedSub', { allocated: runtimeDiagnostics.totalAllocMb })}
|
|
475
|
+
</Text>
|
|
535
476
|
</View>
|
|
536
477
|
</View>
|
|
537
478
|
|
|
538
479
|
{/* ── 4. PERFORMANCE LOGS & DETAILED EVENTS ── */}
|
|
539
480
|
<View style={perfStyles.logsHeaderRow}>
|
|
540
481
|
<Text style={perfStyles.logsSectionHeading}>
|
|
541
|
-
|
|
482
|
+
{t('performance.recordedEvents')} ({filteredEvents.length})
|
|
542
483
|
</Text>
|
|
543
484
|
{events.length > 0 && (<TouchableScale onPress={clearEvents}>
|
|
544
|
-
<Text style={perfStyles.clearLink}>
|
|
485
|
+
<Text style={perfStyles.clearLink}>{t('common.clear')}</Text>
|
|
545
486
|
</TouchableScale>)}
|
|
546
487
|
</View>
|
|
547
488
|
|
|
548
489
|
{filteredEvents.length === 0 ? (<View style={perfStyles.emptyCard}>
|
|
549
490
|
<Text style={{ fontSize: 28 }}>⚡</Text>
|
|
550
|
-
<Text style={perfStyles.emptyTitle}>
|
|
491
|
+
<Text style={perfStyles.emptyTitle}>{t('performance.emptyTitle')}</Text>
|
|
551
492
|
<Text style={perfStyles.emptySub}>
|
|
552
|
-
|
|
493
|
+
{t('performance.emptySubtitle')}
|
|
553
494
|
</Text>
|
|
554
495
|
</View>) : (filteredEvents.map(event => {
|
|
555
496
|
const isOptimal = event.severity === 'optimal';
|
|
556
497
|
const isWarning = event.severity === 'warning';
|
|
557
498
|
const isCritical = event.severity === 'critical';
|
|
558
499
|
const isExpanded = !!expandedEventIds[event.id];
|
|
559
|
-
const badgeBg = isOptimal ?
|
|
560
|
-
const badgeBorder = isOptimal ?
|
|
561
|
-
const badgeColor = isOptimal ?
|
|
500
|
+
const badgeBg = isOptimal ? AppColors.greenBg : isWarning ? AppColors.amberBg : AppColors.errorCardBg;
|
|
501
|
+
const badgeBorder = isOptimal ? AppColors.greenBorder : isWarning ? AppColors.lightOrange : AppColors.errorBorder;
|
|
502
|
+
const badgeColor = isOptimal ? AppColors.emerald600 : isWarning ? AppColors.amber700 : AppColors.errorColor;
|
|
562
503
|
return (<TouchableScale key={event.id} onPress={() => toggleEventExpand(event.id)} style={[
|
|
563
504
|
perfStyles.eventCard,
|
|
564
|
-
isCritical && { borderColor:
|
|
505
|
+
isCritical && { borderColor: AppColors.errorBorder, backgroundColor: AppColors.errorCardBg },
|
|
565
506
|
]}>
|
|
566
507
|
<View style={perfStyles.eventTopRow}>
|
|
567
508
|
<View style={[
|
|
@@ -576,12 +517,12 @@ const PerformanceTab = React.memo(() => {
|
|
|
576
517
|
<View style={{ flex: 1, paddingHorizontal: 6 }}>
|
|
577
518
|
<HighlightText text={event.label} search={search} style={perfStyles.eventLabel} highlightStyle={perfStyles.searchHighlight}/>
|
|
578
519
|
<Text style={perfStyles.eventMeta}>
|
|
579
|
-
{event.durationMs
|
|
520
|
+
{t('performance.msTotal', { duration: event.durationMs, time: new Date(event.timestamp).toLocaleTimeString() })}
|
|
580
521
|
</Text>
|
|
581
522
|
</View>
|
|
582
523
|
|
|
583
524
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
584
|
-
<CopyButton value={() => event} label=
|
|
525
|
+
<CopyButton value={() => event} label={t('common.copyJson')}/>
|
|
585
526
|
<Text style={perfStyles.expandChevron}>
|
|
586
527
|
{isExpanded ? '▲' : '▼'}
|
|
587
528
|
</Text>
|
|
@@ -594,7 +535,7 @@ const PerformanceTab = React.memo(() => {
|
|
|
594
535
|
{isExpanded && (<View style={perfStyles.expandedDetailBox}>
|
|
595
536
|
{/* Source file / hook tag */}
|
|
596
537
|
{event.source && (<View style={perfStyles.sourceRow}>
|
|
597
|
-
<Text style={perfStyles.sourceTag}>
|
|
538
|
+
<Text style={perfStyles.sourceTag}>{t('common.source')}</Text>
|
|
598
539
|
<Text style={perfStyles.sourceText} numberOfLines={1}>
|
|
599
540
|
{event.source}
|
|
600
541
|
</Text>
|
|
@@ -603,28 +544,28 @@ const PerformanceTab = React.memo(() => {
|
|
|
603
544
|
{/* Thread Timing Breakdown */}
|
|
604
545
|
{event.breakdown && (<View style={perfStyles.breakdownGrid}>
|
|
605
546
|
<View style={perfStyles.breakdownCol}>
|
|
606
|
-
<Text style={perfStyles.breakdownLabel}>
|
|
547
|
+
<Text style={perfStyles.breakdownLabel}>{t('performance.jsThread')}</Text>
|
|
607
548
|
<Text style={perfStyles.breakdownVal}>
|
|
608
549
|
{event.breakdown.jsTimeMs} ms
|
|
609
550
|
</Text>
|
|
610
551
|
</View>
|
|
611
552
|
<View style={perfStyles.breakdownCol}>
|
|
612
|
-
<Text style={perfStyles.breakdownLabel}>
|
|
553
|
+
<Text style={perfStyles.breakdownLabel}>{t('performance.uiThread')}</Text>
|
|
613
554
|
<Text style={perfStyles.breakdownVal}>
|
|
614
555
|
{event.breakdown.uiTimeMs} ms
|
|
615
556
|
</Text>
|
|
616
557
|
</View>
|
|
617
558
|
{event.breakdown.bridgeLatencyMs != null && (<View style={perfStyles.breakdownCol}>
|
|
618
|
-
<Text style={perfStyles.breakdownLabel}>
|
|
559
|
+
<Text style={perfStyles.breakdownLabel}>{t('performance.jsiLatency')}</Text>
|
|
619
560
|
<Text style={perfStyles.breakdownVal}>
|
|
620
561
|
{event.breakdown.bridgeLatencyMs} ms
|
|
621
562
|
</Text>
|
|
622
563
|
</View>)}
|
|
623
564
|
{event.heapDeltaKb != null && (<View style={perfStyles.breakdownCol}>
|
|
624
|
-
<Text style={perfStyles.breakdownLabel}>
|
|
565
|
+
<Text style={perfStyles.breakdownLabel}>{t('performance.heapImpact')}</Text>
|
|
625
566
|
<Text style={[
|
|
626
567
|
perfStyles.breakdownVal,
|
|
627
|
-
{ color: event.heapDeltaKb > 0 ?
|
|
568
|
+
{ color: event.heapDeltaKb > 0 ? AppColors.amber700 : AppColors.emerald600 },
|
|
628
569
|
]}>
|
|
629
570
|
{event.heapDeltaKb > 0 ? `+${event.heapDeltaKb}` : event.heapDeltaKb} KB
|
|
630
571
|
</Text>
|
|
@@ -633,14 +574,14 @@ const PerformanceTab = React.memo(() => {
|
|
|
633
574
|
|
|
634
575
|
{/* Actionable Optimization Tip */}
|
|
635
576
|
{event.advice && (<View style={perfStyles.adviceCard}>
|
|
636
|
-
<Text style={perfStyles.adviceHeading}>💡
|
|
577
|
+
<Text style={perfStyles.adviceHeading}>💡 {t('performance.optimizationTip')}</Text>
|
|
637
578
|
<Text style={perfStyles.adviceBodyText}>{event.advice}</Text>
|
|
638
579
|
</View>)}
|
|
639
580
|
</View>)}
|
|
640
581
|
</TouchableScale>);
|
|
641
582
|
}))}
|
|
642
583
|
|
|
643
|
-
<EndOfListFooter count={filteredEvents.length} label=
|
|
584
|
+
<EndOfListFooter count={filteredEvents.length} label={t('performance.recordedEvents')}/>
|
|
644
585
|
</ScrollView>
|
|
645
586
|
</View>);
|
|
646
587
|
});
|
|
@@ -688,12 +629,12 @@ const perfStyles = StyleSheet.create({
|
|
|
688
629
|
borderWidth: 1,
|
|
689
630
|
},
|
|
690
631
|
recordBtnActive: {
|
|
691
|
-
backgroundColor:
|
|
692
|
-
borderColor:
|
|
632
|
+
backgroundColor: AppColors.greenBg,
|
|
633
|
+
borderColor: AppColors.greenBorder,
|
|
693
634
|
},
|
|
694
635
|
recordBtnPaused: {
|
|
695
|
-
backgroundColor:
|
|
696
|
-
borderColor:
|
|
636
|
+
backgroundColor: AppColors.amberBg,
|
|
637
|
+
borderColor: AppColors.lightOrange,
|
|
697
638
|
},
|
|
698
639
|
recordDot: {
|
|
699
640
|
width: 6,
|
|
@@ -852,7 +793,7 @@ const perfStyles = StyleSheet.create({
|
|
|
852
793
|
sparklineFooterTarget: {
|
|
853
794
|
fontFamily: AppFonts.interMedium,
|
|
854
795
|
fontSize: 10,
|
|
855
|
-
color:
|
|
796
|
+
color: AppColors.emerald600,
|
|
856
797
|
},
|
|
857
798
|
budgetCard: {
|
|
858
799
|
backgroundColor: AppColors.white,
|
|
@@ -875,7 +816,7 @@ const perfStyles = StyleSheet.create({
|
|
|
875
816
|
budgetHeadroom: {
|
|
876
817
|
fontFamily: AppFonts.interBold,
|
|
877
818
|
fontSize: 11,
|
|
878
|
-
color:
|
|
819
|
+
color: AppColors.emerald600,
|
|
879
820
|
},
|
|
880
821
|
budgetBarContainer: {
|
|
881
822
|
flexDirection: 'row',
|
|
@@ -1056,27 +997,27 @@ const perfStyles = StyleSheet.create({
|
|
|
1056
997
|
marginTop: 1,
|
|
1057
998
|
},
|
|
1058
999
|
adviceCard: {
|
|
1059
|
-
backgroundColor:
|
|
1000
|
+
backgroundColor: AppColors.purpleTintBg,
|
|
1060
1001
|
borderRadius: 6,
|
|
1061
1002
|
padding: 6,
|
|
1062
1003
|
borderWidth: 1,
|
|
1063
|
-
borderColor:
|
|
1004
|
+
borderColor: AppColors.purpleBorder,
|
|
1064
1005
|
},
|
|
1065
1006
|
adviceHeading: {
|
|
1066
1007
|
fontFamily: AppFonts.interBold,
|
|
1067
1008
|
fontSize: 10,
|
|
1068
|
-
color:
|
|
1009
|
+
color: AppColors.purpleShade700,
|
|
1069
1010
|
},
|
|
1070
1011
|
adviceBodyText: {
|
|
1071
1012
|
fontFamily: AppFonts.interRegular,
|
|
1072
1013
|
fontSize: 10.5,
|
|
1073
|
-
color:
|
|
1014
|
+
color: AppColors.purple,
|
|
1074
1015
|
lineHeight: 14,
|
|
1075
1016
|
marginTop: 1,
|
|
1076
1017
|
},
|
|
1077
1018
|
searchHighlight: {
|
|
1078
|
-
backgroundColor:
|
|
1079
|
-
color:
|
|
1019
|
+
backgroundColor: AppColors.yellowHighlight,
|
|
1020
|
+
color: AppColors.amber800,
|
|
1080
1021
|
fontFamily: AppFonts.interBold,
|
|
1081
1022
|
},
|
|
1082
1023
|
emptyCard: {
|