Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce format-agnostic API for JS Sampling #1603

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions API/hermes/hermes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,27 @@ void HermesRuntime::sampledTraceToStreamInDevToolsFormat(std::ostream &stream) {
#endif // HERMESVM_SAMPLING_PROFILER_AVAILABLE
}

sampling_profiler::Profile HermesRuntime::dumpSampledTraceToProfile() {
#if HERMESVM_SAMPLING_PROFILER_AVAILABLE
vm::SamplingProfiler *sp = impl(this)->runtime_.samplingProfiler.get();
if (!sp) {
throw jsi::JSINativeException("Runtime not registered for profiling");
}
return sp->dumpAsProfile();
#else
throwHermesNotCompiledWithSamplingProfilerSupport();
#endif // HERMESVM_SAMPLING_PROFILER_AVAILABLE
}

std::vector<sampling_profiler::Profile>
HermesRuntime::dumpSampledTraceToProfilesGlobal() {
#if HERMESVM_SAMPLING_PROFILER_AVAILABLE
return ::hermes::vm::SamplingProfiler::dumpAsProfilesGlobal();
#else
throwHermesNotCompiledWithSamplingProfilerSupport();
#endif // HERMESVM_SAMPLING_PROFILER_AVAILABLE
}

/*static*/ std::unordered_map<std::string, std::vector<std::string>>
HermesRuntime::getExecutedFunctions() {
std::unordered_map<
Expand Down
10 changes: 10 additions & 0 deletions API/hermes/hermes.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <hermes/Public/HermesExport.h>
#include <hermes/Public/RuntimeConfig.h>
#include <hermes/Public/SamplingProfiler.h>
#include <jsi/jsi.h>
#include <unordered_map>

Expand Down Expand Up @@ -89,6 +90,15 @@ class HERMES_EXPORT HermesRuntime : public jsi::Runtime {
/// Profiler.stop return type.
void sampledTraceToStreamInDevToolsFormat(std::ostream &stream);

/// Dump sampled stack trace for a given runtime to a data structure that can
/// be used by third parties.
sampling_profiler::Profile dumpSampledTraceToProfile();

/// Dump sampled stack trace for all registered local sampling profiler
/// instances to a data structure that can be used by third parties.
static std::vector<sampling_profiler::Profile>
dumpSampledTraceToProfilesGlobal();

/// Return the executed JavaScript function info.
/// This information holds the segmentID, Virtualoffset and sourceURL.
/// This information is needed specifically to be able to symbolicate non-CJS
Expand Down
10 changes: 10 additions & 0 deletions include/hermes/VM/Profiler/SamplingProfiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#if HERMESVM_SAMPLING_PROFILER_AVAILABLE

#include "hermes/Public/SamplingProfiler.h"
#include "hermes/VM/Callable.h"
#include "hermes/VM/JSNativeFunctions.h"
#include "hermes/VM/Runtime.h"
Expand Down Expand Up @@ -267,6 +268,10 @@ class SamplingProfiler {
/// Dump sampled stack to \p OS in chrome trace format.
void dumpChromeTrace(llvh::raw_ostream &OS);

/// Dump sampled stack trace to data structure that can be used by third
/// parties.
facebook::hermes::sampling_profiler::Profile dumpAsProfile();

/// Dump the sampled stack to \p OS in the format expected by the
/// Profiler.stop return type. See
///
Expand All @@ -281,6 +286,11 @@ class SamplingProfiler {
/// Static wrapper for dumpChromeTrace.
static void dumpChromeTraceGlobal(llvh::raw_ostream &OS);

/// Static wrapper for dumpAsProfile. Will dump in separate Profile for each
/// local sampling profiler instance.
static std::vector<facebook::hermes::sampling_profiler::Profile>
dumpAsProfilesGlobal();

/// Enable and start profiling.
static bool enable(double meanHzFreq = 100);

Expand Down
1 change: 1 addition & 0 deletions lib/VM/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ set(source_files
Profiler/ChromeTraceSerializer.cpp
Profiler/CodeCoverageProfiler.cpp
Profiler/InlineCacheProfiler.cpp
Profiler/ProfileGenerator.cpp
Profiler/SamplingProfiler.cpp
Profiler/SamplingProfilerPosix.cpp
Profiler/SamplingProfilerWindows.cpp
Expand Down
166 changes: 166 additions & 0 deletions lib/VM/Profiler/ProfileGenerator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include "ProfileGenerator.h"

#if HERMESVM_SAMPLING_PROFILER_AVAILABLE

namespace fhsp = ::facebook::hermes::sampling_profiler;

namespace hermes {
namespace vm {

namespace {

/// \return timestamp as time since epoch in microseconds.
static uint64_t convertTimestampToMicroseconds(
SamplingProfiler::TimeStampType timeStamp) {
return std::chrono::duration_cast<std::chrono::microseconds>(
timeStamp.time_since_epoch())
.count();
}

static std::string getJSFunctionName(
hbc::BCProvider *bcProvider,
uint32_t funcId) {
hbc::RuntimeFunctionHeader functionHeader =
bcProvider->getFunctionHeader(funcId);
return bcProvider->getStringRefFromID(functionHeader.functionName()).str();
}

static OptValue<hbc::DebugSourceLocation> getSourceLocation(
hbc::BCProvider *bcProvider,
uint32_t funcId,
uint32_t opcodeOffset) {
const hbc::DebugOffsets *debugOffsets = bcProvider->getDebugOffsets(funcId);
if (debugOffsets &&
debugOffsets->sourceLocations != hbc::DebugOffsets::NO_OFFSET) {
return bcProvider->getDebugInfo()->getLocationForAddress(
debugOffsets->sourceLocations, opcodeOffset);
}
return llvh::None;
}

static fhsp::ProfileSampleCallStackSuspendFrame::SuspendFrameKind
formatSuspendFrameKind(SamplingProfiler::SuspendFrameInfo::Kind kind) {
switch (kind) {
case SamplingProfiler::SuspendFrameInfo::Kind::GC:
return fhsp::ProfileSampleCallStackSuspendFrame::SuspendFrameKind::GC;
case SamplingProfiler::SuspendFrameInfo::Kind::Debugger:
return fhsp::ProfileSampleCallStackSuspendFrame::SuspendFrameKind::
Debugger;
case SamplingProfiler::SuspendFrameInfo::Kind::Multiple:
return fhsp::ProfileSampleCallStackSuspendFrame::SuspendFrameKind::
Multiple;

default:
llvm_unreachable("Unexpected Suspend Frame kind");
}
}

/// Format VM-level frame to public interface.
static fhsp::ProfileSampleCallStackFrame *formatCallStackFrame(
const SamplingProfiler::StackFrame &frame,
const SamplingProfiler &samplingProfiler) {
switch (frame.kind) {
case SamplingProfiler::StackFrame::FrameKind::SuspendFrame: {
return new fhsp::ProfileSampleCallStackSuspendFrame{
formatSuspendFrameKind(frame.suspendFrame.kind),
};
}

case SamplingProfiler::StackFrame::FrameKind::NativeFunction:
return new fhsp::ProfileSampleCallStackNativeFunctionFrame{
samplingProfiler.getNativeFunctionName(frame),
};

case SamplingProfiler::StackFrame::FrameKind::FinalizableNativeFunction:
return new fhsp::ProfileSampleCallStackHostFunctionFrame{
samplingProfiler.getNativeFunctionName(frame),
};

case SamplingProfiler::StackFrame::FrameKind::JSFunction: {
RuntimeModule *module = frame.jsFrame.module;
hbc::BCProvider *bcProvider = module->getBytecode();

std::string functionName =
getJSFunctionName(bcProvider, frame.jsFrame.functionId);
std::optional<uint32_t> scriptId = std::nullopt;
std::optional<std::string> url = std::nullopt;
std::optional<uint32_t> lineNumber = std::nullopt;
std::optional<uint32_t> columnNumber = std::nullopt;

OptValue<hbc::DebugSourceLocation> sourceLocOpt = getSourceLocation(
bcProvider, frame.jsFrame.functionId, frame.jsFrame.offset);
if (sourceLocOpt.hasValue()) {
// Bundle has debug info.
scriptId = sourceLocOpt.getValue().filenameId;
url = bcProvider->getDebugInfo()->getFilenameByID(scriptId.value());

// hbc::DebugSourceLocation is 1-based, but initializes line and column
// fields with 0 by default.
uint32_t line = sourceLocOpt.getValue().line;
uint32_t column = sourceLocOpt.getValue().column;
if (line != 0) {
lineNumber = line;
}
if (column != 0) {
columnNumber = column;
}
}

return new fhsp::ProfileSampleCallStackJSFunctionFrame{
functionName,
scriptId,
url,
lineNumber,
columnNumber,
};
}

default:
llvm_unreachable("Unexpected Frame kind");
}
}

} // namespace

/* static */ fhsp::Profile ProfileGenerator::generate(
const SamplingProfiler &sp,
uint32_t pid,
const SamplingProfiler::ThreadNamesMap &threadNames,
const std::vector<SamplingProfiler::StackTrace> &sampledStacks) {
fhsp::Process process{pid};

assert(!threadNames.empty() && "Expected at least one Thread recorded");
// It is unclear why Hermes keeps map of threads for a local profiler.
// See D67453370 for more context, this map is expected to contain a single
// entry.
auto threadEntry = threadNames.begin();
fhsp::Thread thread{threadEntry->first, threadEntry->second};

std::vector<fhsp::ProfileSample> samples;
samples.reserve(sampledStacks.size());
for (const SamplingProfiler::StackTrace &sampledStack : sampledStacks) {
uint64_t timestamp = convertTimestampToMicroseconds(sampledStack.timeStamp);

std::vector<fhsp::ProfileSampleCallStackFrame *> callFrames;
callFrames.reserve(sampledStack.stack.size());
for (const SamplingProfiler::StackFrame &frame : sampledStack.stack) {
callFrames.emplace_back(formatCallStackFrame(frame, sp));
}

samples.emplace_back(timestamp, callFrames);
}

return fhsp::Profile{process, thread, samples};
}

} // namespace vm
} // namespace hermes

#endif // HERMESVM_SAMPLING_PROFILER_AVAILABLE
40 changes: 40 additions & 0 deletions lib/VM/Profiler/ProfileGenerator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#ifndef HERMES_VM_PROFILER_PROFILEGENERATOR_H
#define HERMES_VM_PROFILER_PROFILEGENERATOR_H

#include "hermes/VM/Profiler/SamplingProfilerDefs.h"

#if HERMESVM_SAMPLING_PROFILER_AVAILABLE

#include "hermes/VM/Profiler/SamplingProfiler.h"

namespace hermes {
namespace vm {

/// Generate format-agnostic data structure, which should contain relevant
/// information about the recorded Sampling Profile and may be used by third
/// parties.
class ProfileGenerator {
ProfileGenerator() = delete;

public:
/// Emit Profile in a single struct.
static facebook::hermes::sampling_profiler::Profile generate(
const SamplingProfiler &sp,
uint32_t pid,
const SamplingProfiler::ThreadNamesMap &threadNames,
const std::vector<SamplingProfiler::StackTrace> &sampledStacks);
};

} // namespace vm
} // namespace hermes

#endif // HERMESVM_SAMPLING_PROFILER_AVAILABLE

#endif // HERMES_VM_PROFILER_PROFILEGENERATOR_H
26 changes: 26 additions & 0 deletions lib/VM/Profiler/SamplingProfiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "llvh/Support/Compiler.h"

#include "ChromeTraceSerializer.h"
#include "ProfileGenerator.h"
#include "SamplingProfilerSampler.h"

#include <fcntl.h>
Expand Down Expand Up @@ -207,6 +208,31 @@ void SamplingProfiler::serializeInDevToolsFormat(llvh::raw_ostream &OS) {
clear();
}

std::vector<facebook::hermes::sampling_profiler::Profile>
SamplingProfiler::dumpAsProfilesGlobal() {
auto globalProfiler = sampling_profiler::Sampler::get();
std::lock_guard<std::mutex> lk(globalProfiler->profilerLock_);

std::vector<facebook::hermes::sampling_profiler::Profile> profiles;
for (auto *currentProfilerInstance : globalProfiler->profilers_) {
auto profileForCurrentInstance = currentProfilerInstance->dumpAsProfile();
profiles.push_back(std::move(profileForCurrentInstance));
}

return profiles;
}

facebook::hermes::sampling_profiler::Profile SamplingProfiler::dumpAsProfile() {
std::lock_guard<std::mutex> lk(runtimeDataLock_);
auto pid = oscompat::process_id();

facebook::hermes::sampling_profiler::Profile profile =
ProfileGenerator::generate(*this, pid, threadNames_, sampledStacks_);

clear();
return profile;
}

bool SamplingProfiler::enable(double meanHzFreq) {
return sampling_profiler::Sampler::get()->enable(meanHzFreq);
}
Expand Down
Loading
Loading