Coverage Report

Created: 2025-01-28 06:38

/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