From 290f237cfaa879cc03689aaad8680ab5a0d335a9 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Thu, 27 Feb 2025 08:32:12 -0800 Subject: [PATCH] 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 --- .../tracing/PerformanceTracer.cpp | 51 ++++++ .../tracing/PerformanceTracer.h | 17 ++ .../tracing/TraceEventProfile.h | 157 ++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventProfile.h diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp index be3e7220f9b..5691ee3ca90 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp @@ -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; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h index 18574b7f267..0c5f29156f8 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -9,6 +9,7 @@ #include "CdpTracing.h" #include "TraceEvent.h" +#include "TraceEventProfile.h" #include @@ -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 buffer_; std::mutex mutex_; }; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventProfile.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventProfile.h new file mode 100644 index 00000000000..76d3e4115a5 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventProfile.h @@ -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 + +#include + +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 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 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 url = std::nullopt, + std::optional lineNumber = std::nullopt, + std::optional 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 url_; + std::optional lineNumber_; + std::optional columnNumber_; + }; + + public: + Node( + uint32_t id, + CallFrame callFrame, + std::optional 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 parentId_; + }; + + public: + CPUProfile(std::vector nodes, std::vector 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 nodes_; + std::vector 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