Coverage Report

Created: 2023-02-22 06:51

/src/hermes/lib/VM/Profiler/SamplingProfilerSampler.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 "SamplingProfilerSampler.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
22
#include <fcntl.h>
23
#include <cassert>
24
#include <chrono>
25
#include <cmath>
26
#include <csignal>
27
#include <random>
28
#include <thread>
29
30
namespace hermes {
31
namespace vm {
32
namespace sampling_profiler {
33
34
0
Sampler::~Sampler() = default;
35
36
0
void Sampler::registerRuntime(SamplingProfiler *profiler) {
37
0
  std::lock_guard<std::mutex> lockGuard(profilerLock_);
38
0
  profilers_.insert(profiler);
39
0
  platformRegisterRuntime(profiler);
40
0
}
41
42
0
void Sampler::unregisterRuntime(SamplingProfiler *profiler) {
43
0
  std::lock_guard<std::mutex> lockGuard(profilerLock_);
44
0
  bool succeed = profilers_.erase(profiler);
45
  // TODO: should we allow recursive style
46
  // register/register -> unregister/unregister call?
47
0
  assert(succeed && "How can runtime not registered yet?");
48
0
  (void)succeed;
49
0
  platformUnregisterRuntime(profiler);
50
0
}
51
52
0
bool Sampler::sampleStacks() {
53
0
  for (SamplingProfiler *localProfiler : profilers_) {
54
0
    std::lock_guard<std::mutex> lk(localProfiler->runtimeDataLock_);
55
0
    if (!sampleStack(localProfiler)) {
56
0
      return false;
57
0
    }
58
0
    platformPostSampleStack(localProfiler);
59
0
  }
60
0
  return true;
61
0
}
62
63
0
bool Sampler::sampleStack(SamplingProfiler *localProfiler) {
64
0
  if (localProfiler->suspendCount_ > 0) {
65
    // Sampling profiler is suspended. Copy pre-captured stack instead without
66
    // interrupting the VM thread.
67
0
    if (localProfiler->preSuspendStackDepth_ > 0) {
68
0
      sampleStorage_ = localProfiler->preSuspendStackStorage_;
69
0
      sampledStackDepth_ = localProfiler->preSuspendStackDepth_;
70
0
    } else {
71
      // This suspension didn't record a stack trace. For example, a GC (like
72
      // mallocGC) did not record JS stack.
73
      // TODO: fix this for all cases.
74
0
      sampledStackDepth_ = 0;
75
0
    }
76
0
  } else {
77
    // Ensure there are no allocations in the signal handler by keeping ample
78
    // reserved space.
79
0
    localProfiler->domains_.reserve(
80
0
        localProfiler->domains_.size() + SamplingProfiler::kMaxStackDepth);
81
0
    size_t domainCapacityBefore = localProfiler->domains_.capacity();
82
0
    (void)domainCapacityBefore;
83
84
    // Ditto for native functions.
85
0
    localProfiler->nativeFunctions_.reserve(
86
0
        localProfiler->nativeFunctions_.size() +
87
0
        SamplingProfiler::kMaxStackDepth);
88
0
    size_t nativeFunctionsCapacityBefore =
89
0
        localProfiler->nativeFunctions_.capacity();
90
0
    (void)nativeFunctionsCapacityBefore;
91
92
0
    if (!platformSuspendVMAndWalkStack(localProfiler)) {
93
0
      return false;
94
0
    }
95
96
0
    assert(
97
0
        localProfiler->domains_.capacity() == domainCapacityBefore &&
98
0
        "Must not dynamically allocate in signal handler");
99
100
0
    assert(
101
0
        localProfiler->nativeFunctions_.capacity() ==
102
0
            nativeFunctionsCapacityBefore &&
103
0
        "Must not dynamically allocate in signal handler");
104
0
  }
105
106
0
  assert(
107
0
      sampledStackDepth_ <= sampleStorage_.stack.size() &&
108
0
      "How can we sample more frames than storage?");
109
0
  localProfiler->sampledStacks_.emplace_back(
110
0
      sampleStorage_.tid,
111
0
      sampleStorage_.timeStamp,
112
0
      sampleStorage_.stack.begin(),
113
0
      sampleStorage_.stack.begin() + sampledStackDepth_);
114
0
  return true;
115
0
}
116
117
0
void Sampler::walkRuntimeStack(SamplingProfiler *profiler) {
118
0
  assert(
119
0
      profiler->suspendCount_ == 0 &&
120
0
      "Shouldn't interrupt the VM thread when the sampling profiler is "
121
0
      "suspended.");
122
123
  // Sampling stack will touch GC objects(like closure) so only do so if heap
124
  // is valid.
125
0
  auto &curThreadRuntime = profiler->runtime_;
126
0
  assert(
127
0
      !curThreadRuntime.getHeap().inGC() &&
128
0
      "sampling profiler should be suspended before GC");
129
0
  (void)curThreadRuntime;
130
0
  sampledStackDepth_ =
131
0
      profiler->walkRuntimeStack(sampleStorage_, SamplingProfiler::InLoom::No);
132
0
}
133
134
0
void Sampler::timerLoop() {
135
0
  oscompat::set_thread_name("hermes-sampling-profiler");
136
137
0
  constexpr double kMeanMilliseconds = 10;
138
0
  constexpr double kStdDevMilliseconds = 5;
139
0
  std::random_device rd{};
140
0
  std::mt19937 gen{rd()};
141
  // The amount of time that is spent sleeping comes from a normal distribution,
142
  // to avoid the case where the timer thread samples a stack at a predictable
143
  // period.
144
0
  std::normal_distribution<> distribution{
145
0
      kMeanMilliseconds, kStdDevMilliseconds};
146
0
  std::unique_lock<std::mutex> uniqueLock(profilerLock_);
147
148
0
  while (enabled_) {
149
0
    if (!sampleStacks()) {
150
0
      return;
151
0
    }
152
153
0
    const uint64_t millis = round(std::fabs(distribution(gen)));
154
    // TODO: make sampling rate configurable.
155
0
    enabledCondVar_.wait_for(
156
0
        uniqueLock, std::chrono::milliseconds(millis), [this]() {
157
0
          return !enabled_;
158
0
        });
159
0
  }
160
0
}
161
162
0
bool Sampler::enabled() {
163
0
  std::lock_guard<std::mutex> lockGuard(profilerLock_);
164
0
  return enabled_;
165
0
}
166
167
0
bool Sampler::enable() {
168
0
  std::lock_guard<std::mutex> lockGuard(profilerLock_);
169
0
  if (enabled_) {
170
0
    return true;
171
0
  }
172
0
  if (!platformEnable()) {
173
0
    return false;
174
0
  }
175
0
  enabled_ = true;
176
  // Start timer thread.
177
0
  timerThread_ = std::thread(&Sampler::timerLoop, this);
178
0
  return true;
179
0
}
180
181
0
bool Sampler::disable() {
182
0
  {
183
0
    std::lock_guard<std::mutex> lockGuard(profilerLock_);
184
0
    if (!enabled_) {
185
      // Already disabled.
186
0
      return true;
187
0
    }
188
0
    if (!platformDisable()) {
189
0
      return false;
190
0
    }
191
    // Telling timer thread to exit.
192
0
    enabled_ = false;
193
0
  }
194
  // Notify the timer thread that it has been disabled.
195
0
  enabledCondVar_.notify_all();
196
  // Wait for timer thread to exit. This avoids the timer thread reading from
197
  // memory that is freed after a main thread exits. This is outside the lock
198
  // on profilerLock_ since the timer thread needs to acquire that lock.
199
0
  timerThread_.join();
200
0
  return true;
201
0
}
202
203
} // namespace sampling_profiler
204
} // namespace vm
205
} // namespace hermes
206
207
#endif // HERMESVM_SAMPLING_PROFILER_AVAILABLE