Skip to content

Commit

Permalink
Record thread id for each Sample
Browse files Browse the repository at this point in the history
Summary:
While I was triaging why Hermes returns different thread id from the one where React Native runs JavaScript, I found this:
1. `threadNames_` map will be populated at the time when Runtime is created. On React Native side, we create Runtime on a Main thread, so Hermes will capture only it in this map: it will never capture JavaScript thread: {F1975078780}
2. The actual thread id of where Runtime was running is stored here:
https://www.internalfb.com/code/fbsource/[6bd4646ee6d2]/xplat/hermes/lib/VM/Profiler/SamplingProfiler.cpp?lines=125
Later, every `StackTrace` object will have `tid` field with correct thread id.

In D58787655, bgirard added a logic that registers RN's JavaScript thread with Hermes' sampling profielr.

Given this, I propose simplifying an API and use just a `threadId` field on every recorded sample.

Reviewed By: bgirard

Differential Revision: D69525657

fbshipit-source-id: 699e8edc1b60211a3438cd6ac3071c7cdd61579a
  • Loading branch information
hoxyq authored and facebook-github-bot committed Feb 13, 2025
1 parent bde8cb6 commit 06bf7a8
Show file tree
Hide file tree
Showing 4 changed files with 16 additions and 73 deletions.
15 changes: 2 additions & 13 deletions lib/VM/Profiler/ProfileGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,18 +131,7 @@ static fhsp::ProfileSampleCallStackFrame *formatCallStackFrame(

/* 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) {
Expand All @@ -154,10 +143,10 @@ static fhsp::ProfileSampleCallStackFrame *formatCallStackFrame(
callFrames.emplace_back(formatCallStackFrame(frame, sp));
}

samples.emplace_back(timestamp, callFrames);
samples.emplace_back(timestamp, sampledStack.tid, callFrames);
}

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

} // namespace vm
Expand Down
2 changes: 0 additions & 2 deletions lib/VM/Profiler/ProfileGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ class ProfileGenerator {
/// 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);
};

Expand Down
3 changes: 1 addition & 2 deletions lib/VM/Profiler/SamplingProfiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,9 @@ SamplingProfiler::dumpAsProfilesGlobal() {

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_);
ProfileGenerator::generate(*this, sampledStacks_);

clear();
return profile;
Expand Down
69 changes: 13 additions & 56 deletions public/hermes/Public/SamplingProfiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,43 +18,6 @@ namespace facebook {
namespace hermes {
namespace sampling_profiler {

/// Provides details about the Process where sampling occurred.
struct HERMES_EXPORT Process {
public:
explicit Process(uint64_t id) : id_(id) {}

/// \return id of the OS process, where sampling occurred.
uint64_t getId() const {
return id_;
}

private:
/// OS-level id of the process.
uint64_t id_;
};

/// Provides details about the Thread where sampling occurred.
struct HERMES_EXPORT Thread {
public:
Thread(uint64_t id, const std::string &name) : id_(id), name_(name) {}

/// \return id of the OS thread, where sampling occurred.
uint64_t getId() const {
return id_;
}

/// \return name of the OS thread, where sampling occurred.
const std::string &getName() const {
return name_;
}

private:
/// OS-level id of the thread.
uint64_t id_;
/// OS-level name of the thread.
std::string name_;
};

/// Represents a single frame inside the captured sample stack.
/// Base struct for different kinds of frames.
struct HERMES_EXPORT ProfileSampleCallStackFrame {
Expand Down Expand Up @@ -215,15 +178,23 @@ struct HERMES_EXPORT ProfileSample {
public:
ProfileSample(
uint64_t timestamp,
uint64_t threadId,
std::vector<ProfileSampleCallStackFrame *> callStack)
: timestamp_(timestamp), callStack_(std::move(callStack)) {}
: timestamp_(timestamp),
threadId_(threadId),
callStack_(std::move(callStack)) {}

/// \return serialized unix timestamp in microseconds granularity. The
/// moment when this sample was recorded.
uint64_t getTimestamp() const {
return timestamp_;
}

/// \return thread id where sample was recorded.
uint64_t getThreadId() const {
return threadId_;
}

/// \return a snapshot of the call stack. The first element of the vector is
/// the lowest frame in the stack.
const std::vector<ProfileSampleCallStackFrame *> &getCallStack() const {
Expand All @@ -233,6 +204,8 @@ struct HERMES_EXPORT ProfileSample {
private:
/// When the call stack snapshot was taken (μs).
uint64_t timestamp_;
/// Thread id where sample was recorded.
uint64_t threadId_;
/// Snapshot of the call stack. The first element of the vector is
/// the lowest frame in the stack.
std::vector<ProfileSampleCallStackFrame *> callStack_;
Expand All @@ -241,31 +214,15 @@ struct HERMES_EXPORT ProfileSample {
/// Contains relevant information about the sampled trace from start to finish.
struct HERMES_EXPORT Profile {
public:
Profile(Process process, Thread thread, std::vector<ProfileSample> samples)
: process_(process),
thread_(std::move(thread)),
samples_(std::move(samples)) {}

/// \return details about the Process where sampling occurred.
const Process &getProcess() const {
return process_;
}

/// \return details about the Thread where sampling occurred.
const Thread &getThread() const {
return thread_;
}
explicit Profile(std::vector<ProfileSample> samples)
: samples_(std::move(samples)) {}

/// \return list of recorded samples, should be chronologically sorted.
const std::vector<ProfileSample> &getSamples() const {
return samples_;
}

private:
/// Represents OS process where sampling occurred.
Process process_;
/// Represents OS thread where sampling occurred.
Thread thread_;
/// List of recorded samples, should be chronologically sorted.
std::vector<ProfileSample> samples_;
};
Expand Down

0 comments on commit 06bf7a8

Please sign in to comment.