/src/hermes/lib/VM/Profiler/ChromeTraceSerializer.h
Line | Count | Source |
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 | | #ifndef HERMES_VM_PROFILER_CHROMETRACESERIALIZER_H |
9 | | #define HERMES_VM_PROFILER_CHROMETRACESERIALIZER_H |
10 | | |
11 | | #include "hermes/VM/Profiler/SamplingProfilerDefs.h" |
12 | | |
13 | | #if HERMESVM_SAMPLING_PROFILER_AVAILABLE |
14 | | // TODO: Remove dependency on SamplingProfilerPosix from ChromeTraceSerializer. |
15 | | // A new header may need to be introduced for data entities. It may make sense |
16 | | // to share the data entity across different SamplingProfiler implementations. |
17 | | |
18 | | /// This file convert sampled stack frames into Chrome trace format which |
19 | | /// is documented here: |
20 | | /// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview |
21 | | |
22 | | #include "hermes/Support/JSONEmitter.h" |
23 | | #include "hermes/VM/Profiler/SamplingProfiler.h" |
24 | | |
25 | | #include "llvh/ADT/DenseMap.h" |
26 | | |
27 | | #include <thread> |
28 | | |
29 | | namespace hermes { |
30 | | namespace vm { |
31 | | |
32 | | /// Generating next id for stack frame. |
33 | | class ChromeFrameIdGenerator { |
34 | | uint32_t nextFrameId_{1}; |
35 | | |
36 | | public: |
37 | 0 | uint32_t getNextFrameNodeId() { |
38 | 0 | return nextFrameId_++; |
39 | 0 | } |
40 | | }; |
41 | | |
42 | | /// Represent a single stack frame node in collapsed/merged call tree |
43 | | /// in chrome trace format. |
44 | | class ChromeStackFrameNode { |
45 | | private: |
46 | | /// Unique id for the stack frame. |
47 | | uint32_t id_; |
48 | | /// Frame information. Is None iff this is the root node. |
49 | | llvh::Optional<SamplingProfiler::StackFrame> frameInfo_; |
50 | | /// All callee/children of this stack frame. |
51 | | std::vector<std::shared_ptr<ChromeStackFrameNode>> children_; |
52 | | /// How many times was this node on the top of the callstack during profiling. |
53 | | uint32_t hitCount_ = 0; |
54 | | |
55 | | /// \p node represents the current visiting node. |
56 | | /// \p parent can be nullptr for root node. |
57 | | using DfsWalkCallback = const std::function<void( |
58 | | const ChromeStackFrameNode &node, |
59 | | const ChromeStackFrameNode *parent)>; |
60 | | |
61 | | private: |
62 | | void dfsWalkHelper( |
63 | | DfsWalkCallback &callback, |
64 | 0 | const ChromeStackFrameNode *parent) const { |
65 | 0 | callback(*this, parent); |
66 | 0 | for (const auto &child : children_) { |
67 | 0 | child->dfsWalkHelper(callback, this); |
68 | 0 | } |
69 | 0 | } |
70 | | |
71 | | public: |
72 | | explicit ChromeStackFrameNode( |
73 | | uint32_t nextFrameId, |
74 | | llvh::Optional<SamplingProfiler::StackFrame> frame) |
75 | 0 | : id_(nextFrameId), frameInfo_(frame) {} |
76 | | |
77 | 0 | uint32_t getId() const { |
78 | 0 | return id_; |
79 | 0 | } |
80 | | |
81 | | // Get the frame info for this node. Should never be called on the root node |
82 | | // since it will be None. |
83 | 0 | const SamplingProfiler::StackFrame &getFrameInfo() const { |
84 | 0 | return *frameInfo_; |
85 | 0 | } |
86 | | |
87 | | /// Increments this node's hit counter by one. |
88 | 0 | void addHit() { |
89 | 0 | ++hitCount_; |
90 | 0 | } |
91 | | |
92 | | /// \return this node's hit counter. |
93 | 0 | uint32_t getHitCount() const { |
94 | 0 | return hitCount_; |
95 | 0 | } |
96 | | |
97 | | /// Find a child node matching \p target, otherwise add \p target |
98 | | /// as a new child. |
99 | | /// \return the found/added child node. |
100 | | std::shared_ptr<ChromeStackFrameNode> findOrAddNewChild( |
101 | | ChromeFrameIdGenerator &frameIdGen, |
102 | | const SamplingProfiler::StackFrame &target); |
103 | | |
104 | | /// DFS walk the call tree using current node as root. |
105 | | /// For each visited node, invoke \p callback. |
106 | 0 | void dfsWalk(DfsWalkCallback &callback) const { |
107 | 0 | this->dfsWalkHelper(callback, nullptr); |
108 | 0 | } |
109 | | |
110 | | /// Get the vector with the this node's children list. |
111 | | const std::vector<std::shared_ptr<ChromeStackFrameNode>> &getChildren() |
112 | 0 | const { |
113 | 0 | return children_; |
114 | 0 | } |
115 | | }; |
116 | | |
117 | | /// Represent an OS sample event(without duration) in chrome format. |
118 | | class ChromeSampleEvent { |
119 | | private: |
120 | | // TODO: get real cpu id. |
121 | | int cpu_{-1}; |
122 | | // Seems should always be one. |
123 | | int weight_{1}; |
124 | | SamplingProfiler::ThreadId tid_; |
125 | | SamplingProfiler::TimeStampType timeStamp_; |
126 | | std::shared_ptr<ChromeStackFrameNode> leafNode_; |
127 | | |
128 | | public: |
129 | | explicit ChromeSampleEvent( |
130 | | SamplingProfiler::ThreadId tid, |
131 | | SamplingProfiler::TimeStampType timeStamp, |
132 | | std::shared_ptr<ChromeStackFrameNode> leaf) |
133 | 0 | : tid_(tid), timeStamp_(timeStamp), leafNode_(leaf) {} |
134 | | |
135 | | /// \return CPU id. |
136 | 0 | int getCpu() const { |
137 | 0 | return cpu_; |
138 | 0 | } |
139 | | |
140 | | /// \return weight. |
141 | 0 | int getWeight() const { |
142 | 0 | return weight_; |
143 | 0 | } |
144 | | |
145 | | /// Thread id of this event. |
146 | 0 | SamplingProfiler::ThreadId getTid() const { |
147 | 0 | return tid_; |
148 | 0 | } |
149 | | |
150 | | /// Timestamp when this event occurred. |
151 | 0 | SamplingProfiler::TimeStampType getTimeStamp() const { |
152 | 0 | return timeStamp_; |
153 | 0 | } |
154 | | |
155 | | /// \return leaf frame of the stack in call tree corresponding to |
156 | | /// this instant sample event. |
157 | 0 | std::shared_ptr<ChromeStackFrameNode> getLeafNode() const { |
158 | 0 | return leafNode_; |
159 | 0 | } |
160 | | }; |
161 | | |
162 | | /// Represent all data for a trace session in chrome trace format. |
163 | | class ChromeTraceFormat { |
164 | | private: |
165 | | /// Id of target process. |
166 | | uint32_t pid_; |
167 | | /// Thread names map. |
168 | | SamplingProfiler::ThreadNamesMap threadNames_; |
169 | | /// The root of the stack frame tree. |
170 | | const std::shared_ptr<ChromeStackFrameNode> root_; |
171 | | /// Maintain all transformed chrome sample events. |
172 | | std::vector<ChromeSampleEvent> sampleEvents_; |
173 | | |
174 | | explicit ChromeTraceFormat( |
175 | | uint32_t pid, |
176 | | const SamplingProfiler::ThreadNamesMap &threadNames, |
177 | | std::unique_ptr<ChromeStackFrameNode> root) |
178 | 0 | : pid_(pid), threadNames_(threadNames), root_(std::move(root)) {} |
179 | | |
180 | | public: |
181 | | static ChromeTraceFormat create( |
182 | | uint32_t pid, |
183 | | const SamplingProfiler::ThreadNamesMap &threadNames, |
184 | | const std::vector<SamplingProfiler::StackTrace> &sampledStacks); |
185 | | |
186 | 0 | uint32_t getPid() const { |
187 | 0 | return pid_; |
188 | 0 | } |
189 | | |
190 | 0 | const SamplingProfiler::ThreadNamesMap &getThreadNames() const { |
191 | 0 | return threadNames_; |
192 | 0 | } |
193 | | |
194 | 0 | const ChromeStackFrameNode &getRoot() const { |
195 | 0 | return *root_; |
196 | 0 | } |
197 | | |
198 | 0 | const std::vector<ChromeSampleEvent> &getSampledEvents() const { |
199 | 0 | return sampleEvents_; |
200 | 0 | } |
201 | | }; |
202 | | |
203 | | /// Serialize input ChromeTraceFormat to output stream. |
204 | | class ChromeTraceSerializer { |
205 | | private: |
206 | | const SamplingProfiler &samplingProfiler_; |
207 | | ChromeTraceFormat trace_; |
208 | | SamplingProfiler::TimeStampType firstEventTimeStamp_; |
209 | | |
210 | | private: |
211 | | // Emit process_name metadata event. |
212 | | void serializeProcessName(JSONEmitter &json) const; |
213 | | // Emit threads related events. |
214 | | void serializeThreads(JSONEmitter &json) const; |
215 | | // Emit "sampled" events for captured stack traces. |
216 | | void serializeSampledEvents(JSONEmitter &json) const; |
217 | | // Emit "stackFrames" entries. |
218 | | void serializeStackFrames(JSONEmitter &json) const; |
219 | | |
220 | | // \return a serializable timeStamp string. |
221 | | static std::string getSerializedTimeStamp( |
222 | | SamplingProfiler::TimeStampType timeStamp); |
223 | | |
224 | | public: |
225 | | explicit ChromeTraceSerializer( |
226 | | const SamplingProfiler &sp, |
227 | | ChromeTraceFormat &&chromeTrace); |
228 | | |
229 | | /// Serialize chrome trace to \p OS. |
230 | | void serialize(llvh::raw_ostream &OS) const; |
231 | | }; |
232 | | |
233 | | /// Serialize the \p chromeTrace as a Profiler.Profile to \p os. See the url |
234 | | /// below for a description of that type. |
235 | | /// |
236 | | /// https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-Profile |
237 | | /// |
238 | | void serializeAsProfilerProfile( |
239 | | const SamplingProfiler &sp, |
240 | | llvh::raw_ostream &os, |
241 | | ChromeTraceFormat &&chromeTrace); |
242 | | |
243 | | } // namespace vm |
244 | | } // namespace hermes |
245 | | |
246 | | #endif // HERMESVM_SAMPLING_PROFILER_AVAILABLE |
247 | | |
248 | | #endif // HERMES_VM_PROFILER_CHROMETRACESERIALIZER_H |