Expose API for registering profiles and profile chunks (#49084)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/49084

# Changelog: [Internal]

> NOTE: Some CI jobs are expected to fail, because changes in Hermes D67353585 should be landed first, and then grafted to Static Hermes.

Added public methods to `PerformanceTracer` instance for registering `Profile` and `ProfileChunk` Trace Events.

Also created data structs in `TraceEvent.h` to simplify serialization process for objects like call frames / samples / etc.

Reviewed By: huntie

Differential Revision: D68558805

fbshipit-source-id: f5eca0435c56828909f99ec0b47841d24ee907b6
This commit is contained in:
Ruslan Lesiutin
2025-02-27 08:32:12 -08:00
committed by Facebook GitHub Bot
parent 1aced32a1c
commit 290f237cfa
3 changed files with 225 additions and 0 deletions
@@ -68,6 +68,7 @@ bool PerformanceTracer::stopTracing() {
}
performanceMeasureCount_ = 0;
profileCount_ = 0;
tracing_ = false;
return true;
}
@@ -213,6 +214,56 @@ void PerformanceTracer::reportThread(uint64_t id, const std::string& name) {
});
}
uint16_t PerformanceTracer::reportRuntimeProfile(
uint64_t threadId,
uint64_t eventUnixTimestamp) {
std::lock_guard lock(mutex_);
if (!tracing_) {
throw std::runtime_error(
"Runtime Profile should only be reported when Tracing is enabled");
}
++profileCount_;
// CDT prioritizes event timestamp over startTime metadata field.
// https://fburl.com/lo764pf4
buffer_.push_back(TraceEvent{
.id = profileCount_,
.name = "Profile",
.cat = "disabled-by-default-v8.cpu_profiler",
.ph = 'P',
.ts = eventUnixTimestamp,
.pid = processId_,
.tid = threadId,
.args = folly::dynamic::object(
"data", folly ::dynamic::object("startTime", eventUnixTimestamp)),
});
return profileCount_;
}
void PerformanceTracer::reportRuntimeProfileChunk(
uint16_t profileId,
uint64_t threadId,
uint64_t eventUnixTimestamp,
const tracing::TraceEventProfileChunk& traceEventProfileChunk) {
std::lock_guard lock(mutex_);
if (!tracing_) {
return;
}
buffer_.push_back(TraceEvent{
.id = profileId,
.name = "ProfileChunk",
.cat = "disabled-by-default-v8.cpu_profiler",
.ph = 'P',
.ts = eventUnixTimestamp,
.pid = processId_,
.tid = threadId,
.args =
folly::dynamic::object("data", traceEventProfileChunk.asDynamic()),
});
}
void PerformanceTracer::reportEventLoopTask(uint64_t start, uint64_t end) {
if (!tracing_) {
return;
@@ -9,6 +9,7 @@
#include "CdpTracing.h"
#include "TraceEvent.h"
#include "TraceEventProfile.h"
#include <folly/dynamic.h>
@@ -94,6 +95,21 @@ class PerformanceTracer {
*/
void reportJavaScriptThread();
/**
* Record a corresponding Profile Trace Event.
* \return the id of the profile, should be used to linking profile chunks.
*/
uint16_t reportRuntimeProfile(uint64_t threadId, uint64_t eventUnixTimestamp);
/**
* Record a corresponding ProfileChunk Trace Event.
*/
void reportRuntimeProfileChunk(
uint16_t profileId,
uint64_t threadId,
uint64_t eventUnixTimestamp,
const tracing::TraceEventProfileChunk& traceEventProfileChunk);
/**
* Record an Event Loop tick, which will be represented as an Event Loop task
* on a timeline view and grouped with JavaScript samples.
@@ -111,6 +127,7 @@ class PerformanceTracer {
bool tracing_{false};
uint64_t processId_;
uint32_t performanceMeasureCount_{0};
uint16_t profileCount_{0};
std::vector<TraceEvent> buffer_;
std::mutex mutex_;
};
@@ -0,0 +1,157 @@
/*
* 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.
*/
#pragma once
#include <folly/dynamic.h>
#include <utility>
namespace facebook::react::jsinspector_modern::tracing {
/// Arbitrary data structure, which represents payload of the "ProfileChunk"
/// Trace Event.
struct TraceEventProfileChunk {
/// Deltas between timestamps of chronolocigally sorted samples.
/// Will be sent as part of the "ProfileChunk" trace event.
struct TimeDeltas {
public:
explicit TimeDeltas(std::vector<long long> deltas)
: deltas_(std::move(deltas)) {}
folly::dynamic asDynamic() const {
folly::dynamic value = folly::dynamic::array();
for (auto delta : deltas_) {
value.push_back(delta);
}
return value;
}
private:
std::vector<long long> deltas_;
};
/// Contains Profile information that will be emitted in this chunk: nodes and
/// sample root node ids.
struct CPUProfile {
/// Unique node in the profile tree, has unique id, call frame and
/// optionally
/// id of its parent node. Only root node has no parent.
struct Node {
/// Unique call frame in the call stack.
struct CallFrame {
public:
CallFrame(
std::string codeType,
uint32_t scriptId,
std::string functionName,
std::optional<std::string> url = std::nullopt,
std::optional<uint32_t> lineNumber = std::nullopt,
std::optional<uint32_t> columnNumber = std::nullopt)
: codeType_(std::move(codeType)),
scriptId_(scriptId),
functionName_(std::move(functionName)),
url_(std::move(url)),
lineNumber_(lineNumber),
columnNumber_(columnNumber) {}
folly::dynamic asDynamic() const {
folly::dynamic dynamicCallFrame = folly::dynamic::object();
dynamicCallFrame["codeType"] = codeType_;
dynamicCallFrame["scriptId"] = scriptId_;
dynamicCallFrame["functionName"] = functionName_;
if (url_.has_value()) {
dynamicCallFrame["url"] = url_.value();
}
if (lineNumber_.has_value()) {
dynamicCallFrame["lineNumber"] = lineNumber_.value();
}
if (columnNumber_.has_value()) {
dynamicCallFrame["columnNumber"] = columnNumber_.value();
}
return dynamicCallFrame;
}
private:
std::string codeType_;
uint32_t scriptId_;
std::string functionName_;
std::optional<std::string> url_;
std::optional<uint32_t> lineNumber_;
std::optional<uint32_t> columnNumber_;
};
public:
Node(
uint32_t id,
CallFrame callFrame,
std::optional<uint32_t> parentId = std::nullopt)
: id_(id), callFrame_(std::move(callFrame)), parentId_(parentId) {}
folly::dynamic asDynamic() const {
folly::dynamic dynamicNode = folly::dynamic::object();
dynamicNode["callFrame"] = callFrame_.asDynamic();
dynamicNode["id"] = id_;
if (parentId_.has_value()) {
dynamicNode["parent"] = parentId_.value();
}
return dynamicNode;
}
private:
uint32_t id_;
CallFrame callFrame_;
std::optional<uint32_t> parentId_;
};
public:
CPUProfile(std::vector<Node> nodes, std::vector<uint32_t> samples)
: nodes_(std::move(nodes)), samples_(std::move(samples)) {}
folly::dynamic asDynamic() const {
folly::dynamic dynamicNodes = folly::dynamic::array();
for (const auto& node : nodes_) {
dynamicNodes.push_back(node.asDynamic());
}
folly::dynamic dynamicSamples = folly::dynamic::array();
for (auto sample : samples_) {
dynamicSamples.push_back(sample);
}
return folly::dynamic::object("nodes", dynamicNodes)(
"samples", dynamicSamples);
}
private:
std::vector<Node> nodes_;
std::vector<uint32_t> samples_;
};
public:
TraceEventProfileChunk(CPUProfile cpuProfile, TimeDeltas timeDeltas)
: cpuProfile_(std::move(cpuProfile)),
timeDeltas_(std::move(timeDeltas)) {}
folly::dynamic asDynamic() const {
folly::dynamic value = folly::dynamic::object;
value["cpuProfile"] = cpuProfile_.asDynamic();
value["timeDeltas"] = timeDeltas_.asDynamic();
return value;
}
private:
CPUProfile cpuProfile_;
TimeDeltas timeDeltas_;
};
} // namespace facebook::react::jsinspector_modern::tracing