diff --git a/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTask.cpp b/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTask.cpp new file mode 100644 index 00000000000000..371d628f968997 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTask.cpp @@ -0,0 +1,27 @@ +/* + * 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 "ConsoleTask.h" +#include "ConsoleTaskOrchestrator.h" + +namespace facebook::react::jsinspector_modern { + +ConsoleTask::ConsoleTask(std::shared_ptr taskContext) + : taskContext_(std::move(taskContext)), + orchestrator_(ConsoleTaskOrchestrator::getInstance()) { + if (taskContext_) { + orchestrator_.startTask(taskContext_->id()); + } +} + +ConsoleTask::~ConsoleTask() { + if (taskContext_) { + orchestrator_.finishTask(taskContext_->id()); + } +} + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTask.h b/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTask.h new file mode 100644 index 00000000000000..599396ca614f72 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTask.h @@ -0,0 +1,38 @@ +/* + * 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 + +namespace facebook::react::jsinspector_modern { + +class ConsoleTaskContext; +class RuntimeTargetDelegate; +class ConsoleTaskOrchestrator; + +class ConsoleTask { + public: + /** + * \param runtimeTargetDelegate The delegate to the corresponding runtime. + * \param taskContext The context that tracks the task. + */ + explicit ConsoleTask(std::shared_ptr taskContext); + ~ConsoleTask(); + + ConsoleTask(const ConsoleTask &) = default; + ConsoleTask &operator=(const ConsoleTask &) = delete; + + ConsoleTask(ConsoleTask &&) = default; + ConsoleTask &operator=(ConsoleTask &&) = delete; + + private: + std::shared_ptr taskContext_; + ConsoleTaskOrchestrator &orchestrator_; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTaskContext.cpp b/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTaskContext.cpp new file mode 100644 index 00000000000000..53ba29297836ba --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTaskContext.cpp @@ -0,0 +1,57 @@ +/* + * 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 "ConsoleTaskContext.h" +#include "ConsoleTaskOrchestrator.h" +#include "RuntimeTarget.h" + +namespace facebook::react::jsinspector_modern { + +ConsoleTaskContext::ConsoleTaskContext( + jsi::Runtime& runtime, + RuntimeTargetDelegate& runtimeTargetDelegate, + std::string name) + : runtimeTargetDelegate_(runtimeTargetDelegate), + name_(std::move(name)), + orchestrator_(ConsoleTaskOrchestrator::getInstance()) { + stackTrace_ = runtimeTargetDelegate_.captureStackTrace(runtime); +} + +ConsoleTaskContext::~ConsoleTaskContext() { + orchestrator_.cancelTask(id()); +} + +ConsoleTaskId ConsoleTaskContext::id() const { + return ConsoleTaskId{(void*)this}; +} + +std::optional ConsoleTaskContext::getSerializedStackTrace() + const { + auto maybeValue = runtimeTargetDelegate_.serializeStackTrace(*stackTrace_); + if (maybeValue) { + maybeValue.value()["description"] = name_; + } + + return maybeValue; +} + +std::function()> +ConsoleTaskContext::getSerializedStackTraceProvider() const { + return [selfWeak = weak_from_this()]() -> std::optional { + if (auto self = selfWeak.lock()) { + return self->getSerializedStackTrace(); + } + + return std::nullopt; + }; +} + +void ConsoleTaskContext::schedule() { + orchestrator_.scheduleTask(id(), weak_from_this()); +} + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTaskContext.h b/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTaskContext.h new file mode 100644 index 00000000000000..e651168661bc37 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTaskContext.h @@ -0,0 +1,107 @@ +/* + * 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 "StackTrace.h" + +#include +#include + +#include +#include +#include +#include + +namespace facebook::react::jsinspector_modern { + +class ConsoleTaskOrchestrator; +class RuntimeTargetDelegate; + +class ConsoleTaskId { + public: + ConsoleTaskId() = default; + ~ConsoleTaskId() = default; + + ConsoleTaskId(const ConsoleTaskId &) = default; + ConsoleTaskId &operator=(const ConsoleTaskId &) = default; + + ConsoleTaskId(ConsoleTaskId &&) = default; + ConsoleTaskId &operator=(ConsoleTaskId &&) = default; + + bool operator==(const ConsoleTaskId &) const = default; + inline operator bool() const + { + return (bool)id_; + } + + explicit inline operator void *() const + { + return id_; + } + + private: + explicit inline ConsoleTaskId(void *id) : id_(id) + { + assert(id_ != nullptr); + } + + void *id_{nullptr}; + + friend class ConsoleTaskContext; +}; + +class ConsoleTaskContext : public std::enable_shared_from_this { + public: + ConsoleTaskContext(jsi::Runtime &runtime, RuntimeTargetDelegate &runtimeTargetDelegate, std::string name); + ~ConsoleTaskContext(); + + // Can't be moved or copied: the address of `ConsoleTaskContext` is used to + // identify this task and all corresponding invocations. + ConsoleTaskContext(const ConsoleTaskContext &) = delete; + ConsoleTaskContext &operator=(const ConsoleTaskContext &) = delete; + + ConsoleTaskContext(ConsoleTaskContext &&) = delete; + ConsoleTaskContext &operator=(ConsoleTaskContext &&) = delete; + + /** + * Unique identifier that is calculated based on the address of + * ConsoleTaskContext. + */ + ConsoleTaskId id() const; + + /** + * Returns the serialized stack trace that was captured during the allocation + * of ConsoleTaskContext. + */ + std::optional getSerializedStackTrace() const; + + /** + * Returns a function that returns the serialized stack trace, if available. + */ + std::function()> getSerializedStackTraceProvider() const; + + void schedule(); + + private: + RuntimeTargetDelegate &runtimeTargetDelegate_; + std::string name_; + ConsoleTaskOrchestrator &orchestrator_; + std::unique_ptr stackTrace_; +}; + +} // namespace facebook::react::jsinspector_modern + +namespace std { +template <> +struct hash { + size_t operator()(const facebook::react::jsinspector_modern::ConsoleTaskId &id) const + { + return std::hash{}(static_cast(id)); + } +}; +} // namespace std diff --git a/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTaskOrchestrator.cpp b/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTaskOrchestrator.cpp new file mode 100644 index 00000000000000..17baa16a19ea81 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTaskOrchestrator.cpp @@ -0,0 +1,55 @@ +/* + * 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 "ConsoleTaskOrchestrator.h" + +namespace facebook::react::jsinspector_modern { + +/* static */ ConsoleTaskOrchestrator& ConsoleTaskOrchestrator::getInstance() { + static ConsoleTaskOrchestrator instance; + return instance; +} + +void ConsoleTaskOrchestrator::scheduleTask( + ConsoleTaskId taskId, + std::weak_ptr taskContext) { + std::lock_guard lock(mutex_); + tasks_.emplace(taskId, taskContext); +} + +void ConsoleTaskOrchestrator::cancelTask(ConsoleTaskId id) { + std::lock_guard lock(mutex_); + tasks_.erase(id); +} + +void ConsoleTaskOrchestrator::startTask(ConsoleTaskId id) { + std::lock_guard lock(mutex_); + stack_.push(id); +} + +void ConsoleTaskOrchestrator::finishTask(ConsoleTaskId id) { + std::lock_guard lock(mutex_); + assert(stack_.top() == id); + + stack_.pop(); +} + +std::shared_ptr ConsoleTaskOrchestrator::top() const { + std::lock_guard lock(mutex_); + if (stack_.empty()) { + return nullptr; + } + + auto it = tasks_.find(stack_.top()); + if (it == tasks_.end()) { + return nullptr; + } + + return it->second.lock(); +} + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTaskOrchestrator.h b/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTaskOrchestrator.h new file mode 100644 index 00000000000000..79d645baeda983 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/ConsoleTaskOrchestrator.h @@ -0,0 +1,48 @@ +/* + * 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 +#include + +#include "ConsoleTaskContext.h" + +namespace facebook::react::jsinspector_modern { + +class ConsoleTaskOrchestrator { + public: + static ConsoleTaskOrchestrator &getInstance(); + + ~ConsoleTaskOrchestrator() = default; + + ConsoleTaskOrchestrator(const ConsoleTaskOrchestrator &) = delete; + ConsoleTaskOrchestrator &operator=(const ConsoleTaskOrchestrator &) = delete; + + ConsoleTaskOrchestrator(ConsoleTaskOrchestrator &&) = delete; + ConsoleTaskOrchestrator &operator=(ConsoleTaskOrchestrator &&) = delete; + + void scheduleTask(ConsoleTaskId taskId, std::weak_ptr taskContext); + void cancelTask(ConsoleTaskId taskId); + + void startTask(ConsoleTaskId taskId); + void finishTask(ConsoleTaskId taskId); + std::shared_ptr top() const; + + private: + ConsoleTaskOrchestrator() = default; + + std::stack stack_; + std::unordered_map> tasks_; + /** + * Protects the stack_ and tasks_ members. + */ + mutable std::mutex mutex_; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h index 7e0b62b442e7ff..74d5c935a5716b 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.h @@ -99,7 +99,7 @@ class RuntimeAgent final { * Lifetime of this agent is bound to the lifetime of the Tracing session - * HostTargetTraceRecording and to the lifetime of the RuntimeTarget. */ -class RuntimeTracingAgent : tracing::TargetTracingAgent { +class RuntimeTracingAgent : public tracing::TargetTracingAgent { public: explicit RuntimeTracingAgent(tracing::TraceRecordingState &state, RuntimeTargetController &targetController); diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp index 0f84ecd0e17987..2fe2f51552bcf2 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp @@ -248,6 +248,18 @@ bool RuntimeTarget::isDomainEnabled(Domain domain) const { return threadSafeDomainStatus_[domain]; } +bool RuntimeTarget::isConsoleCreateTaskEnabled() const { + if (isDomainEnabled(Domain::Runtime)) { + return true; + } + + if (auto tracingAgent = tracingAgent_.lock()) { + return tracingAgent->isRunningInBackgroundMode(); + } + + return false; +} + RuntimeTargetController::RuntimeTargetController(RuntimeTarget& target) : target_(target) {} diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h index 236cf83e7de381..7312baac0dbc3e 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h @@ -207,19 +207,6 @@ class JSINSPECTOR_EXPORT RuntimeTarget : public EnableExecutorFromThis createTracingAgent(tracing::TraceRecordingState &state); - /** - * Start sampling profiler for a particular JavaScript runtime. - */ - void enableSamplingProfiler(); - /** - * Stop sampling profiler for a particular JavaScript runtime. - */ - void disableSamplingProfiler(); - /** - * Return recorded sampling profile for the previous sampling session. - */ - tracing::RuntimeSamplingProfile collectSamplingProfile(); - private: using Domain = RuntimeTargetController::Domain; @@ -275,6 +262,18 @@ class JSINSPECTOR_EXPORT RuntimeTarget : public EnableExecutorFromThis tracingAgent_; + /** + * Start sampling profiler for a particular JavaScript runtime. + */ + void enableSamplingProfiler(); + /** + * Stop sampling profiler for a particular JavaScript runtime. + */ + void disableSamplingProfiler(); + /** + * Return recorded sampling profile for the previous sampling session. + */ + tracing::RuntimeSamplingProfile collectSamplingProfile(); /** * Adds a function with the given name on the runtime's global object, that @@ -293,6 +292,10 @@ class JSINSPECTOR_EXPORT RuntimeTarget : public EnableExecutorFromThis