mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Implement reporting of events from native side to WebPerformance API (#35768)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/35768 Changelog: [Internal] This implements native side mechanics for reporting user events timing to JS (PerformanceObserver API). See the standard for more details: https://www.w3.org/TR/event-timing/ The events are only logged when there are any active subscriptions (via `PerformanceObserver.observe`), also we only log "discrete events" (i.e. no likes of mouse move), so the overhead is non-existing. There are two main metrics of interest for an event lifecycle: * Time the event is spent in the queue, i.e. the time between it's created and dispatched * Time that is spend in the event handler on the JS side (event dispatch), or processing time Both of these are measured, and the corresponding fields are populated. Reviewed By: sammy-SC Differential Revision: D42294947 fbshipit-source-id: 4fd7938c04b942400befa4057d4929fb2763cee1
This commit is contained in:
committed by
Facebook GitHub Bot
parent
28a06d2fd1
commit
09ad0cc0c6
@@ -6,16 +6,20 @@
|
||||
*/
|
||||
|
||||
#include "PerformanceEntryReporter.h"
|
||||
#include <glog/logging.h>
|
||||
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
|
||||
#include <cxxreact/JSExecutor.h>
|
||||
#include <react/renderer/core/EventLogger.h>
|
||||
#include "NativePerformanceObserver.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// All the unflushed entries beyond this amount will get discarded, with
|
||||
// the amount of discarded ones sent back to the observers' callbacks as
|
||||
// "droppedEntryCount" value
|
||||
static constexpr size_t MAX_ENTRY_BUFFER_SIZE = 1024;
|
||||
|
||||
namespace facebook::react {
|
||||
EventTag PerformanceEntryReporter::sCurrentEventTag_{0};
|
||||
|
||||
PerformanceEntryReporter &PerformanceEntryReporter::getInstance() {
|
||||
static PerformanceEntryReporter instance;
|
||||
return instance;
|
||||
@@ -33,27 +37,22 @@ void PerformanceEntryReporter::stopReporting(PerformanceEntryType entryType) {
|
||||
reportingType_[static_cast<int>(entryType)] = false;
|
||||
}
|
||||
|
||||
const std::vector<RawPerformanceEntry>
|
||||
&PerformanceEntryReporter::getPendingEntries() const {
|
||||
return entries_;
|
||||
}
|
||||
|
||||
GetPendingEntriesResult PerformanceEntryReporter::popPendingEntries() {
|
||||
std::lock_guard<std::mutex> lock(entriesMutex_);
|
||||
|
||||
GetPendingEntriesResult res = {std::move(entries_), droppedEntryCount_};
|
||||
entries_ = {};
|
||||
droppedEntryCount_ = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::clearPendingEntries() {
|
||||
entries_.clear();
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::logEntry(const RawPerformanceEntry &entry) {
|
||||
if (!isReportingType(static_cast<PerformanceEntryType>(entry.entryType))) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(entriesMutex_);
|
||||
|
||||
if (entries_.size() == MAX_ENTRY_BUFFER_SIZE) {
|
||||
// Start dropping entries once reached maximum buffer size.
|
||||
// The number of dropped entries will be reported back to the corresponding
|
||||
@@ -179,7 +178,7 @@ void PerformanceEntryReporter::event(
|
||||
{name,
|
||||
static_cast<int>(
|
||||
isFirstInput ? PerformanceEntryType::FIRST_INPUT
|
||||
: PerformanceEntryType::MEASURE),
|
||||
: PerformanceEntryType::EVENT),
|
||||
startTime,
|
||||
duration,
|
||||
processingStart,
|
||||
@@ -207,4 +206,78 @@ void PerformanceEntryReporter::scheduleFlushBuffer() {
|
||||
}
|
||||
}
|
||||
|
||||
static bool isDiscreteEvent(const char *name) {
|
||||
return !std::strstr(name, "Move") && !std::strstr(name, "Layout");
|
||||
}
|
||||
|
||||
EventTag PerformanceEntryReporter::onEventStart(const char *name) {
|
||||
if (!isReportingEvents() || !isDiscreteEvent(name)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sCurrentEventTag_++;
|
||||
if (sCurrentEventTag_ == 0) {
|
||||
// The tag wrapped around (which is highly unlikely, but still)
|
||||
sCurrentEventTag_ = 1;
|
||||
}
|
||||
|
||||
if (std::strstr(name, "top") == name) {
|
||||
// Skip the "top" prefix if present
|
||||
name += 3;
|
||||
}
|
||||
|
||||
auto timeStamp = JSExecutor::performanceNow();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(eventsInFlightMutex_);
|
||||
eventsInFlight_.emplace(
|
||||
std::make_pair(sCurrentEventTag_, EventEntry{name, timeStamp, 0.0}));
|
||||
}
|
||||
return sCurrentEventTag_;
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::onEventDispatch(EventTag tag) {
|
||||
if (!isReportingEvents() || tag == 0) {
|
||||
return;
|
||||
}
|
||||
auto timeStamp = JSExecutor::performanceNow();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(eventsInFlightMutex_);
|
||||
auto it = eventsInFlight_.find(tag);
|
||||
if (it != eventsInFlight_.end()) {
|
||||
it->second.dispatchTime = timeStamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::onEventEnd(EventTag tag) {
|
||||
if (!isReportingEvents() || tag == 0) {
|
||||
return;
|
||||
}
|
||||
auto timeStamp = JSExecutor::performanceNow();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(eventsInFlightMutex_);
|
||||
auto it = eventsInFlight_.find(tag);
|
||||
if (it == eventsInFlight_.end()) {
|
||||
return;
|
||||
}
|
||||
auto &entry = it->second;
|
||||
auto &name = entry.name;
|
||||
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
||||
|
||||
// TODO: Define the way to assign interaction IDs to the event chains
|
||||
// (T141358175)
|
||||
const uint32_t interactionId = 0;
|
||||
bool firstInput = isFirstInput(name);
|
||||
event(
|
||||
std::move(name),
|
||||
entry.startTime,
|
||||
timeStamp - entry.startTime,
|
||||
firstInput,
|
||||
entry.dispatchTime,
|
||||
timeStamp,
|
||||
interactionId);
|
||||
eventsInFlight_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
Reference in New Issue
Block a user