Skip to content

Commit f1d73e5

Browse files
Matt Blagdenfacebook-github-bot
Matt Blagden
authored andcommitted
Identify profiler suspend reasons (#1608)
Summary: Pull Request resolved: #1608 The sampling profiler tracks the type of each frame. A "suspend" frame denotes that execution is suspended, with an arbitrary string providing details. We would like to identify GC frames for the sake of Chrome DevTools visualizations/stats about GC time. This diff adds an enum to suspend frames to allow GC frames to be identified without depending on specific contents of the details string. Another option would be to replace the "suspend" frame with multiple different frame types for debugger, GC, etc. Reviewed By: fbmal7 Differential Revision: D68442991 fbshipit-source-id: d16a3a1b87657e538e8c5471ea9533876f1c757d
1 parent e877897 commit f1d73e5

File tree

5 files changed

+77
-30
lines changed

5 files changed

+77
-30
lines changed

include/hermes/VM/Profiler/SamplingProfiler.h

+34-12
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,29 @@ class SamplingProfiler {
5656
/// been registered with the sampling profiler yet, and therefore can be moved
5757
/// by the GC.
5858
using NativeFunctionFrameInfo = size_t;
59-
/// GC frame info. Pointing to string in suspendEventExtraInfoSet_.
60-
using SuspendFrameInfo = const std::string *;
59+
60+
/// Details about a suspend frame.
61+
struct SuspendFrameInfo {
62+
/// GC frame info. Pointing to string in gcEventExtraInfoSet_.
63+
using GCFrameInfo = const std::string *;
64+
65+
/// Cause of the suspension.
66+
enum class Kind {
67+
/// Suspended to perform GC actions. The gcFrame field contains additional
68+
/// details specific to the GC.
69+
GC,
70+
/// Suspended to perform debugger actions.
71+
Debugger,
72+
/// Multiple suspensions have occurred.
73+
Multiple,
74+
} kind;
75+
/// Extra information about GC suspensions. Only valid when \c kind is
76+
/// \c GC. Pointing to string in gcEventExtraInfoSet_; it is optional
77+
/// and can be null to indicate no extra info.
78+
/// We can't directly use std::string here because it is
79+
/// inside a union.
80+
GCFrameInfo gcFrame;
81+
};
6182

6283
// This will break with more than one RuntimeModule(like FB4a, eval() call or
6384
// lazy compilation etc...). It is simply a temporary thing to get started.
@@ -80,11 +101,7 @@ class SamplingProfiler {
80101
LoomNativeFrameInfo nativeFunctionPtrForLoom;
81102
/// Native function frame info storage used for "regular" profiling.
82103
NativeFunctionFrameInfo nativeFrame;
83-
/// Suspend frame info. Pointing to string
84-
/// in suspendExtraInfoSet_; it is optional and
85-
/// can be null to indicate no extra info.
86-
/// We can't directly use std::string here because it is
87-
/// inside a union.
104+
/// Suspend frame info.
88105
SuspendFrameInfo suspendFrame;
89106
};
90107
FrameKind kind;
@@ -175,7 +192,7 @@ class SamplingProfiler {
175192
ThreadNamesMap threadNames_;
176193

177194
/// Unique GC event extra info strings container.
178-
std::unordered_set<std::string> suspendEventExtraInfoSet_;
195+
std::unordered_set<std::string> gcEventExtraInfoSet_;
179196

180197
/// Domains to be kept alive for sampled RuntimeModules. Protected by
181198
/// runtimeDataLock_.
@@ -223,7 +240,9 @@ class SamplingProfiler {
223240
private:
224241
/// Record JS stack at time of suspension, caller must hold
225242
/// runtimeDataLock_.
226-
void recordPreSuspendStack(std::string_view extraInfo);
243+
void recordPreSuspendStack(
244+
SuspendFrameInfo::Kind reason,
245+
llvh::StringRef gcFrame);
227246

228247
protected:
229248
/// Clear previous stored samples.
@@ -269,8 +288,11 @@ class SamplingProfiler {
269288
static bool disable();
270289

271290
/// Suspends the sample profiling. Every call to suspend must be matched by a
272-
/// call to resume.
273-
void suspend(std::string_view reason);
291+
/// call to resume. \c reason indicates the cause of suspension. If \c reason
292+
/// is GC, \c gcFrame can provide extra details, otherwise it is ignored.
293+
void suspend(
294+
SuspendFrameInfo::Kind reason,
295+
llvh::StringRef gcSuspendDetails = "");
274296

275297
/// Resumes the sample profiling. There must have been a previous call to
276298
/// suspend() that hansn't been resume()d yet.
@@ -290,7 +312,7 @@ class SuspendSamplingProfilerRAII {
290312
public:
291313
explicit SuspendSamplingProfilerRAII(
292314
Runtime &runtime,
293-
std::string_view reason = "")
315+
SamplingProfiler::SuspendFrameInfo::Kind reason)
294316
: profiler_(runtime.samplingProfiler.get()) {
295317
if (profiler_) {
296318
profiler_->suspend(reason);

lib/VM/Debugger/Debugger.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,8 @@ ExecutionStatus Debugger::debuggerLoop(
422422
// Keep the evalResult alive, even if all other handles are flushed.
423423
static constexpr unsigned KEEP_HANDLES = 1;
424424
#if HERMESVM_SAMPLING_PROFILER_AVAILABLE
425-
SuspendSamplingProfilerRAII ssp{runtime_, "debugger"};
425+
SuspendSamplingProfilerRAII ssp{
426+
runtime_, SamplingProfiler::SuspendFrameInfo::Kind::Debugger};
426427
#endif // HERMESVM_SAMPLING_PROFILER_AVAILABLE
427428
while (true) {
428429
GCScopeMarkerRAII marker{runtime_};

lib/VM/Profiler/ChromeTraceSerializer.cpp

+17-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,21 @@
1616
namespace hermes {
1717
namespace vm {
1818

19+
static std::string getSuspendFrameName(
20+
const SamplingProfiler::SuspendFrameInfo &info) {
21+
const char *suspendFrameName;
22+
if (info.kind == SamplingProfiler::SuspendFrameInfo::Kind::GC) {
23+
suspendFrameName = info.gcFrame->c_str();
24+
} else if (info.kind == SamplingProfiler::SuspendFrameInfo::Kind::Debugger) {
25+
suspendFrameName = "debugger";
26+
} else if (info.kind == SamplingProfiler::SuspendFrameInfo::Kind::Multiple) {
27+
suspendFrameName = "multiple";
28+
} else {
29+
assert(false && "suspendFrame name should never be null");
30+
}
31+
return std::string("[") + suspendFrameName + "]";
32+
}
33+
1934
std::shared_ptr<ChromeStackFrameNode> ChromeStackFrameNode::findOrAddNewChild(
2035
ChromeFrameIdGenerator &frameIdGen,
2136
const SamplingProfiler::StackFrame &target) {
@@ -249,8 +264,7 @@ void ChromeTraceSerializer::serializeStackFrames(JSONEmitter &json) const {
249264
}
250265

251266
case SamplingProfiler::StackFrame::FrameKind::SuspendFrame: {
252-
assert(frame.suspendFrame && "suspendFrame name should never be null");
253-
frameName = "[" + *frame.suspendFrame + "]";
267+
frameName = getSuspendFrameName(frame.suspendFrame);
254268
categoryName = "Metadata";
255269
break;
256270
}
@@ -446,8 +460,7 @@ void ProfilerProfileSerializer::processNode(
446460
}
447461

448462
case SamplingProfiler::StackFrame::FrameKind::SuspendFrame: {
449-
assert(frame.suspendFrame && "suspendFrame should never be nullptr");
450-
name = "[" + *frame.suspendFrame + "]";
463+
name = getSuspendFrameName(frame.suspendFrame);
451464
url = "[suspended]";
452465
break;
453466
}

lib/VM/Profiler/SamplingProfiler.cpp

+22-12
Original file line numberDiff line numberDiff line change
@@ -224,22 +224,26 @@ void SamplingProfiler::clear() {
224224
threadNames_.clear();
225225
}
226226

227-
void SamplingProfiler::suspend(std::string_view extraInfo) {
227+
void SamplingProfiler::suspend(
228+
SuspendFrameInfo::Kind reason,
229+
llvh::StringRef gcSuspendDetails) {
228230
// Need to check whether the profiler is enabled without holding the
229231
// runtimeDataLock_. Otherwise, we'd have a lock inversion.
230232
bool enabled = sampling_profiler::Sampler::get()->enabled();
231233

232234
std::lock_guard<std::mutex> lk(runtimeDataLock_);
233-
if (++suspendCount_ > 1 || extraInfo.empty()) {
234-
// If there are multiple nested suspend calls use a default "suspended"
235-
// label for the suspend entry in the call stack. Also use the default
236-
// when no extra info is provided.
237-
extraInfo = "suspended";
235+
if (++suspendCount_ > 1) {
236+
// If there are multiple nested suspend calls use a default "multiple" frame
237+
// kind for the suspend entry in the call stack.
238+
reason = SuspendFrameInfo::Kind::Multiple;
239+
} else if (reason == SuspendFrameInfo::Kind::GC && gcSuspendDetails.empty()) {
240+
// If no GC reason was provided, use a default "suspend" value.
241+
gcSuspendDetails = "suspend";
238242
}
239243

240244
// Only record the stack trace for the first suspend() call.
241245
if (LLVM_UNLIKELY(enabled && suspendCount_ == 1)) {
242-
recordPreSuspendStack(extraInfo);
246+
recordPreSuspendStack(reason, gcSuspendDetails);
243247
}
244248
}
245249

@@ -251,10 +255,15 @@ void SamplingProfiler::resume() {
251255
}
252256
}
253257

254-
void SamplingProfiler::recordPreSuspendStack(std::string_view extraInfo) {
255-
std::pair<std::unordered_set<std::string>::iterator, bool> retPair =
256-
suspendEventExtraInfoSet_.emplace(extraInfo);
257-
SuspendFrameInfo suspendExtraInfo = &(*(retPair.first));
258+
void SamplingProfiler::recordPreSuspendStack(
259+
SuspendFrameInfo::Kind reason,
260+
llvh::StringRef gcFrame) {
261+
SuspendFrameInfo suspendExtraInfo{reason, nullptr};
262+
if (reason == SuspendFrameInfo::Kind::GC) {
263+
std::pair<std::unordered_set<std::string>::iterator, bool> retPair =
264+
gcEventExtraInfoSet_.emplace(gcFrame);
265+
suspendExtraInfo.gcFrame = &(*(retPair.first));
266+
}
258267

259268
auto &leafFrame = preSuspendStackStorage_.stack[0];
260269
leafFrame.kind = StackFrame::FrameKind::SuspendFrame;
@@ -281,7 +290,8 @@ bool operator==(
281290
return left.nativeFrame == right.nativeFrame;
282291

283292
case SamplingProfiler::StackFrame::FrameKind::SuspendFrame:
284-
return left.suspendFrame == right.suspendFrame;
293+
return left.suspendFrame.kind == right.suspendFrame.kind &&
294+
left.suspendFrame.gcFrame == right.suspendFrame.gcFrame;
285295

286296
default:
287297
llvm_unreachable("Unknown frame kind");

lib/VM/Runtime.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -2104,7 +2104,8 @@ void Runtime::onGCEvent(GCEventKind kind, const std::string &extraInfo) {
21042104
if (samplingProfiler) {
21052105
switch (kind) {
21062106
case GCEventKind::CollectionStart:
2107-
samplingProfiler->suspend(extraInfo);
2107+
samplingProfiler->suspend(
2108+
SamplingProfiler::SuspendFrameInfo::Kind::GC, extraInfo);
21082109
break;
21092110
case GCEventKind::CollectionEnd:
21102111
samplingProfiler->resume();

0 commit comments

Comments
 (0)