Skip to content

Implement sampling profile serializer #49191

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

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
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
Expand Up @@ -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"])
Expand Down
3 changes: 2 additions & 1 deletion packages/react-native/React/Runtime/React-RCTRuntime.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
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
Expand Up @@ -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
Expand All @@ -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;

Expand Down Expand Up @@ -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_;
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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_;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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