This guide explains how to capture performance profiles from your React Native application using react-native-release-profiler
and seamlessly upload them to Flamedeck using the @flamedeck/upload
package.
This is particularly useful for understanding the performance characteristics of your app as it runs on a real device or in a production-like environment.
Prerequisites
Install necessary packages
yarn add @flamedeck/upload react-native-release-profiler expo-file-system
# or
npm install @flamedeck/upload react-native-release-profiler expo-file-system
@flamedeck/upload
: For uploading the trace to Flamedeck
react-native-release-profiler
: For capturing the performance profile from your React Native app
expo-file-system
(if using Expo, or a similar file system module for vanilla React Native): For reading the profile file from device storage
Get a Flamedeck API Key
You’ll need an API key from Flamedeck to upload traces. You can obtain one from your Flamedeck settings page.
Remember to replace 'YOUR_FLAMEDECK_API_KEY'
in the code example with your actual key.
Integration Steps
The core logic involves starting the profiler, executing the code you want to measure, stopping the profiler, reading the generated trace file, and then uploading it.
Example Implementation
Here’s a function demonstrating these steps. You can adapt this and trigger it, for example, from a button press in your development builds or testing scenarios.
import { Alert } from 'react-native';
import { UploadError, UploadOptions, uploadTraceToApi } from '@flamedeck/upload';
import * as FileSystem from 'expo-file-system'; // For Expo projects
import { startProfiling, stopProfiling } from 'react-native-release-profiler';
// Helper function to convert base64 to ArrayBuffer
const base64ToArrayBuffer = (base64: string): ArrayBuffer => {
const binaryString = atob(base64); // atob is available in React Native
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
};
// Function to capture and upload a profile
const captureAndUploadProfile = async () => {
Alert.alert('Profiling Started', 'An operation is now being profiled.');
try {
startProfiling();
// --- Your Code to Profile Starts ---
// Example: Simulate a heavy computation or a specific app flow
// Replace this with the actual operations you want to measure
for (let i = 0; i < 50000000; i++) {
// This loop will take some time
}
// --- Your Code to Profile Ends ---
const profilePath = await stopProfiling(true); // true to auto-generate a file name
if (profilePath) {
const apiKey = 'YOUR_FLAMEDECK_API_KEY'; // Replace with your actual API key
const fileUri = `file://${profilePath}`;
const fileInfo = await FileSystem.getInfoAsync(fileUri);
if (!fileInfo.exists) {
throw new Error('Trace file does not exist at the reported path.');
}
if (fileInfo.size === 0) {
throw new Error('Trace file is empty. Profiling might not have captured data.');
}
const base64Data = await FileSystem.readAsStringAsync(fileUri, {
encoding: FileSystem.EncodingType.Base64,
});
if (!base64Data) {
throw new Error('Failed to read trace file as base64.');
}
const traceData = base64ToArrayBuffer(base64Data);
if (traceData.byteLength === 0) {
throw new Error(
'Converted ArrayBuffer is empty. Problem with base64 conversion or original data.'
);
}
const fileName = profilePath.substring(profilePath.lastIndexOf('/') + 1);
const options: UploadOptions = {
apiKey: apiKey,
traceData: traceData,
fileName: fileName,
scenario: 'React Native Performance Profile', // Customize as needed
notes: 'Trace captured from a React Native app using react-native-release-profiler.',
public: false, // Set to true if you want the trace to be publicly accessible
};
const result = await uploadTraceToApi(options);
Alert.alert(
'Upload Successful',
`Trace uploaded!\nID: ${result.id}\nView: ${result.viewUrl}`
);
} else {
Alert.alert('Profiling Failed', 'Could not get profile path.');
}
} catch (error) {
console.error('Profiling or Upload Error:', error);
let alertMessage = 'An unexpected error occurred.';
if (error instanceof UploadError) {
alertMessage = `Flamedeck Upload Error: ${error.message} (Status: ${error.status})`;
if (error.details) console.error('Flamedeck Details:', error.details);
} else if (error instanceof Error) {
alertMessage = error.message;
}
Alert.alert('Error', alertMessage);
}
};
Key Implementation Notes
File System Access: The example uses expo-file-system
for Expo projects. If you’re using vanilla React Native, you’ll need to use a different file system library like react-native-fs
.
Development vs Production: Consider adding environment checks to only enable profiling in development or specific testing builds to avoid impacting production performance.
API Key Security: Never hardcode your API key in production builds. Use environment variables or secure storage mechanisms to handle API keys safely.
Usage Tips
- Trigger Profiling: You can trigger the
captureAndUploadProfile
function from a button press, gesture, or specific app event
- Profile Specific Flows: Replace the example loop with the actual user flows or operations you want to profile
- Customize Metadata: Add relevant metadata like user ID, app version, or device information to help with analysis
- Error Handling: The example includes comprehensive error handling for common issues like empty files or upload failures
Next Steps
Once you’ve uploaded your React Native traces to Flamedeck, you can:
- Analyze performance bottlenecks in your app
- Compare performance across different devices or app versions
- Share traces with your team for collaborative debugging
- Set up automated profiling in your CI/CD pipeline