Skip to content

Commit

Permalink
refactor: extract performance observers to native layer and push entr…
Browse files Browse the repository at this point in the history
…ies to each observer

refactor: use map for user timing API for better performance
  • Loading branch information
robik committed Jul 24, 2024
1 parent 91ecd7e commit 776158a
Show file tree
Hide file tree
Showing 15 changed files with 696 additions and 315 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,11 @@ class NativePerformanceObserver
public:
NativePerformanceObserver(std::shared_ptr<CallInvoker> jsInvoker);

void startReporting(jsi::Runtime& rt, PerformanceEntryType entryType);

void stopReporting(jsi::Runtime& rt, PerformanceEntryType entryType);

void setIsBuffered(
jsi::Runtime& rt,
const std::vector<PerformanceEntryType> entryTypes,
bool isBuffered);

PerformanceEntryReporter::PopPendingEntriesResult popPendingEntries(
jsi::Runtime& rt);

void setOnPerformanceEntryCallback(
jsi::Runtime& rt,
std::optional<AsyncCallback<>> callback);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <algorithm>
#include <functional>
#include <vector>
#include "PerformanceEntry.h"

namespace facebook::react {

Expand All @@ -31,23 +32,9 @@ constexpr size_t DEFAULT_MAX_SIZE = 1024;
template <class T>
class BoundedConsumableBuffer {
public:
/**
* Status of the add/push operation for the `BoundedConsumableBuffer`
* container
*/
enum class PushStatus {
// There was free space in the buffer, element was successfully pushed:
OK = 0,

// Element was pushed, but had to overwrite some already consumed elements:
OVERWRITE = 1,

// Element wasn't pushed, as buffer size limit has been reached and it's
// not possible to overwrite already consumed elements anymore:
DROP = 2,
};
using PushStatus = PerformanceEntryPushStatus;

BoundedConsumableBuffer(size_t maxSize = DEFAULT_MAX_SIZE)
explicit BoundedConsumableBuffer(size_t maxSize = DEFAULT_MAX_SIZE)
: maxSize_(maxSize) {
entries_.reserve(maxSize_);
}
Expand All @@ -57,10 +44,10 @@ class BoundedConsumableBuffer {
* operation, which will depend on whether the buffer reached the max allowed
* size and how many are there unconsumed elements.
*/
PushStatus add(const T&& el) {
PushStatus add(const T& el) {
if (entries_.size() < maxSize_) {
// Haven't reached max buffer size yet, just add and grow the buffer
entries_.emplace_back(el);
entries_.push_back(el);
cursorEnd_++;
numToConsume_++;
return PushStatus::OK;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#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.
#This source code is licensed under the MIT license found in the
#LICENSE file in the root directory of this source tree.

cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* 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 <numeric>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include "PerformanceEntry.h"

namespace facebook::react {

class ConsumableEntryMap {
public:
ConsumableEntryMap() = default;

void add(const PerformanceEntry& entry) {
auto& list = entryMap_.at(entry.name);
auto& toConsume = toConsumeMap_.at(entry.name);
list.push_back(entry);
totalEntryCount_ += 1;
totalToConsume_ += 1;
toConsume += 1;
}

void clear() {
entryMap_.clear();
toConsumeMap_.clear();
totalEntryCount_ = 0;
totalToConsume_ = 0;
}

void clear(const std::string& name) {
if (auto node = entryMap_.find(name); node != entryMap_.end()) {
totalEntryCount_ -= node->second.size();
totalToConsume_ -= node->second.size();
toConsumeMap_[name] = 0;
node->second.clear();
}
}

std::optional<PerformanceEntry> find(const std::string& name) const {
if (auto node = entryMap_.find(name); node != entryMap_.end()) {
if (!node->second.empty()) {
return std::make_optional<PerformanceEntry>(node->second.back());
}
}

return std::nullopt;
}

void getEntries(std::vector<PerformanceEntry>& target) const {
if (allEntriesCache_.has_value()) {
auto& allEntries = allEntriesCache_.value();
target.insert(target.end(), allEntries.begin(), allEntries.end());
return;
}

std::vector<PerformanceEntry> allEntries;
// pre-allocate result vector
allEntries.reserve(totalEntryCount_);

for (const auto& [_, entries] : entryMap_) {
allEntries.insert(allEntries.end(), entries.begin(), entries.end());
}

std::stable_sort(
allEntries.begin(), allEntries.end(), PerformanceEntrySorter{});
allEntriesCache_ = allEntries;
target.insert(target.end(), allEntries.begin(), allEntries.end());
}

/**
* Retrieves buffer entries, whether consumed or not, with predicate
*/
void consume(std::vector<PerformanceEntry>& target) {
for (const auto& [name, entries] : entryMap_) {
target.insert(
target.end(), entries.end() - toConsumeMap_[name], entries.end());
toConsumeMap_[name] = 0;
}
}

size_t getNumToConsume() const {
return totalToConsume_;
}

void getEntries(
const std::string& name,
std::vector<PerformanceEntry>& target) const {
if (auto node = entryMap_.find(name); node != entryMap_.end()) {
target.insert(target.end(), node->second.begin(), node->second.end());
}
}

private:
std::unordered_map<std::string, std::vector<PerformanceEntry>> entryMap_{};
std::unordered_map<std::string, size_t> toConsumeMap_{};
mutable std::optional<std::vector<PerformanceEntry>> allEntriesCache_;
size_t totalEntryCount_ = 0;
size_t totalToConsume_ = 0;
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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 <string>
#include <string_view>
#include <unordered_set>

namespace facebook::react {

using DOMHighResTimeStamp = double;

using PerformanceEntryInteractionId = uint32_t;

enum class PerformanceEntryType {
// We need to preserve these values for backwards compatibility.
MARK = 1,
MEASURE = 2,
EVENT = 3,
LONGTASK = 4,
_NEXT = 5,
};

struct PerformanceEntry {
std::string name;
PerformanceEntryType entryType;
DOMHighResTimeStamp startTime;
DOMHighResTimeStamp duration = 0;

// For "event" entries only:
std::optional<DOMHighResTimeStamp> processingStart;
std::optional<DOMHighResTimeStamp> processingEnd;
std::optional<PerformanceEntryInteractionId> interactionId;
};

constexpr size_t NUM_PERFORMANCE_ENTRY_TYPES =
(size_t)PerformanceEntryType::_NEXT - 1; // Valid types start from 1.

/**
* Status of the add/push operation for the `BoundedConsumableBuffer`
* container
*/
enum class PerformanceEntryPushStatus {
// There was free space in the buffer, element was successfully pushed:
OK = 0,

// Element was pushed, but had to overwrite some already consumed elements:
OVERWRITE = 1,

// Element wasn't pushed, as buffer size limit has been reached and it's
// not possible to overwrite already consumed elements anymore:
DROP = 2,
};

struct PerformanceEntrySorter {
bool operator()(const PerformanceEntry& lhs, const PerformanceEntry& rhs) {
if (lhs.startTime != rhs.startTime) {
return lhs.startTime < rhs.startTime;
} else {
return lhs.duration < rhs.duration;
}
}
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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 "PerformanceEntryBuffer.h"

namespace facebook::react {

PerformanceEntryPushStatus PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry&& entry) {
return entries.add(std::move(entry));
}

void PerformanceEntryCircularBuffer::consume(std::vector<PerformanceEntry>& target) {
entries.consume(target);
}

size_t PerformanceEntryCircularBuffer::pendingMessagesCount() const {
return entries.getNumToConsume();
}

void PerformanceEntryCircularBuffer::getEntries(std::optional<std::string_view> name, std::vector<PerformanceEntry>& target) const {
entries.getEntries(
target, [&](const PerformanceEntry& e) { return e.name == name; });
}

void PerformanceEntryCircularBuffer::clear() {
entries.clear();
}


void PerformanceEntryCircularBuffer::clear(std::string_view name) {
entries.clear([&](const PerformanceEntry& e) { return e.name == name; });
}

PerformanceEntryPushStatus PerformanceEntryKeyedBuffer::add(const facebook::react::PerformanceEntry&& entry) {
entries.add(entry);
return PerformanceEntryPushStatus::OK;
}

void PerformanceEntryKeyedBuffer::consume(std::vector<PerformanceEntry>& target) {
entries.consume(target);
}

size_t PerformanceEntryKeyedBuffer::pendingMessagesCount() const {
return entries.getNumToConsume();
}

void PerformanceEntryKeyedBuffer::getEntries(std::optional<std::string_view> name, std::vector<PerformanceEntry>& target) const {
if (name.has_value()) {
std::string nameStr{name.value()};
entries.getEntries(nameStr, target);
} else {
entries.getEntries(target);
}
}

void PerformanceEntryKeyedBuffer::clear() {
entries.clear();
}

void PerformanceEntryKeyedBuffer::clear(std::string_view name) {
std::string nameStr{name};
entries.clear(nameStr);
}

} // namespace facebook::react
Loading

0 comments on commit 776158a

Please sign in to comment.