Skip to content

Commit

Permalink
Implement native logic for performance event reporting (#35526)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #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
  • Loading branch information
rshest authored and facebook-github-bot committed Dec 1, 2022
1 parent e2c4941 commit 14e69db
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 40 deletions.
30 changes: 19 additions & 11 deletions Libraries/WebPerformance/NativePerformanceObserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 3 additions & 1 deletion Libraries/WebPerformance/NativePerformanceObserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <vector>

namespace facebook::react {
class PerformanceEntryReporter;

#pragma mark - Structs

Expand Down Expand Up @@ -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);

Expand All @@ -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
52 changes: 52 additions & 0 deletions Libraries/WebPerformance/PerformanceEntryReporter.cpp
Original file line number Diff line number Diff line change
@@ -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
44 changes: 44 additions & 0 deletions Libraries/WebPerformance/PerformanceEntryReporter.h
Original file line number Diff line number Diff line change
@@ -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
61 changes: 33 additions & 28 deletions Libraries/WebPerformance/PerformanceObserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -142,30 +163,32 @@ 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) {
if (!NativePerformanceObserver) {
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);
}
Expand All @@ -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);
Expand All @@ -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,
);
}
}

0 comments on commit 14e69db

Please sign in to comment.