forked from facebook/hermes
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce format-agnostic API for JS Sampling (facebook#1603)
Summary: This diff adds a new API on `HermesRuntime`, which will emit a `struct` with all necessary information about completed JavaScript Sampling Profile. This `struct` can later be used by other third parties, such as React Native. The reason for creating a data source, and not just providing a stream to which Hermes will emit serialized information is that on React Native side we own the format in which data should be serialized, together with the data from other potential sources, such as User Timings, Interactions, Network, etc. HermesRuntime will have 2 new API endpoints: - `dumpAsProfile`. A method on `HermesRuntime` instance, which returns `Profile` that contains all relevant information about the recorded sampled stack trace. - `dumpAsProfilesGlobal`. A static method, which returns vector of all recorded Profiles for all registered sampling profiler instances. The actual conversion to Trace Event Format will hapen on React Native side, Hermes will only emit data structure that is agnostic to format and operates only with JavaScript runtime-level entities and general stuff: call stack frame information, timestamps, information about OS process and thread where sampling occured. Reviewed By: dannysu Differential Revision: D67353585
- Loading branch information
1 parent
e0ef9ca
commit 94d5eef
Showing
8 changed files
with
551 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.