diff --git a/Libraries/WebPerformance/NativePerformanceObserver.cpp b/Libraries/WebPerformance/NativePerformanceObserver.cpp index 21bfd9feafb..506cbbe5834 100644 --- a/Libraries/WebPerformance/NativePerformanceObserver.cpp +++ b/Libraries/WebPerformance/NativePerformanceObserver.cpp @@ -7,45 +7,53 @@ #include "NativePerformanceObserver.h" #include +#include "PerformanceEntryReporter.h" namespace facebook::react { +static PerformanceEntryType stringToPerformanceEntryType( + const std::string &entryType) { + if (entryType == "mark") { + return PerformanceEntryType::MARK; + } else { + return PerformanceEntryType::UNDEFINED; + } +} + NativePerformanceObserver::NativePerformanceObserver( std::shared_ptr jsInvoker) - : NativePerformanceObserverCxxSpec(std::move(jsInvoker)) {} + : NativePerformanceObserverCxxSpec(std::move(jsInvoker)), + reporter_(std::make_unique()) {} + +NativePerformanceObserver::~NativePerformanceObserver() {} void NativePerformanceObserver::startReporting( jsi::Runtime &rt, std::string entryType) { - LOG(INFO) << "Started reporting perf entry type: " << entryType; + reporter_->startReporting(stringToPerformanceEntryType(entryType)); } void NativePerformanceObserver::stopReporting( jsi::Runtime &rt, std::string entryType) { - LOG(INFO) << "Stopped reporting perf entry type: " << entryType; + reporter_->stopReporting(stringToPerformanceEntryType(entryType)); } std::vector NativePerformanceObserver::getPendingEntries( jsi::Runtime &rt) { - return std::vector{}; + return reporter_->popPendingEntries(); } void NativePerformanceObserver::setOnPerformanceEntryCallback( jsi::Runtime &rt, std::optional> callback) { - callback_ = callback; - LOG(INFO) << "setOnPerformanceEntryCallback: " - << (callback ? "non-empty" : "empty"); + reporter_->setReportingCallback(callback); } void NativePerformanceObserver::logEntryForDebug( jsi::Runtime &rt, RawPerformanceEntry entry) { - LOG(INFO) << "NativePerformanceObserver::logEntry: " - << "name=" << entry.name << " type=" << entry.entryType - << " startTime=" << entry.startTime - << " duration=" << entry.duration; + reporter_->logEntry(entry); } } // namespace facebook::react diff --git a/Libraries/WebPerformance/NativePerformanceObserver.h b/Libraries/WebPerformance/NativePerformanceObserver.h index e001370f1e2..4c47fe2d1be 100644 --- a/Libraries/WebPerformance/NativePerformanceObserver.h +++ b/Libraries/WebPerformance/NativePerformanceObserver.h @@ -15,6 +15,7 @@ #include namespace facebook::react { +class PerformanceEntryReporter; #pragma mark - Structs @@ -46,6 +47,7 @@ class NativePerformanceObserver std::enable_shared_from_this { public: NativePerformanceObserver(std::shared_ptr jsInvoker); + ~NativePerformanceObserver(); void startReporting(jsi::Runtime &rt, std::string entryType); @@ -60,7 +62,7 @@ class NativePerformanceObserver void logEntryForDebug(jsi::Runtime &rt, RawPerformanceEntry entry); private: - std::optional> callback_; + std::unique_ptr reporter_; }; } // namespace facebook::react diff --git a/Libraries/WebPerformance/PerformanceEntryReporter.cpp b/Libraries/WebPerformance/PerformanceEntryReporter.cpp new file mode 100644 index 00000000000..ee1eb490057 --- /dev/null +++ b/Libraries/WebPerformance/PerformanceEntryReporter.cpp @@ -0,0 +1,52 @@ +/* + * 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 "PerformanceEntryReporter.h" +#include +#include +#include "NativePerformanceObserver.h" + +namespace facebook::react { +void PerformanceEntryReporter::setReportingCallback( + std::optional> callback) { + callback_ = callback; +} + +void PerformanceEntryReporter::startReporting(PerformanceEntryType entryType) { + reportingType_[static_cast(entryType)] = true; +} +void PerformanceEntryReporter::stopReporting(PerformanceEntryType entryType) { + reportingType_[static_cast(entryType)] = false; +} + +std::vector PerformanceEntryReporter::getPendingEntries() + const { + return entries_; +} + +std::vector PerformanceEntryReporter::popPendingEntries() { + auto entriesToReturn = std::move(entries_); + entries_ = {}; + return entriesToReturn; +} + +void PerformanceEntryReporter::clearPendingEntries() { + entries_.clear(); +} + +void PerformanceEntryReporter::logEntry(const RawPerformanceEntry &entry) { + if (!isReportingType(static_cast(entry.entryType))) { + return; + } + + entries_.emplace_back(entry); + + // TODO: Add buffering/throttling - but for testing this works as well, for + // now + callback_->callWithPriority(SchedulerPriority::IdlePriority); +} +} // namespace facebook::react diff --git a/Libraries/WebPerformance/PerformanceEntryReporter.h b/Libraries/WebPerformance/PerformanceEntryReporter.h new file mode 100644 index 00000000000..a54362c56d8 --- /dev/null +++ b/Libraries/WebPerformance/PerformanceEntryReporter.h @@ -0,0 +1,44 @@ +/* + * 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 "NativePerformanceObserver.h" + +namespace facebook::react { + +enum class PerformanceEntryType { + UNDEFINED = 0, + MARK = 1, + _COUNT = 2, +}; + +class PerformanceEntryReporter { + public: + void setReportingCallback(std::optional> callback); + void startReporting(PerformanceEntryType entryType); + void stopReporting(PerformanceEntryType entryType); + + std::vector getPendingEntries() const; + std::vector popPendingEntries(); + void clearPendingEntries(); + void logEntry(const RawPerformanceEntry &entry); + + bool isReportingType(PerformanceEntryType entryType) const { + return reportingType_[static_cast(entryType)]; + } + + private: + std::optional> callback_; + std::vector entries_; + std::array reportingType_{false}; +}; + +} // namespace facebook::react diff --git a/Libraries/WebPerformance/PerformanceObserver.js b/Libraries/WebPerformance/PerformanceObserver.js index b3cf01d9931..477e03a2e67 100644 --- a/Libraries/WebPerformance/PerformanceObserver.js +++ b/Libraries/WebPerformance/PerformanceObserver.js @@ -18,7 +18,7 @@ import NativePerformanceObserver from './NativePerformanceObserver'; export type HighResTimeStamp = number; // TODO: Extend once new types (such as event) are supported. -export type PerformanceEntryType = 'undefined' | 'mark'; +export type PerformanceEntryType = 'mark'; export class PerformanceEntry { name: string; @@ -108,12 +108,33 @@ export type PerformanceObserverInit = type: PerformanceEntryType, }; -let _observedEntryTypeRefCount: Map = new Map(); +const _observedEntryTypeRefCount: Map = new Map(); -let _observers: Set = new Set(); +const _observers: Set = new Set(); let _onPerformanceEntryCallbackIsSet: boolean = false; +// This is a callback that gets scheduled and periodically called from the native side +const onPerformanceEntry = () => { + if (!NativePerformanceObserver) { + return; + } + const rawEntries = NativePerformanceObserver.getPendingEntries(); + if (rawEntries.length === 0) { + return; + } + const entries = rawEntries.map(rawToPerformanceEntry); + for (const observer of _observers) { + const entriesForObserver: PerformanceEntryList = entries.filter( + entry => observer.entryTypes.has(entry.entryType) !== -1, + ); + observer.callback( + new PerformanceObserverEntryList(entriesForObserver), + observer, + ); + } +}; + function warnNoNativePerformanceObserver() { warnOnce( 'missing-native-performance-observer', @@ -142,11 +163,11 @@ function warnNoNativePerformanceObserver() { * observer.observe({ type: "event" }); */ export default class PerformanceObserver { - _callback: PerformanceObserverCallback; - _entryTypes: $ReadOnlySet; + callback: PerformanceObserverCallback; + entryTypes: $ReadOnlySet; constructor(callback: PerformanceObserverCallback) { - this._callback = callback; + this.callback = callback; } observe(options: PerformanceObserverInit) { @@ -154,18 +175,20 @@ export default class PerformanceObserver { warnNoNativePerformanceObserver(); return; } + if (!_onPerformanceEntryCallbackIsSet) { NativePerformanceObserver.setOnPerformanceEntryCallback( onPerformanceEntry, ); _onPerformanceEntryCallbackIsSet = true; } + if (options.entryTypes) { - this._entryTypes = new Set(options.entryTypes); + this.entryTypes = new Set(options.entryTypes); } else { - this._entryTypes = new Set([options.type]); + this.entryTypes = new Set([options.type]); } - for (const type of this._entryTypes) { + for (const type of this.entryTypes) { if (!_observedEntryTypeRefCount.has(type)) { NativePerformanceObserver.startReporting(type); } @@ -182,7 +205,7 @@ export default class PerformanceObserver { warnNoNativePerformanceObserver(); return; } - for (const type of this._entryTypes) { + for (const type of this.entryTypes) { const entryTypeRefCount = _observedEntryTypeRefCount.get(type) ?? 0; if (entryTypeRefCount === 1) { _observedEntryTypeRefCount.delete(type); @@ -202,21 +225,3 @@ export default class PerformanceObserver { // TODO: add types once they are fully supported Object.freeze(['mark']); } - -// This is a callback that gets scheduled and periodically called from the native side -function onPerformanceEntry() { - if (!NativePerformanceObserver) { - return; - } - const rawEntries = NativePerformanceObserver.getPendingEntries(); - const entries = rawEntries.map(rawToPerformanceEntry); - for (const observer of _observers) { - const entriesForObserver: PerformanceEntryList = entries.filter(entry => - observer._entryTypes.has(entry.entryType), - ); - observer._callback( - new PerformanceObserverEntryList(entriesForObserver), - observer, - ); - } -}