Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { Button, StyleSheet, View } from 'react-native';
import { useEffect, useState } from 'react';

import { scheduleOnUI } from 'react-native-worklets';

declare global {
var _startProfiling: (meanHzFreq?: number) => void;
var _stopProfiling: () => string;
}

const RN_RUNTIME_MEAN_HZ_FREQ = 100;
const UI_RUNTIME_MEAN_HZ_FREQ = 1200;

export default function HermesSamplingProfilerExample() {
const [isProfilingRN, setIsProfilingRN] = useState(false);
const [isProfilingUI, setIsProfilingUI] = useState(false);

const handleStartRNProfiling = () => {
if (isProfilingRN) return;
setIsProfilingRN(true);
globalThis._startProfiling(RN_RUNTIME_MEAN_HZ_FREQ);
};

const handleStopRNProfiling = () => {
if (!isProfilingRN) return;
setIsProfilingRN(false);
const path = globalThis._stopProfiling();
console.log(path);
};

const handleStartUIProfiling = () => {
if (isProfilingUI) return;
setIsProfilingUI(true);
scheduleOnUI(() => {
globalThis._startProfiling(UI_RUNTIME_MEAN_HZ_FREQ);
});
};

const handleStopUIProfiling = () => {
if (!isProfilingUI) return;
setIsProfilingUI(false);
scheduleOnUI(() => {
const path = globalThis._stopProfiling();
console.log(path);
});
};

useEffect(() => {
const id = setInterval(function sleepOnJSThread() {
const start = performance.now();
while (performance.now() - start < 100) {
// do nothing
}
}, 300);
return () => clearInterval(id);
}, []);

useEffect(() => {
const id = setInterval(() => {
scheduleOnUI(function sleepOnUIThread() {
const start = performance.now();
while (performance.now() - start < 100) {
// do nothing
}
});
}, 300);
return () => clearInterval(id);
}, []);

return (
<View style={styles.container}>
<Button
title="Start RN runtime profiling"
onPress={handleStartRNProfiling}
disabled={isProfilingRN}
/>
<Button
title="Stop RN runtime profiling"
onPress={handleStopRNProfiling}
disabled={!isProfilingRN}
/>
<Button
title="Start UI runtime profiling"
onPress={handleStartUIProfiling}
disabled={isProfilingUI}
/>
<Button
title="Stop UI runtime profiling"
onPress={handleStopUIProfiling}
disabled={!isProfilingUI}
/>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
});
6 changes: 6 additions & 0 deletions apps/common-app/src/apps/reanimated/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import DetachAnimatedStylesExample from './DetachAnimatedStylesExample';
import DurationZeroExample from './LayoutAnimations/DurationZero';
import FlatListSkipEnteringExiting from './LayoutAnimations/FlatListSkipEnteringExiting';
import HabitsExample from './LayoutAnimations/HabitsExample';
import HermesSamplingProfilerExample from './HermesSamplingProfilerExample';
import KeyframeAnimation from './LayoutAnimations/KeyframeAnimation';
import LayoutTransitionExample from './LayoutAnimations/LayoutTransitionExample';
import ListItemLayoutAnimation from './LayoutAnimations/ListItemLayoutAnimation';
Expand Down Expand Up @@ -199,6 +200,11 @@ export const EXAMPLES: Record<string, Example> = {
title: 'FPS',
screen: FpsExample,
},
HermesSamplingProfilerExample: {
icon: '📊',
title: 'Hermes sampling profiler',
screen: HermesSamplingProfilerExample,
},
SystraceSectionExample: {
icon: '📊',
title: 'Systrace section',
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native-reanimated/src/privateGlobals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ declare global {
var _REANIMATED_VERSION_JS: string | undefined;
var __reanimatedModuleProxy: ReanimatedModuleProxy | undefined;
var _log: (value: unknown) => void;
var _startProfiling: (meanHzFreq?: number) => void;
var _stopProfiling: () => string;
var _beginSection: (name: string) => void;
var _endSection: () => void;
var _notifyAboutProgress: (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include <worklets/Tools/Defs.h>
#include <worklets/WorkletRuntime/HermesProfiling.h>

#include <string>

#if JS_RUNTIME_HERMES
#include <hermes/hermes.h>

#include <chrono>
#include <filesystem>
#include <sstream>
#endif

namespace worklets {

#if JS_RUNTIME_HERMES
static std::string generateUniqueProfilePath() {
auto now = std::chrono::steady_clock::now().time_since_epoch().count();
std::ostringstream oss;
oss << "profile-" << now << ".cpuprofile";
std::filesystem::path dir = std::filesystem::temp_directory_path();
return (dir / oss.str()).string();
}
#endif

void startProfiling(facebook::jsi::Runtime &rt, double meanHzFreq) {
#if JS_RUNTIME_HERMES
#if REACT_NATIVE_MINOR_VERSION >= 81
auto *ihermes = facebook::jsi::castInterface<facebook::hermes::IHermes>(&rt);
if (ihermes) {
ihermes->registerForProfiling();
}
auto *api = facebook::jsi::castInterface<facebook::hermes::IHermesRootAPI>(facebook::hermes::makeHermesRootAPI());
if (api) {
api->enableSamplingProfiler(meanHzFreq);
}
#else
(void)rt;
(void)meanHzFreq;
facebook::hermes::HermesRuntime::enableSamplingProfiler();
#endif
#else
(void)rt;
(void)meanHzFreq;
#endif
}

std::string stopProfiling(facebook::jsi::Runtime &rt) {
#if JS_RUNTIME_HERMES
std::string path = generateUniqueProfilePath();
#if REACT_NATIVE_MINOR_VERSION >= 81
auto *api = facebook::jsi::castInterface<facebook::hermes::IHermesRootAPI>(facebook::hermes::makeHermesRootAPI());
if (api) {
api->dumpSampledTraceToFile(path);
api->disableSamplingProfiler();
}
auto *ihermes = facebook::jsi::castInterface<facebook::hermes::IHermes>(&rt);
if (ihermes) {
ihermes->unregisterForProfiling();
}
#else
(void)rt;
facebook::hermes::HermesRuntime::dumpSampledTraceToFile(path);
facebook::hermes::HermesRuntime::disableSamplingProfiler();
#endif
return path;
#else
(void)rt;
return {};
#endif
}

} // namespace worklets
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include <jsi/jsi.h>

#include <string>

namespace worklets {

void startProfiling(facebook::jsi::Runtime &rt, double meanHzFreq);
std::string stopProfiling(facebook::jsi::Runtime &rt);

} // namespace worklets
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#include <worklets/Tools/WorkletsJSIUtils.h>
#include <worklets/Tools/WorkletsVersion.h>
#include <worklets/WorkletRuntime/HermesProfiling.h>
#include <worklets/WorkletRuntime/RNRuntimeWorkletDecorator.h>
#include <worklets/WorkletRuntime/RuntimeKind.h>
#include <worklets/WorkletRuntime/WorkletRuntimeCollector.h>

#include <memory>
#include <string>
#include <utility>

namespace worklets {
Expand Down Expand Up @@ -34,6 +36,24 @@ void RNRuntimeWorkletDecorator::decorate(
#endif // IS_REANIMATED_EXAMPLE_APP

injectWorkletsCppVersion(rnRuntime);

rnRuntime.global().setProperty(
rnRuntime,
"_startProfiling",
jsi::Function::createFromHostFunction(
rnRuntime,
jsi::PropNameID::forAscii(rnRuntime, "_startProfiling"),
1,
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) {
const double meanHzFreq = (count > 0 && !args[0].isUndefined()) ? args[0].asNumber() : 100.0;
startProfiling(rt, meanHzFreq);
return jsi::Value::undefined();
}));

jsi_utils::installJsiFunction(rnRuntime, "_stopProfiling", [](jsi::Runtime &rt) {
std::string path = stopProfiling(rt);
return jsi::String::createFromUtf8(rt, path);
});
}

#ifdef IS_REANIMATED_EXAMPLE_APP
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include <worklets/SharedItems/Serializable.h>
#include <worklets/SharedItems/SerializableFactory.h>
#include <worklets/Tools/Defs.h>
#include <worklets/Tools/JSISerializer.h>
#include <worklets/Tools/PlatformLogger.h>
#include <worklets/Tools/WorkletsJSIUtils.h>
#include <worklets/WorkletRuntime/HermesProfiling.h>
#include <worklets/WorkletRuntime/RuntimeKind.h>
#include <worklets/WorkletRuntime/WorkletRuntime.h>
#include <worklets/WorkletRuntime/WorkletRuntimeDecorator.h>
Expand All @@ -19,6 +21,7 @@
#endif
#endif

#include <chrono>
#include <memory>
#include <string>
#include <utility>
Expand Down Expand Up @@ -262,6 +265,25 @@ void WorkletRuntimeDecorator::decorate(
}));
rt.global().setProperty(rt, "performance", performance);

#if JS_RUNTIME_HERMES
rt.global().setProperty(
rt,
"_startProfiling",
jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "_startProfiling"),
1,
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) {
const double meanHzFreq = (count > 0 && !args[0].isUndefined()) ? args[0].asNumber() : 100.0;
startProfiling(rt, meanHzFreq);
return jsi::Value::undefined();
}));
jsi_utils::installJsiFunction(rt, "_stopProfiling", [](jsi::Runtime &rt) {
std::string path = stopProfiling(rt);
return jsi::String::createFromUtf8(rt, path);
});
#endif // JS_RUNTIME_HERMES

jsi_utils::installJsiFunction(
rt,
"_scheduleTimeoutCallback",
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native-worklets/src/privateGlobals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ declare global {
var __flushAnimationFrame: (timestamp: number) => void;
var __frameTimestamp: number | undefined;
var _log: (value: unknown) => void;
var _startProfiling: (meanHzFreq?: number) => void;
var _stopProfiling: () => string;
var _beginSection: (name: string) => void;
var _endSection: () => void;
var _getAnimationTimestamp: () => number;
Expand Down
Loading