Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialize Hermes Profile to Tracing Profile #49082

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -70,6 +70,7 @@ Pod::Spec.new do |s|
s.dependency 'React-RCTBlob'
s.dependency "SocketRocket", socket_rocket_version
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')

add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
3 changes: 2 additions & 1 deletion packages/react-native/React/Runtime/React-RCTRuntime.podspec
Original file line number Diff line number Diff line change
@@ -67,6 +67,7 @@ Pod::Spec.new do |s|
s.dependency "React-jsi"
add_dependency(s, "React-jsitooling", :framework_name => "JSITooling")
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')

add_dependency(s, "React-RuntimeCore")
add_dependency(s, "React-RuntimeApple")
@@ -77,7 +78,7 @@ Pod::Spec.new do |s|
s.exclude_files = "RCTJscInstanceFactory.{h,mm}"
elsif ENV['USE_THIRD_PARTY_JSC'] == '1'
s.exclude_files = ["RCTHermesInstanceFactory.{mm,h}", "RCTJscInstanceFactory.{mm,h}"]
else
else
s.exclude_files = ["RCTHermesInstanceFactory.{mm,h}"]
end
depend_on_js_engine(s)
Original file line number Diff line number Diff line change
@@ -48,6 +48,7 @@ Pod::Spec.new do |s|
s.dependency "RCT-Folly", folly_version
s.dependency "glog"
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
s.dependency "React-callinvoker", version
s.dependency "React-runtimeexecutor", version
s.dependency "React-perflogger", version
Original file line number Diff line number Diff line change
@@ -44,6 +44,7 @@ Pod::Spec.new do |s|
s.dependency "React-cxxreact", version
s.dependency "React-jsiexecutor", version
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
s.dependency "React-perflogger", version
s.dependency "RCT-Folly", folly_version
s.dependency "DoubleConversion"
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include "HermesRuntimeSamplingProfileSerializer.h"

namespace facebook::react::jsinspector_modern::tracing {

namespace {

/// 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.
const uint32_t FALLBACK_SCRIPT_ID = 0;
/// Garbage collector frame name, used for parity with Chromium + V8.
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;
}

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);

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);

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::Sample convertHermesSampleToTracingSample(
hermes::sampling_profiler::ProfileSample& hermesSample) {
uint64_t reconciledTimestamp = hermesSample.getTimestamp();
std::vector<hermes::sampling_profiler::ProfileSampleCallStackFrame*>
hermesSampleCallStack = hermesSample.getCallStack();

std::vector<RuntimeSamplingProfile::SampleCallStackFrame>
reconciledSampleCallStack;
reconciledSampleCallStack.reserve(hermesSampleCallStack.size());

for (auto* hermesFrame : hermesSampleCallStack) {
if (shouldIgnoreHermesFrame(hermesFrame)) {
continue;
}
RuntimeSamplingProfile::SampleCallStackFrame reconciledFrame =
convertHermesFrameToTracingFrame(hermesFrame);
reconciledSampleCallStack.push_back(std::move(reconciledFrame));
}

return RuntimeSamplingProfile::Sample{
reconciledTimestamp,
hermesSample.getThreadId(),
std::move(reconciledSampleCallStack)};
}

} // namespace

/* static */ RuntimeSamplingProfile
HermesRuntimeSamplingProfileSerializer::serializeToTracingSamplingProfile(
const hermes::sampling_profiler::Profile& hermesProfile) {
std::vector<hermes::sampling_profiler::ProfileSample> hermesSamples =
hermesProfile.getSamples();
std::vector<RuntimeSamplingProfile::Sample> reconciledSamples;
reconciledSamples.reserve(hermesSamples.size());

for (auto& hermesSample : hermesSamples) {
RuntimeSamplingProfile::Sample reconciledSample =
convertHermesSampleToTracingSample(hermesSample);
reconciledSamples.push_back(std::move(reconciledSample));
}

return RuntimeSamplingProfile{"Hermes", std::move(reconciledSamples)};
}

} // namespace facebook::react::jsinspector_modern::tracing
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <hermes/hermes.h>

#include <jsinspector-modern/tracing/RuntimeSamplingProfile.h>

namespace facebook::react::jsinspector_modern::tracing {

class HermesRuntimeSamplingProfileSerializer {
public:
static tracing::RuntimeSamplingProfile serializeToTracingSamplingProfile(
const hermes::sampling_profiler::Profile& hermesProfile);
};

} // namespace facebook::react::jsinspector_modern::tracing
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@

#include <jsinspector-modern/RuntimeTarget.h>

#include "HermesRuntimeSamplingProfileSerializer.h"
#include "HermesRuntimeTargetDelegate.h"

// If HERMES_ENABLE_DEBUGGER isn't defined, we can't access any Hermes
@@ -28,6 +29,12 @@ using namespace facebook::hermes;
namespace facebook::react::jsinspector_modern {

#ifdef HERMES_ENABLE_DEBUGGER
namespace {

const uint16_t HERMES_SAMPLING_FREQUENCY_HZ = 1000;

} // namespace

class HermesRuntimeTargetDelegate::Impl final : public RuntimeTargetDelegate {
using HermesStackTrace = debugger::StackTrace;

@@ -167,6 +174,20 @@ class HermesRuntimeTargetDelegate::Impl final : public RuntimeTargetDelegate {
runtime_->getDebugger().captureStackTrace());
}

void enableSamplingProfiler() override {
runtime_->enableSamplingProfiler(HERMES_SAMPLING_FREQUENCY_HZ);
}

void disableSamplingProfiler() override {
runtime_->disableSamplingProfiler();
}

tracing::RuntimeSamplingProfile collectSamplingProfile() override {
return tracing::HermesRuntimeSamplingProfileSerializer::
serializeToTracingSamplingProfile(
runtime_->dumpSampledTraceToProfile());
}

private:
HermesRuntimeTargetDelegate& delegate_;
std::shared_ptr<HermesRuntime> runtime_;
@@ -228,6 +249,19 @@ std::unique_ptr<StackTrace> HermesRuntimeTargetDelegate::captureStackTrace(
return impl_->captureStackTrace(runtime, framesToSkip);
}

void HermesRuntimeTargetDelegate::enableSamplingProfiler() {
impl_->enableSamplingProfiler();
}

void HermesRuntimeTargetDelegate::disableSamplingProfiler() {
impl_->disableSamplingProfiler();
}

tracing::RuntimeSamplingProfile
HermesRuntimeTargetDelegate::collectSamplingProfile() {
return impl_->collectSamplingProfile();
}

#ifdef HERMES_ENABLE_DEBUGGER
CDPDebugAPI& HermesRuntimeTargetDelegate::getCDPDebugAPI() {
return impl_->getCDPDebugAPI();
Original file line number Diff line number Diff line change
@@ -54,6 +54,12 @@ class HermesRuntimeTargetDelegate : public RuntimeTargetDelegate {
jsi::Runtime& runtime,
size_t framesToSkip) override;

void enableSamplingProfiler() override;

void disableSamplingProfiler() override;

tracing::RuntimeSamplingProfile collectSamplingProfile() override;

private:
// We use the private implementation idiom to ensure this class has the same
// layout regardless of whether HERMES_ENABLE_DEBUGGER is defined. The net
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ Pod::Spec.new do |s|
s.dependency "fmt", "11.0.2"
s.dependency "glog"
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')

add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1"
s.dependency 'hermes-engine'
end
Original file line number Diff line number Diff line change
@@ -44,4 +44,18 @@ std::unique_ptr<StackTrace> FallbackRuntimeTargetDelegate::captureStackTrace(
return std::make_unique<StackTrace>();
}

void FallbackRuntimeTargetDelegate::enableSamplingProfiler() {
// no-op
};

void FallbackRuntimeTargetDelegate::disableSamplingProfiler() {
// no-op
};

tracing::RuntimeSamplingProfile
FallbackRuntimeTargetDelegate::collectSamplingProfile() {
throw std::logic_error(
"Sampling Profiler capabilities are not supported for Runtime fallback");
}

} // namespace facebook::react::jsinspector_modern
Original file line number Diff line number Diff line change
@@ -40,6 +40,12 @@ class FallbackRuntimeTargetDelegate : public RuntimeTargetDelegate {
jsi::Runtime& runtime,
size_t framesToSkip) override;

void enableSamplingProfiler() override;

void disableSamplingProfiler() override;

tracing::RuntimeSamplingProfile collectSamplingProfile() override;

private:
std::string engineDescription_;
};
Original file line number Diff line number Diff line change
@@ -237,8 +237,11 @@ void HostAgent::sendInfoLogEntry(

void HostAgent::setCurrentInstanceAgent(
std::shared_ptr<InstanceAgent> instanceAgent) {
tracingAgent_.setCurrentInstanceAgent(instanceAgent);

auto previousInstanceAgent = std::move(instanceAgent_);
instanceAgent_ = std::move(instanceAgent);

if (!sessionState_.isRuntimeDomainEnabled) {
return;
}
Original file line number Diff line number Diff line change
@@ -152,4 +152,24 @@ void InstanceAgent::maybeSendPendingConsoleMessages() {
}
}

void InstanceAgent::startTracing() {
if (runtimeAgent_) {
runtimeAgent_->registerForTracing();
runtimeAgent_->enableSamplingProfiler();
}
}

void InstanceAgent::stopTracing() {
if (runtimeAgent_) {
runtimeAgent_->disableSamplingProfiler();
}
}

tracing::InstanceTracingProfile InstanceAgent::collectTracingProfile() {
tracing::RuntimeSamplingProfile runtimeSamplingProfile =
runtimeAgent_->collectSamplingProfile();

return tracing::InstanceTracingProfile{runtimeSamplingProfile};
}

} // namespace facebook::react::jsinspector_modern
Loading
Loading