Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ NativePerformance::NativePerformance(std::shared_ptr<CallInvoker> jsInvoker)
: NativePerformanceCxxSpec(std::move(jsInvoker)) {}

HighResTimeStamp NativePerformance::now(jsi::Runtime& /*rt*/) {
return forcedCurrentTimeStamp_.value_or(HighResTimeStamp::now());
return HighResTimeStamp::now();
}

void NativePerformance::reportMark(
Expand Down Expand Up @@ -395,12 +395,6 @@ NativePerformance::getSupportedPerformanceEntryTypes(jsi::Runtime& /*rt*/) {

#pragma mark - Testing

void NativePerformance::setCurrentTimeStampForTesting(
jsi::Runtime& /*rt*/,
HighResTimeStamp ts) {
forcedCurrentTimeStamp_ = ts;
}

void NativePerformance::clearEventCountsForTesting(jsi::Runtime& /*rt*/) {
PerformanceEntryReporter::getInstance()->clearEventCounts();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,7 @@ class NativePerformance : public NativePerformanceCxxSpec<NativePerformance> {

#pragma mark - Testing

void setCurrentTimeStampForTesting(jsi::Runtime& rt, HighResTimeStamp ts);
void clearEventCountsForTesting(jsi::Runtime& rt);

private:
std::optional<HighResTimeStamp> forcedCurrentTimeStamp_;
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ PerformanceEntryReporter::PerformanceEntryReporter()
}

HighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const {
return timeStampProvider_ != nullptr ? timeStampProvider_()
: HighResTimeStamp::now();
return HighResTimeStamp::now();
}

void PerformanceEntryReporter::addEventTimingListener(
PerformanceEntryReporterEventTimingListener* listener) {
std::unique_lock lock(listenersMutex_);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ class PerformanceEntryReporter {

HighResTimeStamp getCurrentTimeStamp() const;

void setTimeStampProvider(std::function<HighResTimeStamp()> provider) {
timeStampProvider_ = std::move(provider);
}

void addEventTimingListener(
PerformanceEntryReporterEventTimingListener* listener);
void removeEventTimingListener(
Expand Down Expand Up @@ -138,7 +134,6 @@ class PerformanceEntryReporter {

std::unordered_map<std::string, uint32_t> eventCounts_;

std::function<HighResTimeStamp()> timeStampProvider_ = nullptr;
mutable std::shared_mutex listenersMutex_;
std::vector<PerformanceEntryReporterEventTimingListener*>
eventTimingListeners_{};
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native/ReactCommon/react/timing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
add_library(react_timing INTERFACE)

target_include_directories(react_timing INTERFACE ${REACT_COMMON_DIR})
target_link_libraries(react_timing INTERFACE
react_debug)
target_compile_reactnative_options(react_timing INTERFACE)
target_compile_options(react_timing INTERFACE -Wpedantic)
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ Pod::Spec.new do |s|
s.module_name = "React_timing"
s.header_mappings_dir = "./"
end

add_dependency(s, "React-debug")
end
34 changes: 31 additions & 3 deletions packages/react-native/ReactCommon/react/timing/primitives.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

#pragma once

#include <react/debug/flags.h>
#include <chrono>
#include <functional>

namespace facebook::react {

Expand Down Expand Up @@ -193,11 +195,10 @@ class HighResTimeStamp {
const HighResDuration& rhs);

public:
HighResTimeStamp() noexcept
: chronoTimePoint_(std::chrono::steady_clock::now()) {}
HighResTimeStamp() noexcept : chronoTimePoint_(chronoNow()) {}

static HighResTimeStamp now() noexcept {
return HighResTimeStamp(std::chrono::steady_clock::now());
return HighResTimeStamp(chronoNow());
}

static constexpr HighResTimeStamp min() noexcept {
Expand Down Expand Up @@ -228,6 +229,14 @@ class HighResTimeStamp {
return HighResTimeStamp(chronoTimePoint);
}

#ifdef REACT_NATIVE_DEBUG
static void setTimeStampProviderForTesting(
std::function<std::chrono::steady_clock::time_point()>&&
timeStampProvider) {
getTimeStampProvider() = std::move(timeStampProvider);
}
#endif

// This method is provided for convenience, if you need to convert
// HighResTimeStamp to some common epoch with time stamps from other sources.
constexpr std::chrono::steady_clock::time_point toChronoSteadyClockTimePoint()
Expand Down Expand Up @@ -275,6 +284,25 @@ class HighResTimeStamp {
: chronoTimePoint_(chronoTimePoint) {}

std::chrono::steady_clock::time_point chronoTimePoint_;

#ifdef REACT_NATIVE_DEBUG
static std::function<std::chrono::steady_clock::time_point()>&
getTimeStampProvider() {
static std::function<std::chrono::steady_clock::time_point()>
timeStampProvider = nullptr;
return timeStampProvider;
}

static std::chrono::steady_clock::time_point chronoNow() {
auto& timeStampProvider = getTimeStampProvider();
return timeStampProvider != nullptr ? timeStampProvider()
: std::chrono::steady_clock::now();
}
#else
inline static std::chrono::steady_clock::time_point chronoNow() {
return std::chrono::steady_clock::now();
}
#endif
};

inline constexpr HighResDuration operator-(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ interface Spec extends TurboModule {
shadowNode: mixed /* ShadowNode */,
): () => ?number;
saveJSMemoryHeapSnapshot: (filePath: string) => void;
forceHighResTimeStamp: (timeStamp: ?number) => void;
}

export default TurboModuleRegistry.getEnforcing<Spec>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,12 @@ import type {
} from 'react-native/src/private/webapis/performance/PerformanceObserver';

import * as Fantom from '@react-native/fantom';
import nullthrows from 'nullthrows';
import setUpPerformanceObserver from 'react-native/src/private/setup/setUpPerformanceObserver';
import {PerformanceLongTaskTiming} from 'react-native/src/private/webapis/performance/LongTasks';
import {PerformanceObserver} from 'react-native/src/private/webapis/performance/PerformanceObserver';

setUpPerformanceObserver();

function sleep(ms: number) {
const end = performance.now() + ms;
while (performance.now() < end) {}
}

function ensurePerformanceLongTaskTiming(
value: mixed,
): PerformanceLongTaskTiming {
Expand All @@ -41,16 +35,12 @@ function ensurePerformanceLongTaskTiming(
}

let observer: ?PerformanceObserver;
let pendingHighResTimeStampMock: ?Fantom.HighResTimeStampMock;

const constants = Fantom.getConstants();
const isGithubCI = constants.isOSS && constants.isRunningFromCI;

// Skip the test on Github CI.
// In that environment, execution speed is unreliable so some of the tasks
// we schedule as part of the test run (even the task to report long tasks) are
// also being reported as long, and we don't have a way to distinguish those
// from the intentionally long tasks we're scheduling for testing.
const describe = isGithubCI ? global.describe.skip : global.describe;
function installHighResTimeStampMock() {
pendingHighResTimeStampMock = Fantom.installHighResTimeStampMock();
return pendingHighResTimeStampMock;
}

describe('LongTasks API', () => {
afterEach(() => {
Expand All @@ -60,54 +50,62 @@ describe('LongTasks API', () => {
observer = null;
});
}

if (pendingHighResTimeStampMock) {
pendingHighResTimeStampMock.uninstall();
pendingHighResTimeStampMock = null;
}
});

it('does NOT report short tasks (under 50ms)', () => {
const callback = jest.fn();

const mockClock = installHighResTimeStampMock();

mockClock.setTime(0);

Fantom.runTask(() => {
observer = new PerformanceObserver(callback);
observer.observe({entryTypes: ['longtask']});
});

const initialCallCount = callback.mock.calls.length;
expect(callback).not.toHaveBeenCalled();

Fantom.runTask(() => {
// Short task.
mockClock.advanceTimeBy(10);
});

expect(callback).toHaveBeenCalledTimes(initialCallCount);
expect(callback).not.toHaveBeenCalled();

Fantom.runTask(() => {
// Slightly longer task, but still not long.
sleep(40);
mockClock.advanceTimeBy(40);
});

expect(callback).toHaveBeenCalledTimes(initialCallCount);
expect(callback).not.toHaveBeenCalled();
});

it('reports long tasks (over 50ms)', () => {
const callback = jest.fn();

const mockClock = installHighResTimeStampMock();

Fantom.runTask(() => {
observer = new PerformanceObserver(callback);
observer.observe({entryTypes: ['longtask']});
});

const initialCallCount = callback.mock.calls.length;
expect(callback).not.toHaveBeenCalled();

const beforeTaskStartTime = performance.now();
let afterTaskStartTime;
mockClock.setTime(10);

Fantom.runTask(() => {
afterTaskStartTime = performance.now();
// Long task.
sleep(51);
mockClock.advanceTimeBy(51);
});

const afterTaskEndTime = performance.now();

expect(callback).toHaveBeenCalledTimes(initialCallCount + 1);
expect(callback).toHaveBeenCalledTimes(1);

const [entries, _observer, options] = callback.mock
.lastCall as $FlowFixMe as [
Expand All @@ -127,66 +125,64 @@ describe('LongTasks API', () => {

expect(entry.name).toBe('self');
expect(entry.entryType).toBe('longtask');
expect(entry.startTime).toBeGreaterThanOrEqual(beforeTaskStartTime);
expect(entry.startTime).toBeLessThanOrEqual(nullthrows(afterTaskStartTime));
expect(entry.duration).toBeGreaterThanOrEqual(51);
expect(entry.duration).toBeLessThanOrEqual(
afterTaskEndTime - beforeTaskStartTime,
);
expect(entry.startTime).toBe(10);
expect(entry.duration).toBe(51);
expect(entry.attribution).toEqual([]);
});

describe('tasks that yield', () => {
it('should NOT be reported if they are longer than 50ms but had yielding opportunities in intervals shorter than 50ms', () => {
const callback = jest.fn();

const mockClock = installHighResTimeStampMock();

Fantom.runTask(() => {
observer = new PerformanceObserver(callback);
observer.observe({entryTypes: ['longtask']});
});

const initialCallCount = callback.mock.calls.length;
expect(callback).not.toHaveBeenCalled();

const shouldYield = global.nativeRuntimeScheduler.unstable_shouldYield;

mockClock.setTime(10);

Fantom.runTask(() => {
sleep(30);
mockClock.advanceTimeBy(30);
shouldYield();
sleep(30);
mockClock.advanceTimeBy(30);
shouldYield();
sleep(30);
mockClock.advanceTimeBy(30);
});

expect(callback).toHaveBeenCalledTimes(initialCallCount);
expect(callback).not.toHaveBeenCalled();
});

it('should be reported if running for longer than 50ms between yielding opportunities', () => {
const callback = jest.fn();

const mockClock = installHighResTimeStampMock();

Fantom.runTask(() => {
observer = new PerformanceObserver(callback);
observer.observe({entryTypes: ['longtask']});
});

const initialCallCount = callback.mock.calls.length;
expect(callback).not.toHaveBeenCalled();

const shouldYield = global.nativeRuntimeScheduler.unstable_shouldYield;

const beforeTaskStartTime = performance.now();
let afterTaskStartTime;
mockClock.setTime(10);

Fantom.runTask(() => {
afterTaskStartTime = performance.now();
sleep(40);
mockClock.advanceTimeBy(40);
shouldYield();
sleep(51); // long interval without yielding
mockClock.advanceTimeBy(51); // long interval without yielding
shouldYield();
sleep(40);
mockClock.advanceTimeBy(40);
});

const afterTaskEndTime = performance.now();

expect(callback).toHaveBeenCalledTimes(initialCallCount + 1);
expect(callback).toHaveBeenCalledTimes(1);

const entries = callback.mock.lastCall[0] as PerformanceObserverEntryList;
const allEntries = entries.getEntries();
Expand All @@ -195,14 +191,8 @@ describe('LongTasks API', () => {
const entry = ensurePerformanceLongTaskTiming(allEntries[0]);
expect(entry.name).toBe('self');
expect(entry.entryType).toBe('longtask');
expect(entry.startTime).toBeGreaterThanOrEqual(beforeTaskStartTime);
expect(entry.startTime).toBeLessThanOrEqual(
nullthrows(afterTaskStartTime),
);
expect(entry.duration).toBeGreaterThanOrEqual(131); // just the sum of the sleep times in the task
expect(entry.duration).toBeLessThanOrEqual(
afterTaskEndTime - beforeTaskStartTime,
);
expect(entry.startTime).toBe(10);
expect(entry.duration).toBe(131);
expect(entry.attribution).toEqual([]);
});
});
Expand Down
Loading
Loading