Coverage Report

Created: 2025-09-04 06:34

/src/hermes/lib/VM/Profiler/SamplingProfiler.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/Profiler/SamplingProfiler.h"
9
10
#if HERMESVM_SAMPLING_PROFILER_AVAILABLE
11
12
#include "hermes/VM/Callable.h"
13
#include "hermes/VM/HostModel.h"
14
#include "hermes/VM/Runtime.h"
15
#include "hermes/VM/RuntimeModule-inline.h"
16
#include "hermes/VM/StackFrame-inline.h"
17
18
#include "llvh/Support/Compiler.h"
19
20
#include "ChromeTraceSerializer.h"
21
#include "ProfileGenerator.h"
22
#include "SamplingProfilerSampler.h"
23
24
#include <fcntl.h>
25
#include <cassert>
26
#include <chrono>
27
#include <cmath>
28
#include <csignal>
29
#include <random>
30
#include <thread>
31
32
namespace hermes {
33
namespace vm {
34
35
0
void SamplingProfiler::registerDomain(Domain *domain) {
36
  // If domain is not already registered, add it to the list.
37
0
  auto it = std::find(domains_.begin(), domains_.end(), domain);
38
0
  if (it == domains_.end())
39
0
    domains_.push_back(domain);
40
0
}
41
42
SamplingProfiler::NativeFunctionFrameInfo
43
0
SamplingProfiler::registerNativeFunction(NativeFunction *nativeFunction) {
44
  // If nativeFunction is not already registered, add it to the list.
45
0
  auto it = std::find(
46
0
      nativeFunctions_.begin(), nativeFunctions_.end(), nativeFunction);
47
0
  if (it != nativeFunctions_.end()) {
48
0
    return it - nativeFunctions_.begin();
49
0
  }
50
51
0
  nativeFunctions_.push_back(nativeFunction);
52
0
  return nativeFunctions_.size() - 1;
53
0
}
54
55
0
void SamplingProfiler::markRootsForCompleteMarking(RootAcceptor &acceptor) {
56
0
  std::lock_guard<std::mutex> lockGuard(runtimeDataLock_);
57
0
  for (Domain *&domain : domains_) {
58
0
    acceptor.acceptPtr(domain);
59
0
  }
60
0
}
61
62
0
void SamplingProfiler::markRoots(RootAcceptor &acceptor) {
63
0
  std::lock_guard<std::mutex> lockGuard(runtimeDataLock_);
64
0
  for (Domain *&domain : domains_) {
65
0
    acceptor.acceptPtr(domain);
66
0
  }
67
68
0
  for (NativeFunction *&fn : nativeFunctions_) {
69
0
    acceptor.acceptPtr(fn);
70
0
  }
71
0
}
72
73
uint32_t SamplingProfiler::walkRuntimeStack(
74
    StackTrace &sampleStorage,
75
    InLoom inLoom,
76
0
    uint32_t startIndex) {
77
0
  unsigned count = startIndex;
78
79
  // TODO: capture leaf frame IP.
80
0
  const Inst *ip = nullptr;
81
0
  for (ConstStackFramePtr frame : runtime_.getStackFrames()) {
82
    // Whether we successfully captured a stack frame or not.
83
0
    bool capturedFrame = true;
84
0
    auto &frameStorage = sampleStorage.stack[count];
85
    // Check if it is pure JS frame.
86
0
    auto *calleeCodeBlock = frame.getCalleeCodeBlock(runtime_);
87
0
    if (calleeCodeBlock != nullptr) {
88
0
      frameStorage.kind = StackFrame::FrameKind::JSFunction;
89
0
      frameStorage.jsFrame.functionId = calleeCodeBlock->getFunctionID();
90
0
      frameStorage.jsFrame.offset =
91
0
          (ip == nullptr ? 0 : calleeCodeBlock->getOffsetOf(ip));
92
0
      auto *module = calleeCodeBlock->getRuntimeModule();
93
0
      assert(module != nullptr && "Cannot fetch runtimeModule for code block");
94
0
      frameStorage.jsFrame.module = module;
95
      // Don't execute a read or write barrier here because this is a signal
96
      // handler.
97
0
      if (inLoom != InLoom::Yes)
98
0
        registerDomain(module->getDomainForSamplingProfiler(runtime_));
99
0
    } else if (
100
0
        auto *nativeFunction =
101
0
            dyn_vmcast<NativeFunction>(frame.getCalleeClosureUnsafe())) {
102
0
      frameStorage.kind = vmisa<FinalizableNativeFunction>(nativeFunction)
103
0
          ? StackFrame::FrameKind::FinalizableNativeFunction
104
0
          : StackFrame::FrameKind::NativeFunction;
105
0
      if (inLoom != InLoom::Yes) {
106
0
        frameStorage.nativeFrame = registerNativeFunction(nativeFunction);
107
0
      } else {
108
0
        frameStorage.nativeFunctionPtrForLoom =
109
0
            nativeFunction->getFunctionPtr();
110
0
      }
111
0
    } else {
112
      // TODO: handle BoundFunction.
113
0
      capturedFrame = false;
114
0
    }
115
116
    // Update ip to caller for next iteration.
117
0
    ip = frame.getSavedIP();
118
0
    if (capturedFrame) {
119
0
      ++count;
120
0
      if (count >= sampleStorage.stack.size()) {
121
0
        break;
122
0
      }
123
0
    }
124
0
  }
125
0
  sampleStorage.tid = threadID_;
126
0
  sampleStorage.timeStamp = std::chrono::steady_clock::now();
127
0
  return count;
128
0
}
129
130
SamplingProfiler::SamplingProfiler(Runtime &runtime)
131
0
    : threadID_{oscompat::global_thread_id()}, runtime_{runtime} {
132
0
  threadNames_[threadID_] = oscompat::thread_name();
133
0
  sampling_profiler::Sampler::get()->registerRuntime(this);
134
0
}
135
136
0
void SamplingProfiler::dumpSampledStackGlobal(llvh::raw_ostream &OS) {
137
0
  auto globalProfiler = sampling_profiler::Sampler::get();
138
0
  std::lock_guard<std::mutex> lk(globalProfiler->profilerLock_);
139
0
  if (!globalProfiler->profilers_.empty()) {
140
0
    auto *localProfiler = *globalProfiler->profilers_.begin();
141
0
    localProfiler->dumpSampledStack(OS);
142
0
  }
143
0
}
144
145
0
void SamplingProfiler::dumpSampledStack(llvh::raw_ostream &OS) {
146
0
  std::lock_guard<std::mutex> lk(runtimeDataLock_);
147
0
  OS << "dumpSamples called from runtime\n";
148
0
  OS << "Total " << sampledStacks_.size() << " samples\n";
149
0
  for (unsigned i = 0; i < sampledStacks_.size(); ++i) {
150
0
    auto &sample = sampledStacks_[i];
151
0
    uint64_t timeStamp = sample.timeStamp.time_since_epoch().count();
152
0
    OS << "[" << i << "]: tid[" << sample.tid << "], ts[" << timeStamp << "] ";
153
154
    // Leaf frame is in sample[0] so dump it backward to
155
    // get root => leaf represenation.
156
0
    for (auto iter = sample.stack.rbegin(); iter != sample.stack.rend();
157
0
         ++iter) {
158
0
      const StackFrame &frame = *iter;
159
0
      switch (frame.kind) {
160
0
        case StackFrame::FrameKind::JSFunction:
161
0
          OS << "[JS] " << frame.jsFrame.functionId << ":"
162
0
             << frame.jsFrame.offset;
163
0
          break;
164
165
0
        case StackFrame::FrameKind::NativeFunction: {
166
0
          NativeFunctionPtr nativeFrame = getNativeFunctionPtr(frame);
167
0
          OS << "[Native] " << reinterpret_cast<uintptr_t>(nativeFrame);
168
0
          break;
169
0
        }
170
171
0
        case StackFrame::FrameKind::FinalizableNativeFunction:
172
0
          OS << "[HostFunction] " << getNativeFunctionName(frame);
173
0
          break;
174
175
0
        default:
176
0
          llvm_unreachable("Unknown frame kind");
177
0
      }
178
0
      OS << " => ";
179
0
    }
180
0
    OS << "\n";
181
0
  }
182
0
}
183
184
0
void SamplingProfiler::dumpChromeTraceGlobal(llvh::raw_ostream &OS) {
185
0
  auto globalProfiler = sampling_profiler::Sampler::get();
186
0
  std::lock_guard<std::mutex> lk(globalProfiler->profilerLock_);
187
0
  if (!globalProfiler->profilers_.empty()) {
188
0
    auto *localProfiler = *globalProfiler->profilers_.begin();
189
0
    localProfiler->dumpChromeTrace(OS);
190
0
  }
191
0
}
192
193
0
void SamplingProfiler::dumpChromeTrace(llvh::raw_ostream &OS) {
194
0
  std::lock_guard<std::mutex> lk(runtimeDataLock_);
195
0
  auto pid = oscompat::process_id();
196
0
  ChromeTraceSerializer serializer(
197
0
      *this, ChromeTraceFormat::create(pid, threadNames_, sampledStacks_));
198
0
  serializer.serialize(OS);
199
0
  clear();
200
0
}
201
202
0
void SamplingProfiler::serializeInDevToolsFormat(llvh::raw_ostream &OS) {
203
0
  std::lock_guard<std::mutex> lk(runtimeDataLock_);
204
0
  hermes::vm::serializeAsProfilerProfile(
205
0
      *this,
206
0
      OS,
207
0
      ChromeTraceFormat::create(
208
0
          oscompat::process_id(), threadNames_, sampledStacks_));
209
0
  clear();
210
0
}
211
212
0
facebook::hermes::sampling_profiler::Profile SamplingProfiler::dumpAsProfile() {
213
0
  std::lock_guard<std::mutex> lk(runtimeDataLock_);
214
215
0
  facebook::hermes::sampling_profiler::Profile profile =
216
0
      generateProfile(*this, sampledStacks_);
217
218
0
  clear();
219
0
  return profile;
220
0
}
221
222
0
bool SamplingProfiler::enable(double meanHzFreq) {
223
0
  return sampling_profiler::Sampler::get()->enable(meanHzFreq);
224
0
}
225
226
0
bool SamplingProfiler::disable() {
227
0
  return sampling_profiler::Sampler::get()->disable();
228
0
}
229
230
0
void SamplingProfiler::clear() {
231
0
  sampledStacks_.clear();
232
  // Release all strong roots.
233
0
  domains_.clear();
234
0
  nativeFunctions_.clear();
235
0
}
236
237
void SamplingProfiler::suspend(
238
    SuspendFrameInfo::Kind reason,
239
0
    llvh::StringRef gcSuspendDetails) {
240
  // Need to check whether the profiler is enabled without holding the
241
  // runtimeDataLock_. Otherwise, we'd have a lock inversion.
242
0
  bool enabled = sampling_profiler::Sampler::get()->enabled();
243
244
0
  std::lock_guard<std::mutex> lk(runtimeDataLock_);
245
0
  if (++suspendCount_ > 1) {
246
    // If there are multiple nested suspend calls use a default "multiple" frame
247
    // kind for the suspend entry in the call stack.
248
0
    reason = SuspendFrameInfo::Kind::Multiple;
249
0
  } else if (reason == SuspendFrameInfo::Kind::GC && gcSuspendDetails.empty()) {
250
    // If no GC reason was provided, use a default "suspend" value.
251
0
    gcSuspendDetails = "suspend";
252
0
  }
253
254
  // Only record the stack trace for the first suspend() call.
255
0
  if (LLVM_UNLIKELY(enabled && suspendCount_ == 1)) {
256
0
    recordPreSuspendStack(reason, gcSuspendDetails);
257
0
  }
258
0
}
259
260
0
void SamplingProfiler::resume() {
261
0
  std::lock_guard<std::mutex> lk(runtimeDataLock_);
262
0
  assert(suspendCount_ > 0 && "resume() without suspend()");
263
0
  if (--suspendCount_ == 0) {
264
0
    preSuspendStackDepth_ = 0;
265
0
  }
266
0
}
267
268
void SamplingProfiler::recordPreSuspendStack(
269
    SuspendFrameInfo::Kind reason,
270
0
    llvh::StringRef gcFrame) {
271
0
  SuspendFrameInfo suspendExtraInfo{reason, nullptr};
272
0
  if (reason == SuspendFrameInfo::Kind::GC) {
273
0
    std::pair<std::unordered_set<std::string>::iterator, bool> retPair =
274
0
        gcEventExtraInfoSet_.emplace(gcFrame);
275
0
    suspendExtraInfo.gcFrame = &(*(retPair.first));
276
0
  }
277
278
0
  auto &leafFrame = preSuspendStackStorage_.stack[0];
279
0
  leafFrame.kind = StackFrame::FrameKind::SuspendFrame;
280
0
  leafFrame.suspendFrame = suspendExtraInfo;
281
282
  // Leaf frame slot has been used, filling from index 1.
283
0
  preSuspendStackDepth_ =
284
0
      walkRuntimeStack(preSuspendStackStorage_, InLoom::No, 1);
285
0
}
286
287
bool operator==(
288
    const SamplingProfiler::StackFrame &left,
289
0
    const SamplingProfiler::StackFrame &right) {
290
0
  if (left.kind != right.kind) {
291
0
    return false;
292
0
  }
293
0
  switch (left.kind) {
294
0
    case SamplingProfiler::StackFrame::FrameKind::JSFunction:
295
0
      return left.jsFrame.functionId == right.jsFrame.functionId &&
296
0
          left.jsFrame.offset == right.jsFrame.offset;
297
298
0
    case SamplingProfiler::StackFrame::FrameKind::NativeFunction:
299
0
    case SamplingProfiler::StackFrame::FrameKind::FinalizableNativeFunction:
300
0
      return left.nativeFrame == right.nativeFrame;
301
302
0
    case SamplingProfiler::StackFrame::FrameKind::SuspendFrame:
303
0
      return left.suspendFrame.kind == right.suspendFrame.kind &&
304
0
          left.suspendFrame.gcFrame == right.suspendFrame.gcFrame;
305
306
0
    default:
307
0
      llvm_unreachable("Unknown frame kind");
308
0
  }
309
0
}
310
311
} // namespace vm
312
} // namespace hermes
313
314
#endif // HERMESVM_SAMPLING_PROFILER_AVAILABLE