From 6548b18de2de1e8b01156c1c0d76d33bf2b8d2a7 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 25 Feb 2026 01:33:38 -0800 Subject: [PATCH] Add C++ sampling profiler support with FANTOM_PROFILE_CPP Summary: Add support for profiling C++ code in Fantom tests using Linux `perf`. When `FANTOM_PROFILE_CPP=1` is set, the fantom_tester binary is wrapped with `perf record` to capture sampling profiler data with DWARF call graphs. Usage: ``` FANTOM_PROFILE_CPP=1 yarn fantom --benchmarks --testPathPattern="View-benchmark" ``` Output is saved to `.out/cpp-traces/perf-.data` and can be analyzed with: ``` perf report -i .out/cpp-traces/perf-.data ``` Differential Revision: D92155573 --- .../runner/EnvironmentOptions.js | 7 +++ .../runner/executables/tester.js | 51 +++++++++++++++++-- private/react-native-fantom/runner/paths.js | 4 ++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/private/react-native-fantom/runner/EnvironmentOptions.js b/private/react-native-fantom/runner/EnvironmentOptions.js index 553049aacaa8..5e164e32f8e2 100644 --- a/private/react-native-fantom/runner/EnvironmentOptions.js +++ b/private/react-native-fantom/runner/EnvironmentOptions.js @@ -20,6 +20,7 @@ const VALID_ENVIRONMENT_VARIABLES = [ 'FANTOM_PRINT_OUTPUT', 'FANTOM_DEBUG_JS', 'FANTOM_PROFILE_JS', + 'FANTOM_PROFILE_CPP', 'FANTOM_ENABLE_JS_MEMORY_INSTRUMENTATION', ]; @@ -69,6 +70,12 @@ export const debugJS: boolean = Boolean(process.env.FANTOM_DEBUG_JS); export const profileJS: boolean = Boolean(process.env.FANTOM_PROFILE_JS); +/** + * Enables C++ sampling profiler using Linux perf. + * Saves perf.data files to .out/cpp-traces/ for analysis. + */ +export const profileCpp: boolean = Boolean(process.env.FANTOM_PROFILE_CPP); + /** * Enables address sanitizer (ASAN) build mode for the C++ side. */ diff --git a/private/react-native-fantom/runner/executables/tester.js b/private/react-native-fantom/runner/executables/tester.js index b3459c802f35..5f051de1a56f 100644 --- a/private/react-native-fantom/runner/executables/tester.js +++ b/private/react-native-fantom/runner/executables/tester.js @@ -10,8 +10,8 @@ import type {AsyncCommandResult, HermesVariant} from '../utils'; -import {debugCpp, isCI} from '../EnvironmentOptions'; -import {NATIVE_BUILD_OUTPUT_PATH} from '../paths'; +import {debugCpp, isCI, profileCpp} from '../EnvironmentOptions'; +import {CPP_TRACES_OUTPUT_PATH, NATIVE_BUILD_OUTPUT_PATH} from '../paths'; import { getBuckModesForPlatform, getBuckOptionsForHermes, @@ -84,6 +84,10 @@ export function run( throw new Error('Cannot run Fantom with C++ debugging on CI'); } + if (isCI && profileCpp) { + throw new Error('Cannot run Fantom with C++ profiling on CI'); + } + if (!isCI && !debugCpp) { build(options); } @@ -104,5 +108,46 @@ export function run( ); } - return runCommand(getFantomTesterPath(options), args); + const testerPath = getFantomTesterPath(options); + + if (profileCpp) { + // Ensure output directory exists + if (!fs.existsSync(CPP_TRACES_OUTPUT_PATH)) { + fs.mkdirSync(CPP_TRACES_OUTPUT_PATH, {recursive: true}); + } + + // Generate unique output path for perf data + const perfOutputPath = path.join( + CPP_TRACES_OUTPUT_PATH, + `perf-${Date.now()}.data`, + ); + + // Wrap command with perf record + // -g: enable call-graph (stack traces) + // -F 997: sample at 997 Hz (prime number to avoid aliasing) + // --call-graph dwarf: use DWARF for accurate stack traces + const result = runCommand('perf', [ + 'record', + '-g', + '-F', + '997', + '--call-graph', + 'dwarf', + '-o', + perfOutputPath, + '--', + testerPath, + ...args, + ]); + + // Log the output path after the command starts + console.log( + `\n🔥 C++ sampling profiler recording to: ${perfOutputPath}\n` + + ` View with: perf report -i ${perfOutputPath}\n`, + ); + + return result; + } + + return runCommand(testerPath, args); } diff --git a/private/react-native-fantom/runner/paths.js b/private/react-native-fantom/runner/paths.js index 43bacc1d7c5a..aaf2a6f9be8c 100644 --- a/private/react-native-fantom/runner/paths.js +++ b/private/react-native-fantom/runner/paths.js @@ -25,6 +25,10 @@ export const JS_TRACES_OUTPUT_PATH: string = path.join( OUTPUT_PATH, 'js-traces', ); +export const CPP_TRACES_OUTPUT_PATH: string = path.join( + OUTPUT_PATH, + 'cpp-traces', +); export const JS_HEAP_SNAPSHOTS_OUTPUT_PATH: string = path.join( OUTPUT_PATH, 'js-heap-snapshots',