/src/hermes/lib/VM/HeapSnapshot.cpp
Line | Count | Source (jump to first uncovered line) |
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 "hermes/VM/HeapSnapshot.h" |
9 | | |
10 | | #include "hermes/Support/Conversions.h" |
11 | | #include "hermes/Support/JSONEmitter.h" |
12 | | #include "hermes/Support/UTF8.h" |
13 | | #include "hermes/VM/GC.h" |
14 | | #include "hermes/VM/StackTracesTree.h" |
15 | | #include "hermes/VM/StringPrimitive.h" |
16 | | |
17 | | #include <type_traits> |
18 | | |
19 | | namespace hermes { |
20 | | namespace vm { |
21 | | #pragma GCC diagnostic push |
22 | | |
23 | | #ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32 |
24 | | #pragma GCC diagnostic ignored "-Wshorten-64-to-32" |
25 | | #endif |
26 | | #ifdef HERMES_MEMORY_INSTRUMENTATION |
27 | | |
28 | | namespace { |
29 | | |
30 | | /// Lower an instance \p e of enumeration \p Enum to its underlying type. |
31 | | template <typename Enum> |
32 | 0 | constexpr typename std::underlying_type<Enum>::type index(Enum e) { |
33 | 0 | return static_cast<typename std::underlying_type<Enum>::type>(e); |
34 | 0 | } Unexecuted instantiation: HeapSnapshot.cpp:std::__1::underlying_type<hermes::vm::HeapSnapshot::Section>::type hermes::vm::(anonymous namespace)::index<hermes::vm::HeapSnapshot::Section>(hermes::vm::HeapSnapshot::Section) Unexecuted instantiation: HeapSnapshot.cpp:std::__1::underlying_type<hermes::vm::HeapSnapshot::NodeType>::type hermes::vm::(anonymous namespace)::index<hermes::vm::HeapSnapshot::NodeType>(hermes::vm::HeapSnapshot::NodeType) Unexecuted instantiation: HeapSnapshot.cpp:std::__1::underlying_type<hermes::vm::HeapSnapshot::EdgeType>::type hermes::vm::(anonymous namespace)::index<hermes::vm::HeapSnapshot::EdgeType>(hermes::vm::HeapSnapshot::EdgeType) |
35 | | |
36 | | const char *kSectionLabels[] = { |
37 | | #define V8_SNAPSHOT_SECTION(enumerand, label) label, |
38 | | #include "hermes/VM/HeapSnapshot.def" |
39 | | }; |
40 | | |
41 | | } // namespace |
42 | | |
43 | | HeapSnapshot::HeapSnapshot( |
44 | | JSONEmitter &json, |
45 | | NodeIndex nodeCount, |
46 | | EdgeIndex edgeCount, |
47 | | size_t traceFunctionCount, |
48 | | StackTracesTree *stackTracesTree) |
49 | 0 | : json_(json), |
50 | 0 | stackTracesTree_(stackTracesTree), |
51 | | stringTable_( |
52 | 0 | stackTracesTree ? stackTracesTree->getStringTable() |
53 | 0 | : std::make_shared<StringSetVector>()) { |
54 | 0 | json_.openDict(); |
55 | 0 | emitMeta(nodeCount, edgeCount, traceFunctionCount); |
56 | 0 | } |
57 | | |
58 | 0 | HeapSnapshot::~HeapSnapshot() { |
59 | 0 | assert(edgeCount_ == expectedEdges_ && "Unexpected edges count"); |
60 | 0 | emitStrings(); |
61 | 0 | json_.closeDict(); // top level |
62 | 0 | } |
63 | | |
64 | 0 | void HeapSnapshot::beginSection(Section section) { |
65 | 0 | auto i = index(nextSection_); |
66 | |
|
67 | 0 | assert(!sectionOpened_ && "Sections must be explicitly close"); |
68 | 0 | assert(section != Section::END && "Can't open the end section."); |
69 | 0 | assert( |
70 | 0 | i <= index(section) && |
71 | 0 | "Trying to open a section after it has already been closed. Are your " |
72 | 0 | "sections ordered correctly?"); |
73 | | |
74 | 0 | for (; i < index(section); ++i) { |
75 | 0 | json_.emitKey(kSectionLabels[i]); |
76 | 0 | json_.openArray(); |
77 | 0 | json_.closeArray(); |
78 | 0 | } |
79 | |
|
80 | 0 | json_.emitKey(kSectionLabels[i]); |
81 | 0 | json_.openArray(); |
82 | |
|
83 | 0 | nextSection_ = section; |
84 | 0 | sectionOpened_ = true; |
85 | 0 | } |
86 | | |
87 | 0 | void HeapSnapshot::endSection(Section section) { |
88 | 0 | assert(sectionOpened_ && "No section to close"); |
89 | 0 | assert(section != Section::END && "Can't close the end section."); |
90 | 0 | assert(nextSection_ == section && "Closing a different section."); |
91 | | |
92 | 0 | json_.closeArray(); |
93 | 0 | nextSection_ = static_cast<Section>(index(section) + 1); |
94 | 0 | sectionOpened_ = false; |
95 | 0 | } |
96 | | |
97 | 0 | void HeapSnapshot::beginNode() { |
98 | | // Reset the edge counter. |
99 | 0 | currEdgeCount_ = 0; |
100 | 0 | assert( |
101 | 0 | nextSection_ == Section::Edges || |
102 | 0 | (nextSection_ == Section::Nodes && sectionOpened_)); |
103 | 0 | } |
104 | | |
105 | | void HeapSnapshot::endNode( |
106 | | NodeType type, |
107 | | llvh::StringRef name, |
108 | | NodeID id, |
109 | | HeapSizeType selfSize, |
110 | 0 | HeapSizeType traceNodeID) { |
111 | 0 | if (nextSection_ == Section::Edges) { |
112 | | // If the edges are being emitted, ignore node output. |
113 | 0 | return; |
114 | 0 | } |
115 | 0 | auto &nodeStats = traceNodeStats_[traceNodeID]; |
116 | 0 | nodeStats.count++; |
117 | 0 | nodeStats.size += selfSize; |
118 | 0 | assert(nextSection_ == Section::Nodes && sectionOpened_); |
119 | 0 | auto res = nodeToIndex_.try_emplace(id, nodeCount_++); |
120 | 0 | assert(res.second); |
121 | 0 | (void)res; |
122 | 0 | json_.emitValue(index(type)); |
123 | 0 | json_.emitValue(stringTable_->insert(name)); |
124 | 0 | json_.emitValue(id); |
125 | 0 | json_.emitValue(selfSize); |
126 | 0 | json_.emitValue(currEdgeCount_); |
127 | 0 | json_.emitValue(traceNodeID); |
128 | | // detachedness is always zero for hermes, since there's no DOM to attach to. |
129 | 0 | json_.emitValue(0); |
130 | 0 | #ifndef NDEBUG |
131 | 0 | expectedEdges_ += currEdgeCount_; |
132 | 0 | #endif |
133 | 0 | } |
134 | | |
135 | | void HeapSnapshot::addNamedEdge( |
136 | | EdgeType type, |
137 | | llvh::StringRef name, |
138 | 0 | NodeID toNode) { |
139 | | // If we're emitting nodes, only count the number of edges being processed, |
140 | | // but don't actually emit them. |
141 | 0 | currEdgeCount_++; |
142 | 0 | if (nextSection_ == Section::Nodes) { |
143 | 0 | return; |
144 | 0 | } |
145 | 0 | assert(edgeCount_ < expectedEdges_ && "Added more edges than were expected"); |
146 | 0 | assert(nextSection_ == Section::Edges && sectionOpened_); |
147 | 0 | edgeCount_++; |
148 | |
|
149 | 0 | json_.emitValue(index(type)); |
150 | 0 | json_.emitValue(stringTable_->insert(name)); |
151 | |
|
152 | 0 | auto nodeIt = nodeToIndex_.find(toNode); |
153 | 0 | assert(nodeIt != nodeToIndex_.end()); |
154 | | // Point to the beginning of the target node in the `nodes` flat array. |
155 | 0 | json_.emitValue(nodeIt->second * V8_SNAPSHOT_NODE_FIELD_COUNT); |
156 | 0 | } |
157 | | |
158 | | void HeapSnapshot::addIndexedEdge( |
159 | | EdgeType type, |
160 | | EdgeIndex edgeIndex, |
161 | 0 | NodeID toNode) { |
162 | | // If we're emitting nodes, only count the number of edges being processed, |
163 | | // but don't actually emit them. |
164 | 0 | currEdgeCount_++; |
165 | 0 | if (nextSection_ == Section::Nodes) { |
166 | 0 | return; |
167 | 0 | } |
168 | 0 | assert(edgeCount_ < expectedEdges_ && "Added more edges than were expected"); |
169 | 0 | assert(nextSection_ == Section::Edges && sectionOpened_); |
170 | 0 | edgeCount_++; |
171 | |
|
172 | 0 | json_.emitValue(index(type)); |
173 | 0 | json_.emitValue(edgeIndex); |
174 | |
|
175 | 0 | auto nodeIt = nodeToIndex_.find(toNode); |
176 | 0 | assert(nodeIt != nodeToIndex_.end()); |
177 | | // Point to the beginning of the target node in the `nodes` flat array. |
178 | 0 | json_.emitValue(nodeIt->second * V8_SNAPSHOT_NODE_FIELD_COUNT); |
179 | 0 | } |
180 | | |
181 | | void HeapSnapshot::addLocation( |
182 | | NodeID id, |
183 | | ::facebook::hermes::debugger::ScriptID script, |
184 | | uint32_t line, |
185 | 0 | uint32_t column) { |
186 | 0 | assert( |
187 | 0 | nextSection_ == Section::Locations && sectionOpened_ && |
188 | 0 | "Shouldn't be emitting locations until the location section starts"); |
189 | 0 | auto nodeIt = nodeToIndex_.find(id); |
190 | 0 | assert( |
191 | 0 | nodeIt != nodeToIndex_.end() && |
192 | 0 | "Couldn't add a location for an object that doesn't exist"); |
193 | 0 | json_.emitValue(nodeIt->second * V8_SNAPSHOT_NODE_FIELD_COUNT); |
194 | 0 | json_.emitValue(script); |
195 | | // The serialized format uses 0-based indexing for line and column, but the |
196 | | // parameters are 1-based. |
197 | 0 | assert(line != 0 && "Line should be 1-based"); |
198 | 0 | assert(column != 0 && "Column should be 1-based"); |
199 | 0 | json_.emitValue(line - 1); |
200 | 0 | json_.emitValue(column - 1); |
201 | 0 | } |
202 | | |
203 | | void HeapSnapshot::addSample( |
204 | | std::chrono::microseconds timestamp, |
205 | 0 | NodeID lastSeenObjectID) { |
206 | 0 | assert( |
207 | 0 | nextSection_ == Section::Samples && sectionOpened_ && |
208 | 0 | "Shouldn't be emitting samples until the sample section starts"); |
209 | 0 | assert( |
210 | 0 | lastSeenObjectID != GCBase::IDTracker::kInvalidNode && |
211 | 0 | "Last seen object ID must be valid"); |
212 | 0 | json_.emitValues( |
213 | 0 | {static_cast<uint64_t>(timestamp.count()), |
214 | 0 | static_cast<uint64_t>(lastSeenObjectID)}); |
215 | 0 | } |
216 | | |
217 | 0 | const char *HeapSnapshot::nodeTypeToName(NodeType type) { |
218 | 0 | switch (type) { |
219 | 0 | #define V8_NODE_TYPE(enumerand, label) \ |
220 | 0 | case NodeType::enumerand: \ |
221 | 0 | return #label; |
222 | 0 | #include "hermes/VM/HeapSnapshot.def" |
223 | 0 | } |
224 | 0 | assert(false && "Invalid NodeType"); |
225 | 0 | return ""; |
226 | 0 | } |
227 | | |
228 | 0 | const char *HeapSnapshot::edgeTypeToName(EdgeType type) { |
229 | 0 | switch (type) { |
230 | 0 | #define V8_EDGE_TYPE(enumerand, label) \ |
231 | 0 | case EdgeType::enumerand: \ |
232 | 0 | return #label; |
233 | 0 | #include "hermes/VM/HeapSnapshot.def" |
234 | 0 | } |
235 | 0 | assert(false && "Invalid EdgeType"); |
236 | 0 | return ""; |
237 | 0 | } |
238 | | |
239 | | void HeapSnapshot::emitMeta( |
240 | | HeapSnapshot::NodeIndex nodeCount, |
241 | | HeapSnapshot::EdgeIndex edgeCount, |
242 | 0 | size_t traceFunctionCount) { |
243 | 0 | json_.emitKey("snapshot"); |
244 | 0 | json_.openDict(); |
245 | |
|
246 | 0 | json_.emitKey("meta"); |
247 | 0 | json_.openDict(); |
248 | |
|
249 | 0 | json_.emitKey("node_fields"); |
250 | 0 | json_.openArray(); |
251 | 0 | json_.emitValues({ |
252 | 0 | "type", |
253 | 0 | #define V8_NODE_FIELD(label, type) #label, |
254 | 0 | #include "hermes/VM/HeapSnapshot.def" |
255 | 0 | }); |
256 | 0 | json_.closeArray(); // node_fields |
257 | |
|
258 | 0 | json_.emitKey("node_types"); |
259 | 0 | json_.openArray(); |
260 | 0 | json_.openArray(); |
261 | 0 | json_.emitValues({ |
262 | 0 | #define V8_NODE_TYPE(enumerand, label) label, |
263 | 0 | #include "hermes/VM/HeapSnapshot.def" |
264 | 0 | }); |
265 | 0 | json_.closeArray(); |
266 | 0 | json_.emitValues({ |
267 | 0 | #define V8_NODE_FIELD(label, type) #type, |
268 | 0 | #include "hermes/VM/HeapSnapshot.def" |
269 | 0 | }); |
270 | 0 | json_.closeArray(); // node_types |
271 | |
|
272 | 0 | json_.emitKey("edge_fields"); |
273 | 0 | json_.openArray(); |
274 | 0 | json_.emitValues({ |
275 | 0 | "type", |
276 | 0 | #define V8_EDGE_FIELD(label, type) #label, |
277 | 0 | #include "hermes/VM/HeapSnapshot.def" |
278 | 0 | }); |
279 | 0 | json_.closeArray(); // edge_fields |
280 | |
|
281 | 0 | json_.emitKey("edge_types"); |
282 | 0 | json_.openArray(); |
283 | 0 | json_.openArray(); |
284 | 0 | json_.emitValues({ |
285 | 0 | #define V8_EDGE_TYPE(enumerand, label) label, |
286 | 0 | #include "hermes/VM/HeapSnapshot.def" |
287 | 0 | }); |
288 | 0 | json_.closeArray(); |
289 | 0 | json_.emitValues({ |
290 | 0 | #define V8_EDGE_FIELD(label, type) #type, |
291 | 0 | #include "hermes/VM/HeapSnapshot.def" |
292 | 0 | }); |
293 | 0 | json_.closeArray(); // edge_types |
294 | |
|
295 | 0 | json_.emitKey("trace_function_info_fields"); |
296 | 0 | json_.openArray(); |
297 | 0 | json_.emitValues({ |
298 | 0 | #define V8_TRACE_FUNCTION_INFO_FIELD(name) #name, |
299 | 0 | #include "hermes/VM/HeapSnapshot.def" |
300 | 0 | }); |
301 | 0 | json_.closeArray(); // trace_function_info_fields |
302 | |
|
303 | 0 | json_.emitKey("trace_node_fields"); |
304 | 0 | json_.openArray(); |
305 | 0 | json_.emitValues({ |
306 | 0 | #define V8_TRACE_NODE_FIELD(name) #name, |
307 | 0 | #include "hermes/VM/HeapSnapshot.def" |
308 | 0 | }); |
309 | 0 | json_.closeArray(); // trace_node_fields |
310 | |
|
311 | 0 | json_.emitKey("sample_fields"); |
312 | 0 | json_.openArray(); |
313 | 0 | json_.emitValues({ |
314 | 0 | #define V8_SAMPLE_FIELD(name) #name, |
315 | 0 | #include "hermes/VM/HeapSnapshot.def" |
316 | 0 | }); |
317 | 0 | json_.closeArray(); // sample_fields |
318 | |
|
319 | 0 | json_.emitKey("location_fields"); |
320 | 0 | json_.openArray(); |
321 | 0 | json_.emitValues({ |
322 | 0 | #define V8_LOCATION_FIELD(label) #label, |
323 | 0 | #include "hermes/VM/HeapSnapshot.def" |
324 | 0 | }); |
325 | 0 | json_.closeArray(); // location_fields |
326 | |
|
327 | 0 | json_.closeDict(); // "meta" |
328 | |
|
329 | 0 | json_.emitKey("node_count"); |
330 | | // This can be zero because it's only used as an optimization hint to |
331 | | // the viewer. |
332 | 0 | json_.emitValue(nodeCount); |
333 | 0 | json_.emitKey("edge_count"); |
334 | | // This can be zero because it's only used as an optimization hint to |
335 | | // the viewer. |
336 | 0 | json_.emitValue(edgeCount); |
337 | 0 | json_.emitKey("trace_function_count"); |
338 | 0 | json_.emitValue(traceFunctionCount); |
339 | 0 | json_.closeDict(); // "snapshot" |
340 | 0 | } |
341 | | |
342 | 0 | void HeapSnapshot::emitAllocationTraceInfo() { |
343 | 0 | if (!stackTracesTree_) { |
344 | 0 | return; |
345 | 0 | } |
346 | | |
347 | 0 | llvh::DenseMap< |
348 | 0 | StackTracesTreeNode::SourceLoc, |
349 | 0 | size_t, |
350 | 0 | StackTracesTreeNode::SourceLocMapInfo> |
351 | 0 | sourceLocToFuncIdxMap; |
352 | 0 | size_t nextFunctionIdx = 0; |
353 | |
|
354 | 0 | std::stack< |
355 | 0 | StackTracesTreeNode *, |
356 | 0 | llvh::SmallVector<StackTracesTreeNode *, 128>> |
357 | 0 | nodeStack; |
358 | |
|
359 | 0 | beginSection(Section::TraceFunctionInfos); |
360 | 0 | nodeStack.push(stackTracesTree_->getRootNode()); |
361 | 0 | while (!nodeStack.empty()) { |
362 | 0 | auto curNode = nodeStack.top(); |
363 | 0 | nodeStack.pop(); |
364 | 0 | auto entry = sourceLocToFuncIdxMap.find(curNode->sourceLoc); |
365 | 0 | if (entry == sourceLocToFuncIdxMap.end()) { |
366 | 0 | const auto functionIdx = nextFunctionIdx++; |
367 | 0 | sourceLocToFuncIdxMap.try_emplace(curNode->sourceLoc, functionIdx); |
368 | | // function_id needs to match the zero-based index of this function in the |
369 | | // list. |
370 | 0 | json_.emitValue(functionIdx); // "function_id" |
371 | 0 | json_.emitValue(curNode->name); // "name" |
372 | 0 | json_.emitValue(curNode->sourceLoc.scriptName); // "script_name" |
373 | 0 | json_.emitValue(curNode->sourceLoc.scriptID); // "script_id" |
374 | | // These should be emitted as 1-based, not 0-based like locations. |
375 | 0 | json_.emitValue(curNode->sourceLoc.lineNo); // "line" |
376 | 0 | json_.emitValue(curNode->sourceLoc.columnNo); // "column" |
377 | 0 | } |
378 | 0 | for (auto child : curNode->getChildren()) { |
379 | 0 | nodeStack.push(child); |
380 | 0 | } |
381 | 0 | } |
382 | 0 | endSection(Section::TraceFunctionInfos); |
383 | | |
384 | | // Save the trace function count (which is essentially the number of nodes |
385 | | // on the tree with unique SourceLocs. |
386 | 0 | traceFunctionCount_ = sourceLocToFuncIdxMap.size(); |
387 | |
|
388 | 0 | beginSection(Section::TraceTree); |
389 | 0 | nodeStack.push(stackTracesTree_->getRootNode()); |
390 | 0 | while (!nodeStack.empty()) { |
391 | 0 | auto curNode = nodeStack.top(); |
392 | 0 | nodeStack.pop(); |
393 | 0 | if (curNode == nullptr) { |
394 | 0 | json_.closeArray(); |
395 | 0 | continue; |
396 | 0 | } |
397 | 0 | json_.emitValue(curNode->id); |
398 | 0 | auto sourceLocIdxIt = sourceLocToFuncIdxMap.find(curNode->sourceLoc); |
399 | 0 | assert( |
400 | 0 | sourceLocIdxIt != sourceLocToFuncIdxMap.end() && |
401 | 0 | "Could not find trace function info ID for sourceLoc"); |
402 | | // This index must correspond to the "function_id" emitted in the |
403 | | // "trace_function_infos" section. |
404 | 0 | json_.emitValue(sourceLocIdxIt->second); // "function_info_index" |
405 | 0 | json_.emitValue(traceNodeStats_[curNode->id].count); // "count" |
406 | 0 | json_.emitValue(traceNodeStats_[curNode->id].size); // "size" |
407 | 0 | json_.openArray(); |
408 | 0 | nodeStack.push(nullptr); |
409 | 0 | for (auto child : curNode->getChildren()) { |
410 | 0 | nodeStack.push(child); |
411 | 0 | } |
412 | 0 | } |
413 | 0 | endSection(Section::TraceTree); |
414 | 0 | } |
415 | | |
416 | 0 | void HeapSnapshot::emitStrings() { |
417 | 0 | beginSection(Section::Strings); |
418 | |
|
419 | 0 | for (const auto &str : *stringTable_) { |
420 | 0 | json_.emitValue(str); |
421 | 0 | } |
422 | |
|
423 | 0 | endSection(Section::Strings); |
424 | 0 | } |
425 | | |
426 | | ChromeSamplingMemoryProfile::ChromeSamplingMemoryProfile(JSONEmitter &json) |
427 | 0 | : json_(json) { |
428 | 0 | json_.openDict(); |
429 | 0 | } |
430 | | |
431 | 0 | ChromeSamplingMemoryProfile::~ChromeSamplingMemoryProfile() { |
432 | 0 | json_.closeDict(); |
433 | 0 | } |
434 | | |
435 | | void ChromeSamplingMemoryProfile::emitTree( |
436 | | StackTracesTree *stackTracesTree, |
437 | | const llvh::DenseMap<StackTracesTreeNode *, llvh::DenseMap<size_t, size_t>> |
438 | 0 | &sizesToCounts) { |
439 | 0 | json_.emitKey("head"); |
440 | 0 | emitNode( |
441 | 0 | stackTracesTree->getRootNode(), |
442 | 0 | *stackTracesTree->getStringTable(), |
443 | 0 | sizesToCounts); |
444 | 0 | } |
445 | | |
446 | | void ChromeSamplingMemoryProfile::emitNode( |
447 | | StackTracesTreeNode *node, |
448 | | StringSetVector &strings, |
449 | | const llvh::DenseMap<StackTracesTreeNode *, llvh::DenseMap<size_t, size_t>> |
450 | 0 | &sizesToCounts) { |
451 | 0 | json_.openDict(); |
452 | 0 | json_.emitKey("callFrame"); |
453 | 0 | json_.openDict(); |
454 | 0 | json_.emitKeyValue("functionName", strings[node->name]); |
455 | 0 | json_.emitKeyValue("scriptId", std::to_string(node->sourceLoc.scriptID)); |
456 | 0 | json_.emitKeyValue("url", strings[node->sourceLoc.scriptName]); |
457 | | // For the sampling memory profiler, lines should be 0-based. The source |
458 | | // location is 1-based, so subtract 1 here. |
459 | 0 | json_.emitKeyValue("lineNumber", node->sourceLoc.lineNo - 1); |
460 | 0 | json_.emitKeyValue("columnNumber", node->sourceLoc.columnNo - 1); |
461 | 0 | json_.closeDict(); |
462 | |
|
463 | 0 | size_t selfSize = 0; |
464 | 0 | for (const auto &sizeAndCount : sizesToCounts.lookup(node)) { |
465 | | // Size is the key, count is the value. |
466 | 0 | selfSize += sizeAndCount.first * sizeAndCount.second; |
467 | 0 | } |
468 | 0 | json_.emitKeyValue("selfSize", selfSize); |
469 | 0 | json_.emitKeyValue("id", node->id); |
470 | |
|
471 | 0 | json_.emitKey("children"); |
472 | 0 | json_.openArray(); |
473 | 0 | for (StackTracesTreeNode *child : node->getChildren()) { |
474 | 0 | emitNode(child, strings, sizesToCounts); |
475 | 0 | } |
476 | 0 | json_.closeArray(); |
477 | 0 | json_.closeDict(); |
478 | 0 | } |
479 | | |
480 | 0 | void ChromeSamplingMemoryProfile::beginSamples() { |
481 | 0 | json_.emitKey("samples"); |
482 | 0 | json_.openArray(); |
483 | 0 | } |
484 | | |
485 | | void ChromeSamplingMemoryProfile::emitSample( |
486 | | size_t size, |
487 | | StackTracesTreeNode *node, |
488 | 0 | uint64_t id) { |
489 | 0 | json_.openDict(); |
490 | 0 | json_.emitKeyValue("size", size); |
491 | 0 | json_.emitKeyValue("nodeId", node->id); |
492 | 0 | json_.emitKeyValue("ordinal", id); |
493 | 0 | json_.closeDict(); |
494 | 0 | } |
495 | | |
496 | 0 | void ChromeSamplingMemoryProfile::endSamples() { |
497 | 0 | json_.closeArray(); |
498 | 0 | } |
499 | | |
500 | | #endif |
501 | | |
502 | 0 | std::string converter(const char *name) { |
503 | 0 | return std::string(name); |
504 | 0 | } |
505 | 0 | std::string converter(unsigned index) { |
506 | 0 | return std::to_string(index); |
507 | 0 | } |
508 | 0 | std::string converter(int index) { |
509 | 0 | return std::to_string(index); |
510 | 0 | } |
511 | 0 | std::string converter(const StringPrimitive *str) { |
512 | 0 | llvh::SmallVector<char16_t, 16> buf; |
513 | 0 | str->appendUTF16String(buf); |
514 | 0 | std::string out; |
515 | 0 | convertUTF16ToUTF8WithReplacements(out, UTF16Ref(buf)); |
516 | 0 | return out; |
517 | 0 | } |
518 | 0 | std::string converter(const UTF16Ref ref) { |
519 | 0 | std::string out; |
520 | 0 | convertUTF16ToUTF8WithReplacements(out, ref); |
521 | 0 | return out; |
522 | 0 | } |
523 | | |
524 | | } // namespace vm |
525 | | } // namespace hermes |