mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Serialize Hermes Profile to Tracing Profile (#49082)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49082 # Changelog: [Internal] > NOTE: Some CI jobs are expected to fail, because changes in Hermes D67353585 should be landed first, and then grafted to Static Hermes. In this diff we will: - Call newly added API in Hermes from `HermesRuntimeTargetDelegate.cpp` - Define format for local Sampling Profile that will be used in Tracing domain - Implement formatter for Hermes Profile -> Tracign Profile Reviewed By: bgirard Differential Revision: D68414421 fbshipit-source-id: 05d76e9bcff46f88a2338490a9d858cb121f72cc
This commit is contained in:
committed by
Facebook GitHub Bot
parent
067c5f9954
commit
1aced32a1c
+168
@@ -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
|
||||
+22
@@ -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
|
||||
+4
-1
@@ -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
|
||||
@@ -182,7 +183,9 @@ class HermesRuntimeTargetDelegate::Impl final : public RuntimeTargetDelegate {
|
||||
}
|
||||
|
||||
tracing::RuntimeSamplingProfile collectSamplingProfile() override {
|
||||
return tracing::RuntimeSamplingProfile{};
|
||||
return tracing::HermesRuntimeSamplingProfileSerializer::
|
||||
serializeToTracingSamplingProfile(
|
||||
runtime_->dumpSampledTraceToProfile());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
+144
-1
@@ -7,8 +7,151 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace facebook::react::jsinspector_modern::tracing {
|
||||
|
||||
struct RuntimeSamplingProfile {};
|
||||
/// Contains relevant information about the sampled runtime from start to
|
||||
/// finish.
|
||||
struct RuntimeSamplingProfile {
|
||||
public:
|
||||
/// Represents a single frame inside the captured sample stack.
|
||||
struct SampleCallStackFrame {
|
||||
/// Represents type of frame inside of recorded call stack.
|
||||
enum class Kind {
|
||||
JSFunction, /// JavaScript function frame.
|
||||
NativeFunction, /// Native built-in functions, like arrayPrototypeMap.
|
||||
HostFunction, /// Native functions, defined by Host, a.k.a. Host
|
||||
/// functions.
|
||||
GarbageCollector, /// Garbage collection frame.
|
||||
};
|
||||
|
||||
public:
|
||||
SampleCallStackFrame(
|
||||
const Kind kind,
|
||||
const uint32_t scriptId,
|
||||
std::string functionName,
|
||||
std::optional<std::string> url = std::nullopt,
|
||||
const std::optional<uint32_t>& lineNumber = std::nullopt,
|
||||
const std::optional<uint32_t>& columnNumber = std::nullopt)
|
||||
: kind_(kind),
|
||||
scriptId_(scriptId),
|
||||
functionName_(std::move(functionName)),
|
||||
url_(std::move(url)),
|
||||
lineNumber_(lineNumber),
|
||||
columnNumber_(columnNumber) {}
|
||||
|
||||
/// \return type of the call stack frame.
|
||||
Kind getKind() const {
|
||||
return kind_;
|
||||
}
|
||||
|
||||
/// \return id of the corresponding script in the VM.
|
||||
uint32_t getScriptId() const {
|
||||
return scriptId_;
|
||||
}
|
||||
|
||||
/// \return name of the function that represents call frame.
|
||||
const std::string& getFunctionName() const {
|
||||
return functionName_;
|
||||
}
|
||||
|
||||
bool hasUrl() const {
|
||||
return url_.has_value();
|
||||
}
|
||||
|
||||
/// \return source url of the corresponding script in the VM.
|
||||
const std::string& getUrl() const {
|
||||
return url_.value();
|
||||
}
|
||||
|
||||
bool hasLineNumber() const {
|
||||
return lineNumber_.has_value();
|
||||
}
|
||||
|
||||
/// \return 0-based line number of the corresponding call frame.
|
||||
uint32_t getLineNumber() const {
|
||||
return lineNumber_.value();
|
||||
}
|
||||
|
||||
bool hasColumnNumber() const {
|
||||
return columnNumber_.has_value();
|
||||
}
|
||||
|
||||
/// \return 0-based column number of the corresponding call frame.
|
||||
uint32_t getColumnNumber() const {
|
||||
return columnNumber_.value();
|
||||
}
|
||||
|
||||
private:
|
||||
Kind kind_;
|
||||
uint32_t scriptId_;
|
||||
std::string functionName_;
|
||||
std::optional<std::string> url_;
|
||||
std::optional<uint32_t> lineNumber_;
|
||||
std::optional<uint32_t> columnNumber_;
|
||||
};
|
||||
|
||||
/// A pair of a timestamp and a snapshot of the call stack at this point in
|
||||
/// time.
|
||||
struct Sample {
|
||||
public:
|
||||
Sample(
|
||||
uint64_t timestamp,
|
||||
uint64_t threadId,
|
||||
std::vector<SampleCallStackFrame> callStack)
|
||||
: timestamp_(timestamp),
|
||||
threadId_(threadId),
|
||||
callStack_(std::move(callStack)) {}
|
||||
|
||||
/// \return serialized unix timestamp in microseconds granularity. The
|
||||
/// moment when this sample was recorded.
|
||||
uint64_t getTimestamp() const {
|
||||
return timestamp_;
|
||||
}
|
||||
|
||||
/// \return thread id where sample was recorded.
|
||||
uint64_t getThreadId() const {
|
||||
return threadId_;
|
||||
}
|
||||
|
||||
/// \return a snapshot of the call stack. The first element of the vector is
|
||||
/// the lowest frame in the stack.
|
||||
const std::vector<SampleCallStackFrame>& getCallStack() const {
|
||||
return callStack_;
|
||||
}
|
||||
|
||||
private:
|
||||
/// When the call stack snapshot was taken (μs).
|
||||
uint64_t timestamp_;
|
||||
/// Thread id where sample was recorded.
|
||||
uint64_t threadId_;
|
||||
/// Snapshot of the call stack. The first element of the vector is
|
||||
/// the lowest frame in the stack.
|
||||
std::vector<SampleCallStackFrame> callStack_;
|
||||
};
|
||||
|
||||
RuntimeSamplingProfile(std::string runtimeName, std::vector<Sample> samples)
|
||||
: runtimeName_(std::move(runtimeName)), samples_(std::move(samples)) {}
|
||||
|
||||
/// \return name of the JavaScript runtime, where sampling occurred.
|
||||
const std::string& getRuntimeName() const {
|
||||
return runtimeName_;
|
||||
}
|
||||
|
||||
/// \return list of recorded samples, should be chronologically sorted.
|
||||
const std::vector<Sample>& getSamples() const {
|
||||
return samples_;
|
||||
}
|
||||
|
||||
private:
|
||||
/// Name of the runtime, where sampling occurred: Hermes, V8, etc.
|
||||
std::string runtimeName_;
|
||||
/// List of recorded samples, should be chronologically sorted.
|
||||
std::vector<Sample> samples_;
|
||||
};
|
||||
|
||||
} // namespace facebook::react::jsinspector_modern::tracing
|
||||
|
||||
Reference in New Issue
Block a user