Skip to content

Commit 87d4300

Browse files
hoxyqfacebook-github-bot
authored andcommitted
Implement sampling profile serializer (facebook#49191)
Summary: Pull Request resolved: facebook#49191 # Changelog: [Internal] In this diff we are adding another serializer, that will receive local sampling profile (in tracing domain), and will record corresponding Trace Events with `PerformanceTracer`. It encapsulates the logic of transforming list of samples to `"Profile"` and `"ProfileChunk"` trace events, which will be parsed by Chrome DevTools later. Reviewed By: huntie Differential Revision: D68439735 fbshipit-source-id: 0b3f2b3aff5b79a921e0350759e93f5b05e34d8e
1 parent 290f237 commit 87d4300

File tree

5 files changed

+436
-0
lines changed

5 files changed

+436
-0
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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 "RuntimeSamplingProfile.h"
11+
12+
namespace facebook::react::jsinspector_modern::tracing {
13+
14+
/*
15+
* Auxiliary data structure used for creating Profile tree and identifying
16+
* identical frames.
17+
*/
18+
class ProfileTreeNode {
19+
public:
20+
/*
21+
* For Chromium & V8 this could also be WASM, this is not the case for us.
22+
*/
23+
enum class CodeType {
24+
JavaScript,
25+
Other,
26+
};
27+
28+
ProfileTreeNode(
29+
uint32_t id,
30+
CodeType codeType,
31+
ProfileTreeNode* parent,
32+
RuntimeSamplingProfile::SampleCallStackFrame callFrame)
33+
: id_(id),
34+
codeType_(codeType),
35+
parent_(parent),
36+
callFrame_(std::move(callFrame)) {}
37+
38+
uint32_t getId() const {
39+
return id_;
40+
}
41+
42+
CodeType getCodeType() const {
43+
return codeType_;
44+
}
45+
46+
/**
47+
* \return pointer to the parent node, nullptr if this is the root node.
48+
*/
49+
ProfileTreeNode* getParent() const {
50+
return parent_;
51+
}
52+
53+
/**
54+
* \return call frame information that is represented by this node.
55+
*/
56+
const RuntimeSamplingProfile::SampleCallStackFrame& getCallFrame() const {
57+
return callFrame_;
58+
}
59+
60+
/**
61+
* Will only add unique child node. Returns pointer to the already existing
62+
* child node, nullptr if the added child node is unique.
63+
*/
64+
ProfileTreeNode* addChild(ProfileTreeNode* child) {
65+
for (auto existingChild : children_) {
66+
if (*existingChild == child) {
67+
return existingChild;
68+
}
69+
}
70+
71+
children_.push_back(child);
72+
return nullptr;
73+
}
74+
75+
bool operator==(const ProfileTreeNode* rhs) const {
76+
if (this->parent_ != rhs->parent_) {
77+
return false;
78+
}
79+
if (this->codeType_ != rhs->codeType_) {
80+
return false;
81+
}
82+
83+
return this->getCallFrame() == rhs->getCallFrame();
84+
}
85+
86+
private:
87+
/**
88+
* Unique id of the node.
89+
*/
90+
uint32_t id_;
91+
/**
92+
* Type of the code that is represented by this node. Either JavaScript or
93+
* Other.
94+
*/
95+
CodeType codeType_;
96+
/**
97+
* Pointer to the parent node. Should be nullptr only for root node.
98+
*/
99+
ProfileTreeNode* parent_{nullptr};
100+
/**
101+
* Lst of pointers to children nodes.
102+
*/
103+
std::vector<ProfileTreeNode*> children_{};
104+
/**
105+
* Information about the corresponding call frame that is represented by this
106+
* node.
107+
*/
108+
RuntimeSamplingProfile::SampleCallStackFrame callFrame_;
109+
};
110+
111+
} // namespace facebook::react::jsinspector_modern::tracing

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ struct RuntimeSamplingProfile {
8686
return columnNumber_.value();
8787
}
8888

89+
inline bool operator==(const SampleCallStackFrame& rhs) const noexcept {
90+
return kind_ == rhs.kind_ && scriptId_ == rhs.scriptId_ &&
91+
functionName_ == rhs.functionName_ && url_ == rhs.url_ &&
92+
lineNumber_ == rhs.lineNumber_ && columnNumber_ == rhs.columnNumber_;
93+
}
94+
8995
private:
9096
Kind kind_;
9197
uint32_t scriptId_;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
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 "RuntimeSamplingProfileTraceEventSerializer.h"
9+
#include "ProfileTreeNode.h"
10+
11+
namespace facebook::react::jsinspector_modern::tracing {
12+
13+
namespace {
14+
15+
uint64_t formatTimePointToUnixTimestamp(
16+
std::chrono::steady_clock::time_point timestamp) {
17+
return std::chrono::duration_cast<std::chrono::microseconds>(
18+
timestamp.time_since_epoch())
19+
.count();
20+
}
21+
22+
TraceEventProfileChunk::CPUProfile::Node convertToTraceEventProfileNode(
23+
ProfileTreeNode* node) {
24+
ProfileTreeNode* nodeParent = node->getParent();
25+
const RuntimeSamplingProfile::SampleCallStackFrame& callFrame =
26+
node->getCallFrame();
27+
auto traceEventCallFrame =
28+
TraceEventProfileChunk::CPUProfile::Node::CallFrame{
29+
node->getCodeType() == ProfileTreeNode::CodeType::JavaScript
30+
? "JS"
31+
: "other",
32+
callFrame.getScriptId(),
33+
callFrame.getFunctionName(),
34+
callFrame.hasUrl() ? std::optional<std::string>(callFrame.getUrl())
35+
: std::nullopt,
36+
callFrame.hasLineNumber()
37+
? std::optional<uint32_t>(callFrame.getLineNumber())
38+
: std::nullopt,
39+
callFrame.hasColumnNumber()
40+
? std::optional<uint32_t>(callFrame.getColumnNumber())
41+
: std::nullopt};
42+
43+
return TraceEventProfileChunk::CPUProfile::Node{
44+
node->getId(),
45+
traceEventCallFrame,
46+
nodeParent != nullptr ? std::optional<uint32_t>(nodeParent->getId())
47+
: std::nullopt};
48+
}
49+
50+
void emitSingleProfileChunk(
51+
PerformanceTracer& performanceTracer,
52+
uint16_t profileId,
53+
uint64_t threadId,
54+
uint64_t chunkTimestamp,
55+
std::vector<ProfileTreeNode*>& nodes,
56+
std::vector<uint32_t>& samples,
57+
std::vector<long long>& timeDeltas) {
58+
std::vector<TraceEventProfileChunk::CPUProfile::Node> traceEventNodes;
59+
traceEventNodes.reserve(nodes.size());
60+
for (ProfileTreeNode* node : nodes) {
61+
traceEventNodes.push_back(convertToTraceEventProfileNode(node));
62+
}
63+
64+
performanceTracer.reportRuntimeProfileChunk(
65+
profileId,
66+
threadId,
67+
chunkTimestamp,
68+
TraceEventProfileChunk{
69+
TraceEventProfileChunk::CPUProfile{traceEventNodes, samples},
70+
TraceEventProfileChunk::TimeDeltas{timeDeltas},
71+
});
72+
}
73+
74+
} // namespace
75+
76+
/* static */ void
77+
RuntimeSamplingProfileTraceEventSerializer::serializeAndBuffer(
78+
PerformanceTracer& performanceTracer,
79+
const RuntimeSamplingProfile& profile,
80+
std::chrono::steady_clock::time_point tracingStartTime,
81+
uint16_t profileChunkSize) {
82+
std::vector<RuntimeSamplingProfile::Sample> runtimeSamples =
83+
profile.getSamples();
84+
if (runtimeSamples.empty()) {
85+
return;
86+
}
87+
88+
uint64_t chunkThreadId = runtimeSamples.front().getThreadId();
89+
uint64_t tracingStartUnixTimestamp =
90+
formatTimePointToUnixTimestamp(tracingStartTime);
91+
uint16_t profileId = performanceTracer.reportRuntimeProfile(
92+
chunkThreadId, tracingStartUnixTimestamp);
93+
94+
uint32_t nodeCount = 0;
95+
auto* rootNode = new ProfileTreeNode(
96+
++nodeCount,
97+
ProfileTreeNode::CodeType::Other,
98+
nullptr,
99+
RuntimeSamplingProfile::SampleCallStackFrame{
100+
RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction,
101+
0,
102+
"(root)"});
103+
auto* programNode = new ProfileTreeNode(
104+
++nodeCount,
105+
ProfileTreeNode::CodeType::Other,
106+
rootNode,
107+
RuntimeSamplingProfile::SampleCallStackFrame{
108+
RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction,
109+
0,
110+
"(program)"});
111+
auto* idleNode = new ProfileTreeNode(
112+
++nodeCount,
113+
ProfileTreeNode::CodeType::Other,
114+
rootNode,
115+
RuntimeSamplingProfile::SampleCallStackFrame{
116+
RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction,
117+
0,
118+
"(idle)"});
119+
120+
rootNode->addChild(programNode);
121+
rootNode->addChild(idleNode);
122+
123+
// Ideally, we should use a timestamp from Runtime Sampling Profiler.
124+
// We currently use tracingStartTime, which is defined in TracingAgent.
125+
uint64_t previousSampleTimestamp = tracingStartUnixTimestamp;
126+
// There could be any number of new nodes in this chunk. Empty if all nodes
127+
// are already emitted in previous chunks.
128+
std::vector<ProfileTreeNode*> nodesInThisChunk;
129+
nodesInThisChunk.push_back(rootNode);
130+
nodesInThisChunk.push_back(programNode);
131+
nodesInThisChunk.push_back(idleNode);
132+
133+
std::vector<uint32_t> samplesInThisChunk;
134+
samplesInThisChunk.reserve(profileChunkSize);
135+
std::vector<long long> timeDeltasInThisChunk;
136+
timeDeltasInThisChunk.reserve(profileChunkSize);
137+
138+
RuntimeSamplingProfile::SampleCallStackFrame garbageCollectorCallFrame =
139+
RuntimeSamplingProfile::SampleCallStackFrame{
140+
RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector,
141+
0,
142+
"(garbage collector)"};
143+
uint64_t chunkTimestamp = tracingStartUnixTimestamp;
144+
for (const RuntimeSamplingProfile::Sample& sample : runtimeSamples) {
145+
uint64_t sampleThreadId = sample.getThreadId();
146+
// If next sample was recorded on a different thread, emit the current chunk
147+
// and continue.
148+
if (chunkThreadId != sampleThreadId) {
149+
emitSingleProfileChunk(
150+
performanceTracer,
151+
profileId,
152+
chunkThreadId,
153+
chunkTimestamp,
154+
nodesInThisChunk,
155+
samplesInThisChunk,
156+
timeDeltasInThisChunk);
157+
158+
nodesInThisChunk.clear();
159+
samplesInThisChunk.clear();
160+
timeDeltasInThisChunk.clear();
161+
}
162+
163+
chunkThreadId = sampleThreadId;
164+
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack =
165+
sample.getCallStack();
166+
uint64_t sampleTimestamp = sample.getTimestamp();
167+
if (samplesInThisChunk.empty()) {
168+
// New chunk. Reset the timestamp.
169+
chunkTimestamp = sampleTimestamp;
170+
}
171+
172+
long long timeDelta = sampleTimestamp - previousSampleTimestamp;
173+
timeDeltasInThisChunk.push_back(timeDelta);
174+
previousSampleTimestamp = sampleTimestamp;
175+
176+
ProfileTreeNode* previousNode = callStack.empty() ? idleNode : rootNode;
177+
for (auto it = callStack.rbegin(); it != callStack.rend(); ++it) {
178+
auto callFrame = *it;
179+
bool isGarbageCollectorFrame = callFrame.getKind() ==
180+
RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector;
181+
// We don't need real garbage collector call frame, we change it to
182+
// what Chrome DevTools expects.
183+
auto* currentNode = new ProfileTreeNode(
184+
nodeCount + 1,
185+
isGarbageCollectorFrame ? ProfileTreeNode::CodeType::Other
186+
: ProfileTreeNode::CodeType::JavaScript,
187+
previousNode,
188+
isGarbageCollectorFrame ? garbageCollectorCallFrame : callFrame);
189+
190+
ProfileTreeNode* alreadyExistingNode =
191+
previousNode->addChild(currentNode);
192+
if (alreadyExistingNode != nullptr) {
193+
previousNode = alreadyExistingNode;
194+
} else {
195+
nodesInThisChunk.push_back(currentNode);
196+
++nodeCount;
197+
198+
previousNode = currentNode;
199+
}
200+
}
201+
samplesInThisChunk.push_back(previousNode->getId());
202+
203+
if (samplesInThisChunk.size() == profileChunkSize) {
204+
emitSingleProfileChunk(
205+
performanceTracer,
206+
profileId,
207+
chunkThreadId,
208+
chunkTimestamp,
209+
nodesInThisChunk,
210+
samplesInThisChunk,
211+
timeDeltasInThisChunk);
212+
213+
nodesInThisChunk.clear();
214+
samplesInThisChunk.clear();
215+
timeDeltasInThisChunk.clear();
216+
}
217+
}
218+
219+
if (!samplesInThisChunk.empty()) {
220+
emitSingleProfileChunk(
221+
performanceTracer,
222+
profileId,
223+
chunkThreadId,
224+
chunkTimestamp,
225+
nodesInThisChunk,
226+
samplesInThisChunk,
227+
timeDeltasInThisChunk);
228+
}
229+
}
230+
231+
} // namespace facebook::react::jsinspector_modern::tracing
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 "PerformanceTracer.h"
11+
#include "RuntimeSamplingProfile.h"
12+
13+
namespace facebook::react::jsinspector_modern::tracing {
14+
15+
/**
16+
* Serializes RuntimeSamplingProfile into collection of specific Trace Events,
17+
* which represent Profile information on a timeline.
18+
*/
19+
class RuntimeSamplingProfileTraceEventSerializer {
20+
public:
21+
RuntimeSamplingProfileTraceEventSerializer() = delete;
22+
23+
static void serializeAndBuffer(
24+
PerformanceTracer& performanceTracer,
25+
const RuntimeSamplingProfile& profile,
26+
std::chrono::steady_clock::time_point tracingStartTime,
27+
uint16_t profileChunkSize = 100);
28+
};
29+
30+
} // namespace facebook::react::jsinspector_modern::tracing

0 commit comments

Comments
 (0)