mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Use std::variant, remove heap allocations (#50847)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50847 # Changelog: [Internal] We have different types of call frames, right now we defined 4 of them. Previously, they would descend from `ProfileSampleCallStackFrame`, which has `kind_` field that can be used for determining the type of the call frame. We were using polymorphism later on React Native side to cast from base type to derived. Instead of this, and instead of doing heap allocations for potentially tens of thousands of objects, we will use std::variant for storing frames in a single container and them distinguishing them. This fixes memory leaks caused by new-ing objects and not clearing out when Profile is destroyed. Reviewed By: dannysu Differential Revision: D72803934 fbshipit-source-id: a279c6d68c7c2628f0eab585f33137222ad9e3f1
This commit is contained in:
committed by
Facebook GitHub Bot
parent
88a04c530e
commit
c09340e73e
+98
-103
@@ -5,12 +5,16 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "HermesRuntimeSamplingProfileSerializer.h"
|
||||
|
||||
namespace facebook::react::jsinspector_modern::tracing {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace fhsp = facebook::hermes::sampling_profiler;
|
||||
|
||||
/// Fallback script ID for call frames, when Hermes didn't provide one or when
|
||||
/// this frame is part of the VM, like native functions, used for parity with
|
||||
/// Chromium + V8.
|
||||
@@ -21,123 +25,114 @@ const std::string GARBAGE_COLLECTOR_FRAME_NAME = "(garbage collector)";
|
||||
/// Filters out Hermes Suspend frames related to Debugger.
|
||||
/// Even though Debugger domain is expected to be disabled, Hermes might run
|
||||
/// Debugger loop while recording sampling profile. We only allow GC frames.
|
||||
bool shouldIgnoreHermesFrame(
|
||||
hermes::sampling_profiler::ProfileSampleCallStackFrame* hermesFrame) {
|
||||
if (hermesFrame->getKind() !=
|
||||
hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind::Suspend) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* suspendFrame = static_cast<
|
||||
hermes::sampling_profiler::ProfileSampleCallStackSuspendFrame*>(
|
||||
hermesFrame);
|
||||
auto suspendFrameKind = suspendFrame->getSuspendFrameKind();
|
||||
return suspendFrameKind !=
|
||||
hermes::sampling_profiler::ProfileSampleCallStackSuspendFrame::
|
||||
SuspendFrameKind::GC;
|
||||
inline bool shouldIgnoreHermesFrame(
|
||||
const fhsp::ProfileSampleCallStackSuspendFrame& suspendFrame) {
|
||||
return suspendFrame.getSuspendFrameKind() !=
|
||||
fhsp::ProfileSampleCallStackSuspendFrame::SuspendFrameKind::GC;
|
||||
}
|
||||
|
||||
RuntimeSamplingProfile::SampleCallStackFrame convertHermesFrameToTracingFrame(
|
||||
hermes::sampling_profiler::ProfileSampleCallStackFrame* hermesFrame) {
|
||||
switch (hermesFrame->getKind()) {
|
||||
case hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind::
|
||||
JSFunction: {
|
||||
auto* jsFunctionFrame = static_cast<
|
||||
hermes::sampling_profiler::ProfileSampleCallStackJSFunctionFrame*>(
|
||||
hermesFrame);
|
||||
return RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction,
|
||||
jsFunctionFrame->hasScriptId() ? jsFunctionFrame->getScriptId()
|
||||
: FALLBACK_SCRIPT_ID,
|
||||
jsFunctionFrame->getFunctionName(),
|
||||
jsFunctionFrame->hasUrl()
|
||||
? std::optional<std::string>{jsFunctionFrame->getUrl()}
|
||||
: std::nullopt,
|
||||
jsFunctionFrame->hasLineNumber()
|
||||
? std::optional<uint32_t>{jsFunctionFrame->getLineNumber() - 1}
|
||||
// Hermes VM keeps line numbers as 1-based. Convert to
|
||||
// 0-based.
|
||||
: std::nullopt,
|
||||
jsFunctionFrame->hasColumnNumber()
|
||||
? std::optional<uint32_t>{jsFunctionFrame->getColumnNumber() - 1}
|
||||
// Hermes VM keeps column numbers as 1-based. Convert to
|
||||
// 0-based.
|
||||
: std::nullopt,
|
||||
};
|
||||
}
|
||||
case hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind::
|
||||
NativeFunction: {
|
||||
auto* nativeFunctionFrame =
|
||||
static_cast<hermes::sampling_profiler::
|
||||
ProfileSampleCallStackNativeFunctionFrame*>(
|
||||
hermesFrame);
|
||||
RuntimeSamplingProfile::SampleCallStackFrame convertNativeHermesFrame(
|
||||
const fhsp::ProfileSampleCallStackNativeFunctionFrame& frame) {
|
||||
return RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::NativeFunction,
|
||||
FALLBACK_SCRIPT_ID, // JavaScript Runtime defines the implementation
|
||||
// for native function, no script ID to reference.
|
||||
frame.getFunctionName(),
|
||||
};
|
||||
}
|
||||
|
||||
return RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::NativeFunction,
|
||||
FALLBACK_SCRIPT_ID, // JavaScript Runtime defines the implementation
|
||||
// for native function, no script ID to reference.
|
||||
nativeFunctionFrame->getFunctionName(),
|
||||
};
|
||||
}
|
||||
case hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind::
|
||||
HostFunction: {
|
||||
auto* hostFunctionFrame = static_cast<
|
||||
hermes::sampling_profiler::ProfileSampleCallStackHostFunctionFrame*>(
|
||||
hermesFrame);
|
||||
RuntimeSamplingProfile::SampleCallStackFrame convertHostFunctionHermesFrame(
|
||||
const fhsp::ProfileSampleCallStackHostFunctionFrame& frame) {
|
||||
return RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::HostFunction,
|
||||
FALLBACK_SCRIPT_ID, // JavaScript Runtime defines the implementation
|
||||
// for host function, no script ID to reference.
|
||||
frame.getFunctionName(),
|
||||
};
|
||||
}
|
||||
|
||||
return RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::HostFunction,
|
||||
FALLBACK_SCRIPT_ID, // JavaScript Runtime defines the implementation
|
||||
// for host function, no script ID to reference.
|
||||
hostFunctionFrame->getFunctionName(),
|
||||
};
|
||||
}
|
||||
case hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind::
|
||||
Suspend: {
|
||||
auto* suspendFrame = static_cast<
|
||||
hermes::sampling_profiler::ProfileSampleCallStackSuspendFrame*>(
|
||||
hermesFrame);
|
||||
auto suspendFrameKind = suspendFrame->getSuspendFrameKind();
|
||||
if (suspendFrameKind ==
|
||||
hermes::sampling_profiler::ProfileSampleCallStackSuspendFrame::
|
||||
SuspendFrameKind::GC) {
|
||||
return RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::
|
||||
GarbageCollector,
|
||||
FALLBACK_SCRIPT_ID, // GC frames are part of the VM, no script ID to
|
||||
// reference.
|
||||
GARBAGE_COLLECTOR_FRAME_NAME,
|
||||
};
|
||||
}
|
||||
|
||||
// We should have filtered out Debugger Suspend frames before in
|
||||
// shouldFilterOutHermesFrame().
|
||||
throw std::logic_error{
|
||||
"Unexpected Suspend frame found in Hermes call stack"};
|
||||
}
|
||||
|
||||
default:
|
||||
throw std::logic_error{"Unknown Hermes stack frame kind"};
|
||||
RuntimeSamplingProfile::SampleCallStackFrame convertSuspendHermesFrame(
|
||||
const fhsp::ProfileSampleCallStackSuspendFrame& frame) {
|
||||
if (frame.getSuspendFrameKind() ==
|
||||
fhsp::ProfileSampleCallStackSuspendFrame::SuspendFrameKind::GC) {
|
||||
return RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector,
|
||||
FALLBACK_SCRIPT_ID, // GC frames are part of the VM, no script ID to
|
||||
// reference.
|
||||
GARBAGE_COLLECTOR_FRAME_NAME,
|
||||
};
|
||||
}
|
||||
|
||||
// We should have filtered out Debugger Suspend frames before in
|
||||
// shouldFilterOutHermesFrame().
|
||||
throw std::logic_error{"Unexpected Suspend frame found in Hermes call stack"};
|
||||
}
|
||||
|
||||
RuntimeSamplingProfile::SampleCallStackFrame convertJSFunctionHermesFrame(
|
||||
const fhsp::ProfileSampleCallStackJSFunctionFrame& frame) {
|
||||
return RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction,
|
||||
frame.hasScriptId() ? frame.getScriptId() : FALLBACK_SCRIPT_ID,
|
||||
frame.getFunctionName(),
|
||||
frame.hasUrl() ? std::optional<std::string>{frame.getUrl()}
|
||||
: std::nullopt,
|
||||
frame.hasLineNumber() ? std::optional<uint32_t>{frame.getLineNumber() - 1}
|
||||
// Hermes VM keeps line numbers as 1-based. Convert
|
||||
// to 0-based.
|
||||
: std::nullopt,
|
||||
frame.hasColumnNumber()
|
||||
? std::optional<uint32_t>{frame.getColumnNumber() - 1}
|
||||
// Hermes VM keeps column numbers as 1-based. Convert to
|
||||
// 0-based.
|
||||
: std::nullopt,
|
||||
};
|
||||
}
|
||||
|
||||
RuntimeSamplingProfile::Sample convertHermesSampleToTracingSample(
|
||||
const hermes::sampling_profiler::ProfileSample& hermesSample) {
|
||||
const fhsp::ProfileSample& hermesSample) {
|
||||
uint64_t reconciledTimestamp = hermesSample.getTimestamp();
|
||||
std::vector<hermes::sampling_profiler::ProfileSampleCallStackFrame*>
|
||||
hermesSampleCallStack = hermesSample.getCallStack();
|
||||
const std::vector<fhsp::ProfileSampleCallStackFrame>& hermesSampleCallStack =
|
||||
hermesSample.getCallStack();
|
||||
|
||||
std::vector<RuntimeSamplingProfile::SampleCallStackFrame>
|
||||
reconciledSampleCallStack;
|
||||
reconciledSampleCallStack.reserve(hermesSampleCallStack.size());
|
||||
|
||||
for (auto* hermesFrame : hermesSampleCallStack) {
|
||||
if (shouldIgnoreHermesFrame(hermesFrame)) {
|
||||
continue;
|
||||
for (const auto& hermesFrame : hermesSampleCallStack) {
|
||||
if (std::holds_alternative<fhsp::ProfileSampleCallStackSuspendFrame>(
|
||||
hermesFrame)) {
|
||||
const auto& suspendFrame =
|
||||
std::get<fhsp::ProfileSampleCallStackSuspendFrame>(hermesFrame);
|
||||
if (shouldIgnoreHermesFrame(suspendFrame)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
reconciledSampleCallStack.emplace_back(
|
||||
convertSuspendHermesFrame(suspendFrame));
|
||||
} else if (std::holds_alternative<
|
||||
fhsp::ProfileSampleCallStackNativeFunctionFrame>(
|
||||
hermesFrame)) {
|
||||
const auto& nativeFunctionFrame =
|
||||
std::get<fhsp::ProfileSampleCallStackNativeFunctionFrame>(
|
||||
hermesFrame);
|
||||
reconciledSampleCallStack.emplace_back(
|
||||
convertNativeHermesFrame(nativeFunctionFrame));
|
||||
} else if (std::holds_alternative<
|
||||
fhsp::ProfileSampleCallStackHostFunctionFrame>(
|
||||
hermesFrame)) {
|
||||
const auto& hostFunctionFrame =
|
||||
std::get<fhsp::ProfileSampleCallStackHostFunctionFrame>(hermesFrame);
|
||||
reconciledSampleCallStack.emplace_back(
|
||||
convertHostFunctionHermesFrame(hostFunctionFrame));
|
||||
} else if (std::holds_alternative<
|
||||
fhsp::ProfileSampleCallStackJSFunctionFrame>(hermesFrame)) {
|
||||
const auto& jsFunctionFrame =
|
||||
std::get<fhsp::ProfileSampleCallStackJSFunctionFrame>(hermesFrame);
|
||||
reconciledSampleCallStack.emplace_back(
|
||||
convertJSFunctionHermesFrame(jsFunctionFrame));
|
||||
} else {
|
||||
throw std::logic_error{"Unknown Hermes stack frame kind"};
|
||||
}
|
||||
RuntimeSamplingProfile::SampleCallStackFrame reconciledFrame =
|
||||
convertHermesFrameToTracingFrame(hermesFrame);
|
||||
reconciledSampleCallStack.push_back(std::move(reconciledFrame));
|
||||
}
|
||||
|
||||
return RuntimeSamplingProfile::Sample{
|
||||
@@ -156,7 +151,7 @@ HermesRuntimeSamplingProfileSerializer::serializeToTracingSamplingProfile(
|
||||
std::vector<RuntimeSamplingProfile::Sample> reconciledSamples;
|
||||
reconciledSamples.reserve(hermesSamples.size());
|
||||
|
||||
for (auto& hermesSample : hermesSamples) {
|
||||
for (const auto& hermesSample : hermesSamples) {
|
||||
RuntimeSamplingProfile::Sample reconciledSample =
|
||||
convertHermesSampleToTracingSample(hermesSample);
|
||||
reconciledSamples.push_back(std::move(reconciledSample));
|
||||
|
||||
Reference in New Issue
Block a user