Skip to content

Commit d99fd14

Browse files
hoxyqfacebook-github-bot
authored andcommitted
Serialize Hermes Profile to Tracing Profile (#49082)
Summary: Pull Request resolved: #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
1 parent b69121f commit d99fd14

File tree

4 files changed

+338
-2
lines changed

4 files changed

+338
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include "HermesRuntimeSamplingProfileSerializer.h"
9+
10+
namespace facebook::react::jsinspector_modern::tracing {
11+
12+
namespace {
13+
14+
/// Fallback script ID for call frames, when Hermes didn't provide one or when
15+
/// this frame is part of the VM, like native functions, used for parity with
16+
/// Chromium + V8.
17+
const uint32_t FALLBACK_SCRIPT_ID = 0;
18+
/// Garbage collector frame name, used for parity with Chromium + V8.
19+
const std::string GARBAGE_COLLECTOR_FRAME_NAME = "(garbage collector)";
20+
21+
/// Filters out Hermes Suspend frames related to Debugger.
22+
/// Even though Debugger domain is expected to be disabled, Hermes might run
23+
/// Debugger loop while recording sampling profile. We only allow GC frames.
24+
bool shouldIgnoreHermesFrame(
25+
hermes::sampling_profiler::ProfileSampleCallStackFrame* hermesFrame) {
26+
if (hermesFrame->getKind() !=
27+
hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind::Suspend) {
28+
return false;
29+
}
30+
31+
auto* suspendFrame = static_cast<
32+
hermes::sampling_profiler::ProfileSampleCallStackSuspendFrame*>(
33+
hermesFrame);
34+
auto suspendFrameKind = suspendFrame->getSuspendFrameKind();
35+
return suspendFrameKind !=
36+
hermes::sampling_profiler::ProfileSampleCallStackSuspendFrame::
37+
SuspendFrameKind::GC;
38+
}
39+
40+
RuntimeSamplingProfile::SampleCallStackFrame convertHermesFrameToTracingFrame(
41+
hermes::sampling_profiler::ProfileSampleCallStackFrame* hermesFrame) {
42+
switch (hermesFrame->getKind()) {
43+
case hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind::
44+
JSFunction: {
45+
auto* jsFunctionFrame = static_cast<
46+
hermes::sampling_profiler::ProfileSampleCallStackJSFunctionFrame*>(
47+
hermesFrame);
48+
return RuntimeSamplingProfile::SampleCallStackFrame{
49+
RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction,
50+
jsFunctionFrame->hasScriptId() ? jsFunctionFrame->getScriptId()
51+
: FALLBACK_SCRIPT_ID,
52+
jsFunctionFrame->getFunctionName(),
53+
jsFunctionFrame->hasUrl()
54+
? std::optional<std::string>{jsFunctionFrame->getUrl()}
55+
: std::nullopt,
56+
jsFunctionFrame->hasLineNumber()
57+
? std::optional<uint32_t>{jsFunctionFrame->getLineNumber() - 1}
58+
// Hermes VM keeps line numbers as 1-based. Convert to
59+
// 0-based.
60+
: std::nullopt,
61+
jsFunctionFrame->hasColumnNumber()
62+
? std::optional<uint32_t>{jsFunctionFrame->getColumnNumber() - 1}
63+
// Hermes VM keeps column numbers as 1-based. Convert to
64+
// 0-based.
65+
: std::nullopt,
66+
};
67+
}
68+
case hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind::
69+
NativeFunction: {
70+
auto* nativeFunctionFrame =
71+
static_cast<hermes::sampling_profiler::
72+
ProfileSampleCallStackNativeFunctionFrame*>(
73+
hermesFrame);
74+
75+
return RuntimeSamplingProfile::SampleCallStackFrame{
76+
RuntimeSamplingProfile::SampleCallStackFrame::Kind::NativeFunction,
77+
FALLBACK_SCRIPT_ID, // JavaScript Runtime defines the implementation
78+
// for native function, no script ID to reference.
79+
nativeFunctionFrame->getFunctionName(),
80+
};
81+
}
82+
case hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind::
83+
HostFunction: {
84+
auto* hostFunctionFrame = static_cast<
85+
hermes::sampling_profiler::ProfileSampleCallStackHostFunctionFrame*>(
86+
hermesFrame);
87+
88+
return RuntimeSamplingProfile::SampleCallStackFrame{
89+
RuntimeSamplingProfile::SampleCallStackFrame::Kind::HostFunction,
90+
FALLBACK_SCRIPT_ID, // JavaScript Runtime defines the implementation
91+
// for host function, no script ID to reference.
92+
hostFunctionFrame->getFunctionName(),
93+
};
94+
}
95+
case hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind::
96+
Suspend: {
97+
auto* suspendFrame = static_cast<
98+
hermes::sampling_profiler::ProfileSampleCallStackSuspendFrame*>(
99+
hermesFrame);
100+
auto suspendFrameKind = suspendFrame->getSuspendFrameKind();
101+
if (suspendFrameKind ==
102+
hermes::sampling_profiler::ProfileSampleCallStackSuspendFrame::
103+
SuspendFrameKind::GC) {
104+
return RuntimeSamplingProfile::SampleCallStackFrame{
105+
RuntimeSamplingProfile::SampleCallStackFrame::Kind::
106+
GarbageCollector,
107+
FALLBACK_SCRIPT_ID, // GC frames are part of the VM, no script ID to
108+
// reference.
109+
GARBAGE_COLLECTOR_FRAME_NAME,
110+
};
111+
}
112+
113+
// We should have filtered out Debugger Suspend frames before in
114+
// shouldFilterOutHermesFrame().
115+
throw std::logic_error{
116+
"Unexpected Suspend frame found in Hermes call stack"};
117+
}
118+
119+
default:
120+
throw std::logic_error{"Unknown Hermes stack frame kind"};
121+
}
122+
}
123+
124+
RuntimeSamplingProfile::Sample convertHermesSampleToTracingSample(
125+
hermes::sampling_profiler::ProfileSample& hermesSample) {
126+
uint64_t reconciledTimestamp = hermesSample.getTimestamp();
127+
std::vector<hermes::sampling_profiler::ProfileSampleCallStackFrame*>
128+
hermesSampleCallStack = hermesSample.getCallStack();
129+
130+
std::vector<RuntimeSamplingProfile::SampleCallStackFrame>
131+
reconciledSampleCallStack;
132+
reconciledSampleCallStack.reserve(hermesSampleCallStack.size());
133+
134+
for (auto* hermesFrame : hermesSampleCallStack) {
135+
if (shouldIgnoreHermesFrame(hermesFrame)) {
136+
continue;
137+
}
138+
RuntimeSamplingProfile::SampleCallStackFrame reconciledFrame =
139+
convertHermesFrameToTracingFrame(hermesFrame);
140+
reconciledSampleCallStack.push_back(std::move(reconciledFrame));
141+
}
142+
143+
return RuntimeSamplingProfile::Sample{
144+
reconciledTimestamp,
145+
hermesSample.getThreadId(),
146+
std::move(reconciledSampleCallStack)};
147+
}
148+
149+
} // namespace
150+
151+
/* static */ RuntimeSamplingProfile
152+
HermesRuntimeSamplingProfileSerializer::serializeToTracingSamplingProfile(
153+
const hermes::sampling_profiler::Profile& hermesProfile) {
154+
std::vector<hermes::sampling_profiler::ProfileSample> hermesSamples =
155+
hermesProfile.getSamples();
156+
std::vector<RuntimeSamplingProfile::Sample> reconciledSamples;
157+
reconciledSamples.reserve(hermesSamples.size());
158+
159+
for (auto& hermesSample : hermesSamples) {
160+
RuntimeSamplingProfile::Sample reconciledSample =
161+
convertHermesSampleToTracingSample(hermesSample);
162+
reconciledSamples.push_back(std::move(reconciledSample));
163+
}
164+
165+
return RuntimeSamplingProfile{"Hermes", std::move(reconciledSamples)};
166+
}
167+
168+
} // namespace facebook::react::jsinspector_modern::tracing
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <hermes/hermes.h>
11+
12+
#include <jsinspector-modern/tracing/RuntimeSamplingProfile.h>
13+
14+
namespace facebook::react::jsinspector_modern::tracing {
15+
16+
class HermesRuntimeSamplingProfileSerializer {
17+
public:
18+
static tracing::RuntimeSamplingProfile serializeToTracingSamplingProfile(
19+
const hermes::sampling_profiler::Profile& hermesProfile);
20+
};
21+
22+
} // namespace facebook::react::jsinspector_modern::tracing

packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <jsinspector-modern/RuntimeTarget.h>
99

10+
#include "HermesRuntimeSamplingProfileSerializer.h"
1011
#include "HermesRuntimeTargetDelegate.h"
1112

1213
// If HERMES_ENABLE_DEBUGGER isn't defined, we can't access any Hermes
@@ -182,7 +183,9 @@ class HermesRuntimeTargetDelegate::Impl final : public RuntimeTargetDelegate {
182183
}
183184

184185
tracing::RuntimeSamplingProfile collectSamplingProfile() override {
185-
return tracing::RuntimeSamplingProfile{};
186+
return tracing::HermesRuntimeSamplingProfileSerializer::
187+
serializeToTracingSamplingProfile(
188+
runtime_->dumpSampledTraceToProfile());
186189
}
187190

188191
private:

packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfile.h

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,151 @@
77

88
#pragma once
99

10+
#include <optional>
11+
#include <string>
12+
#include <utility>
13+
#include <vector>
14+
1015
namespace facebook::react::jsinspector_modern::tracing {
1116

12-
struct RuntimeSamplingProfile {};
17+
/// Contains relevant information about the sampled runtime from start to
18+
/// finish.
19+
struct RuntimeSamplingProfile {
20+
public:
21+
/// Represents a single frame inside the captured sample stack.
22+
struct SampleCallStackFrame {
23+
/// Represents type of frame inside of recorded call stack.
24+
enum class Kind {
25+
JSFunction, /// JavaScript function frame.
26+
NativeFunction, /// Native built-in functions, like arrayPrototypeMap.
27+
HostFunction, /// Native functions, defined by Host, a.k.a. Host
28+
/// functions.
29+
GarbageCollector, /// Garbage collection frame.
30+
};
31+
32+
public:
33+
SampleCallStackFrame(
34+
const Kind kind,
35+
const uint32_t scriptId,
36+
std::string functionName,
37+
std::optional<std::string> url = std::nullopt,
38+
const std::optional<uint32_t>& lineNumber = std::nullopt,
39+
const std::optional<uint32_t>& columnNumber = std::nullopt)
40+
: kind_(kind),
41+
scriptId_(scriptId),
42+
functionName_(std::move(functionName)),
43+
url_(std::move(url)),
44+
lineNumber_(lineNumber),
45+
columnNumber_(columnNumber) {}
46+
47+
/// \return type of the call stack frame.
48+
Kind getKind() const {
49+
return kind_;
50+
}
51+
52+
/// \return id of the corresponding script in the VM.
53+
uint32_t getScriptId() const {
54+
return scriptId_;
55+
}
56+
57+
/// \return name of the function that represents call frame.
58+
const std::string& getFunctionName() const {
59+
return functionName_;
60+
}
61+
62+
bool hasUrl() const {
63+
return url_.has_value();
64+
}
65+
66+
/// \return source url of the corresponding script in the VM.
67+
const std::string& getUrl() const {
68+
return url_.value();
69+
}
70+
71+
bool hasLineNumber() const {
72+
return lineNumber_.has_value();
73+
}
74+
75+
/// \return 0-based line number of the corresponding call frame.
76+
uint32_t getLineNumber() const {
77+
return lineNumber_.value();
78+
}
79+
80+
bool hasColumnNumber() const {
81+
return columnNumber_.has_value();
82+
}
83+
84+
/// \return 0-based column number of the corresponding call frame.
85+
uint32_t getColumnNumber() const {
86+
return columnNumber_.value();
87+
}
88+
89+
private:
90+
Kind kind_;
91+
uint32_t scriptId_;
92+
std::string functionName_;
93+
std::optional<std::string> url_;
94+
std::optional<uint32_t> lineNumber_;
95+
std::optional<uint32_t> columnNumber_;
96+
};
97+
98+
/// A pair of a timestamp and a snapshot of the call stack at this point in
99+
/// time.
100+
struct Sample {
101+
public:
102+
Sample(
103+
uint64_t timestamp,
104+
uint64_t threadId,
105+
std::vector<SampleCallStackFrame> callStack)
106+
: timestamp_(timestamp),
107+
threadId_(threadId),
108+
callStack_(std::move(callStack)) {}
109+
110+
/// \return serialized unix timestamp in microseconds granularity. The
111+
/// moment when this sample was recorded.
112+
uint64_t getTimestamp() const {
113+
return timestamp_;
114+
}
115+
116+
/// \return thread id where sample was recorded.
117+
uint64_t getThreadId() const {
118+
return threadId_;
119+
}
120+
121+
/// \return a snapshot of the call stack. The first element of the vector is
122+
/// the lowest frame in the stack.
123+
const std::vector<SampleCallStackFrame>& getCallStack() const {
124+
return callStack_;
125+
}
126+
127+
private:
128+
/// When the call stack snapshot was taken (μs).
129+
uint64_t timestamp_;
130+
/// Thread id where sample was recorded.
131+
uint64_t threadId_;
132+
/// Snapshot of the call stack. The first element of the vector is
133+
/// the lowest frame in the stack.
134+
std::vector<SampleCallStackFrame> callStack_;
135+
};
136+
137+
RuntimeSamplingProfile(std::string runtimeName, std::vector<Sample> samples)
138+
: runtimeName_(std::move(runtimeName)), samples_(std::move(samples)) {}
139+
140+
/// \return name of the JavaScript runtime, where sampling occurred.
141+
const std::string& getRuntimeName() const {
142+
return runtimeName_;
143+
}
144+
145+
/// \return list of recorded samples, should be chronologically sorted.
146+
const std::vector<Sample>& getSamples() const {
147+
return samples_;
148+
}
149+
150+
private:
151+
/// Name of the runtime, where sampling occurred: Hermes, V8, etc.
152+
std::string runtimeName_;
153+
/// List of recorded samples, should be chronologically sorted.
154+
std::vector<Sample> samples_;
155+
};
13156

14157
} // namespace facebook::react::jsinspector_modern::tracing

0 commit comments

Comments
 (0)