Implement native logic for performance event reporting (#35526)

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

[Changelog][Internal]

This closes the full loop according to the [technical design](https://fb.quip.com/MdqgAk1Eb2dV) of the WebPerf API implementation, with the main components and the working central data flow in place.

The next step is to add some buffering/throttling, as in this diff we just spawn an idle-priority task after every performance entry coming (even though they still naturally do come in batches, because they manage to accumulate before the task is executed).

Reviewed By: christophpurrer

Differential Revision: D41496082

fbshipit-source-id: 5fd4cf22e75806f7bc98d1d1b6691596ccadf8b9
This commit is contained in:
Ruslan Shestopalyuk 2022-12-01 09:49:44 -08:00 коммит произвёл Facebook GitHub Bot
Родитель e2c4941c80
Коммит 14e69db482
5 изменённых файлов: 151 добавлений и 40 удалений

Просмотреть файл

@ -7,45 +7,53 @@
#include "NativePerformanceObserver.h"
#include <glog/logging.h>
#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<CallInvoker> jsInvoker)
: NativePerformanceObserverCxxSpec(std::move(jsInvoker)) {}
: NativePerformanceObserverCxxSpec(std::move(jsInvoker)),
reporter_(std::make_unique<PerformanceEntryReporter>()) {}
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<RawPerformanceEntry> NativePerformanceObserver::getPendingEntries(
jsi::Runtime &rt) {
return std::vector<RawPerformanceEntry>{};
return reporter_->popPendingEntries();
}
void NativePerformanceObserver::setOnPerformanceEntryCallback(
jsi::Runtime &rt,
std::optional<AsyncCallback<>> 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

Просмотреть файл

@ -15,6 +15,7 @@
#include <vector>
namespace facebook::react {
class PerformanceEntryReporter;
#pragma mark - Structs
@ -46,6 +47,7 @@ class NativePerformanceObserver
std::enable_shared_from_this<NativePerformanceObserver> {
public:
NativePerformanceObserver(std::shared_ptr<CallInvoker> 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<AsyncCallback<>> callback_;
std::unique_ptr<PerformanceEntryReporter> reporter_;
};
} // namespace facebook::react

Просмотреть файл

@ -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 <glog/logging.h>
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
#include "NativePerformanceObserver.h"
namespace facebook::react {
void PerformanceEntryReporter::setReportingCallback(
std::optional<AsyncCallback<>> callback) {
callback_ = callback;
}
void PerformanceEntryReporter::startReporting(PerformanceEntryType entryType) {
reportingType_[static_cast<int>(entryType)] = true;
}
void PerformanceEntryReporter::stopReporting(PerformanceEntryType entryType) {
reportingType_[static_cast<int>(entryType)] = false;
}
std::vector<RawPerformanceEntry> PerformanceEntryReporter::getPendingEntries()
const {
return entries_;
}
std::vector<RawPerformanceEntry> 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<PerformanceEntryType>(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

Просмотреть файл

@ -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 <react/bridging/Function.h>
#include <array>
#include <optional>
#include "NativePerformanceObserver.h"
namespace facebook::react {
enum class PerformanceEntryType {
UNDEFINED = 0,
MARK = 1,
_COUNT = 2,
};
class PerformanceEntryReporter {
public:
void setReportingCallback(std::optional<AsyncCallback<>> callback);
void startReporting(PerformanceEntryType entryType);
void stopReporting(PerformanceEntryType entryType);
std::vector<RawPerformanceEntry> getPendingEntries() const;
std::vector<RawPerformanceEntry> popPendingEntries();
void clearPendingEntries();
void logEntry(const RawPerformanceEntry &entry);
bool isReportingType(PerformanceEntryType entryType) const {
return reportingType_[static_cast<int>(entryType)];
}
private:
std::optional<AsyncCallback<>> callback_;
std::vector<RawPerformanceEntry> entries_;
std::array<bool, (size_t)PerformanceEntryType::_COUNT> reportingType_{false};
};
} // namespace facebook::react

Просмотреть файл

@ -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<PerformanceEntryType, number> = new Map();
const _observedEntryTypeRefCount: Map<PerformanceEntryType, number> = new Map();
let _observers: Set<PerformanceObserver> = new Set();
const _observers: Set<PerformanceObserver> = 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<PerformanceEntryType>;
callback: PerformanceObserverCallback;
entryTypes: $ReadOnlySet<PerformanceEntryType>;
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,
);
}
}