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:
Родитель
e2c4941c80
Коммит
14e69db482
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче