mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Refactor implementation of performance.mark and performance.measure (#52586)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52586 Changelog: [internal] This fixes a "bug" (or spec-compliance issue) in the `performance.measure` method where `end` and `duration` couldn't be used together (only `start` and `duration`, and `start` and `end` could be used). This also refactors the API to be "JS-first", preparing the native module methods to support passing instances of entries to fix other issues. Verified performance impact: `mark` is slightly regressed (~7% slower) due to an additional JSI call to get the default start time and `measure` is significantly optimized (25/30% faster) due to simplified parameter handling in JSI calls. * Before | (index) | Task name | Latency average (ns) | Latency median (ns) | Throughput average (ops/s) | Throughput median (ops/s) | Samples | | ------- | --------------------------------------------------------- | -------------------- | ------------------- | -------------------------- | ------------------------- | ------- | | 0 | 'mark (default)' | '1621.03 ± 1.17%' | '1562.00' | '636986 ± 0.01%' | '640205' | 616890 | | 1 | 'mark (with custom startTime)' | '1756.88 ± 1.15%' | '1693.00' | '586826 ± 0.01%' | '590667' | 569190 | | 2 | 'measure (default)' | '2424.66 ± 1.35%' | '2333.00' | '426122 ± 0.02%' | '428633' | 412429 | | 3 | 'measure (with start and end timestamps)' | '2679.96 ± 1.23%' | '2574.00' | '385266 ± 0.02%' | '388500' | 373140 | | 4 | 'measure (with mark names)' | '2713.49 ± 0.50%' | '2644.00' | '375383 ± 0.02%' | '378215' | 368530 | | 5 | 'clearMarks' | '691.13 ± 0.07%' | '681.00' | '1467016 ± 0.01%' | '1468429' | 1446900 | | 6 | 'clearMeasures' | '706.00 ± 0.05%' | '691.00' | '1431489 ± 0.01%' | '1447178' | 1416435 | | 7 | 'mark + clearMarks' | '2083.21 ± 1.14%' | '2003.00' | '497974 ± 0.01%' | '499251' | 480028 | | 8 | 'measure + clearMeasures (with start and end timestamps)' | '3085.14 ± 0.88%' | '2974.00' | '334337 ± 0.02%' | '336247' | 324135 | | 9 | 'measure + clearMeasures (with mark names)' | '2949.45 ± 0.62%' | '2884.00' | '345335 ± 0.02%' | '346741' | 339046 | * After | (index) | Task name | Latency average (ns) | Latency median (ns) | Throughput average (ops/s) | Throughput median (ops/s) | Samples | | ------- | --------------------------------------------------------- | -------------------- | ------------------- | -------------------------- | ------------------------- | ------- | | 0 | 'mark (default)' | '1740.06 ± 1.01%' | '1692.00' | '587400 ± 0.01%' | '591017' | 574695 | | 1 | 'mark (with custom startTime)' | '1661.64 ± 1.16%' | '1612.00' | '617453 ± 0.01%' | '620347' | 601815 | | 2 | 'measure (default)' | '1808.71 ± 1.28%' | '1753.00' | '566516 ± 0.01%' | '570451' | 552882 | | 3 | 'measure (with start and end timestamps)' | '1869.21 ± 1.00%' | '1823.00' | '546571 ± 0.01%' | '548546' | 534987 | | 4 | 'measure (with mark names)' | '2016.40 ± 0.74%' | '1983.00' | '502987 ± 0.01%' | '504286' | 496075 | | 5 | 'clearMarks' | '682.18 ± 0.03%' | '671.00' | '1476364 ± 0.01%' | '1490313' | 1465899 | | 6 | 'clearMeasures' | '686.78 ± 0.03%' | '681.00' | '1467264 ± 0.01%' | '1468429' | 1456081 | | 7 | 'mark + clearMarks' | '2148.90 ± 1.28%' | '2073.00' | '480925 ± 0.01%' | '482393' | 465356 | | 8 | 'measure + clearMeasures (with start and end timestamps)' | '2277.26 ± 1.10%' | '2204.00' | '451016 ± 0.01%' | '453721' | 439125 | | 9 | 'measure + clearMeasures (with mark names)' | '2277.82 ± 0.51%' | '2243.00' | '443853 ± 0.01%' | '445831' | 439016 | Reviewed By: hoxyq Differential Revision: D78193072 fbshipit-source-id: 03b52927a1999f19a2baf0d02335a959525d7add
This commit is contained in:
committed by
Facebook GitHub Bot
parent
1b7c9ea523
commit
4edc33e4e1
+17
-95
@@ -120,108 +120,30 @@ HighResTimeStamp NativePerformance::now(jsi::Runtime& /*rt*/) {
|
||||
return forcedCurrentTimeStamp_.value_or(HighResTimeStamp::now());
|
||||
}
|
||||
|
||||
HighResTimeStamp NativePerformance::markWithResult(
|
||||
void NativePerformance::reportMark(
|
||||
jsi::Runtime& rt,
|
||||
std::string name,
|
||||
std::optional<HighResTimeStamp> startTime) {
|
||||
auto entry = PerformanceEntryReporter::getInstance()->reportMark(
|
||||
name, startTime.value_or(now(rt)));
|
||||
return entry.startTime;
|
||||
HighResTimeStamp startTime,
|
||||
jsi::Value /*entry*/) {
|
||||
PerformanceEntryReporter::getInstance()->reportMark(name, startTime);
|
||||
}
|
||||
|
||||
std::tuple<HighResTimeStamp, HighResDuration> NativePerformance::measure(
|
||||
jsi::Runtime& runtime,
|
||||
std::string name,
|
||||
std::optional<HighResTimeStamp> startTime,
|
||||
std::optional<HighResTimeStamp> endTime,
|
||||
std::optional<HighResDuration> duration,
|
||||
std::optional<std::string> startMark,
|
||||
std::optional<std::string> endMark) {
|
||||
auto reporter = PerformanceEntryReporter::getInstance();
|
||||
|
||||
HighResTimeStamp startTimeValue;
|
||||
|
||||
// If the start time mark name is specified, it takes precedence over the
|
||||
// startTime parameter, which can be set to 0 by default from JavaScript.
|
||||
if (startTime) {
|
||||
startTimeValue = *startTime;
|
||||
} else if (startMark) {
|
||||
if (auto startMarkBufferedTime = reporter->getMarkTime(*startMark)) {
|
||||
startTimeValue = *startMarkBufferedTime;
|
||||
} else {
|
||||
throw jsi::JSError(
|
||||
runtime, "The mark '" + *startMark + "' does not exist.");
|
||||
}
|
||||
} else {
|
||||
startTimeValue = HighResTimeStamp::fromDOMHighResTimeStamp(0);
|
||||
}
|
||||
|
||||
HighResTimeStamp endTimeValue;
|
||||
|
||||
if (endTime) {
|
||||
endTimeValue = *endTime;
|
||||
} else if (duration) {
|
||||
endTimeValue = startTimeValue + *duration;
|
||||
} else if (endMark) {
|
||||
if (auto endMarkBufferedTime = reporter->getMarkTime(*endMark)) {
|
||||
endTimeValue = *endMarkBufferedTime;
|
||||
} else {
|
||||
throw jsi::JSError(
|
||||
runtime, "The mark '" + *endMark + "' does not exist.");
|
||||
}
|
||||
} else {
|
||||
// The end time is not specified, take the current time, according to the
|
||||
// standard
|
||||
endTimeValue = now(runtime);
|
||||
}
|
||||
|
||||
auto entry = reporter->reportMeasure(name, startTimeValue, endTimeValue);
|
||||
return std::tuple{entry.startTime, entry.duration};
|
||||
}
|
||||
|
||||
std::tuple<HighResTimeStamp, HighResDuration>
|
||||
NativePerformance::measureWithResult(
|
||||
jsi::Runtime& runtime,
|
||||
void NativePerformance::reportMeasure(
|
||||
jsi::Runtime& rt,
|
||||
std::string name,
|
||||
HighResTimeStamp startTime,
|
||||
HighResTimeStamp endTime,
|
||||
std::optional<HighResDuration> duration,
|
||||
std::optional<std::string> startMark,
|
||||
std::optional<std::string> endMark) {
|
||||
auto reporter = PerformanceEntryReporter::getInstance();
|
||||
HighResDuration duration,
|
||||
jsi::Value /*entry*/) {
|
||||
PerformanceEntryReporter::getInstance()->reportMeasure(
|
||||
name, startTime, duration);
|
||||
}
|
||||
|
||||
HighResTimeStamp startTimeValue = startTime;
|
||||
// If the start time mark name is specified, it takes precedence over the
|
||||
// startTime parameter, which can be set to 0 by default from JavaScript.
|
||||
if (startMark) {
|
||||
if (auto startMarkBufferedTime = reporter->getMarkTime(*startMark)) {
|
||||
startTimeValue = *startMarkBufferedTime;
|
||||
} else {
|
||||
throw jsi::JSError(
|
||||
runtime, "The mark '" + *startMark + "' does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
HighResTimeStamp endTimeValue = endTime;
|
||||
// If the end time mark name is specified, it takes precedence over the
|
||||
// startTime parameter, which can be set to 0 by default from JavaScript.
|
||||
if (endMark) {
|
||||
if (auto endMarkBufferedTime = reporter->getMarkTime(*endMark)) {
|
||||
endTimeValue = *endMarkBufferedTime;
|
||||
} else {
|
||||
throw jsi::JSError(
|
||||
runtime, "The mark '" + *endMark + "' does not exist.");
|
||||
}
|
||||
} else if (duration) {
|
||||
endTimeValue = startTimeValue + *duration;
|
||||
} else if (endTimeValue < startTimeValue) {
|
||||
// The end time is not specified, take the current time, according to the
|
||||
// standard
|
||||
endTimeValue = now(runtime);
|
||||
}
|
||||
|
||||
auto entry = reporter->reportMeasure(name, startTime, endTime);
|
||||
return std::tuple{entry.startTime, entry.duration};
|
||||
std::optional<double> NativePerformance::getMarkTime(
|
||||
jsi::Runtime& rt,
|
||||
std::string name) {
|
||||
auto markTime = PerformanceEntryReporter::getInstance()->getMarkTime(name);
|
||||
return markTime ? std::optional{(*markTime).toDOMHighResTimeStamp()}
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
void NativePerformance::clearMarks(
|
||||
|
||||
+8
-20
@@ -90,32 +90,20 @@ class NativePerformance : public NativePerformanceCxxSpec<NativePerformance> {
|
||||
|
||||
#pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/)
|
||||
|
||||
// https://w3c.github.io/user-timing/#mark-method
|
||||
HighResTimeStamp markWithResult(
|
||||
void reportMark(
|
||||
jsi::Runtime& rt,
|
||||
std::string name,
|
||||
std::optional<HighResTimeStamp> startTime);
|
||||
HighResTimeStamp time,
|
||||
jsi::Value entry);
|
||||
|
||||
// https://w3c.github.io/user-timing/#measure-method
|
||||
std::tuple<HighResTimeStamp, HighResDuration> measure(
|
||||
jsi::Runtime& rt,
|
||||
std::string name,
|
||||
std::optional<HighResTimeStamp> startTime,
|
||||
std::optional<HighResTimeStamp> endTime,
|
||||
std::optional<HighResDuration> duration,
|
||||
std::optional<std::string> startMark,
|
||||
std::optional<std::string> endMark);
|
||||
|
||||
// https://w3c.github.io/user-timing/#measure-method
|
||||
[[deprecated("This method is deprecated. Use the measure method instead.")]]
|
||||
std::tuple<HighResTimeStamp, HighResDuration> measureWithResult(
|
||||
void reportMeasure(
|
||||
jsi::Runtime& rt,
|
||||
std::string name,
|
||||
HighResTimeStamp startTime,
|
||||
HighResTimeStamp endTime,
|
||||
std::optional<HighResDuration> duration,
|
||||
std::optional<std::string> startMark,
|
||||
std::optional<std::string> endMark);
|
||||
HighResDuration duration,
|
||||
jsi::Value entry);
|
||||
|
||||
std::optional<double> getMarkTime(jsi::Runtime& rt, std::string name);
|
||||
|
||||
// https://w3c.github.io/user-timing/#clearmarks-method
|
||||
void clearMarks(
|
||||
|
||||
+6
-16
@@ -167,12 +167,10 @@ void PerformanceEntryReporter::clearEntries(
|
||||
getBufferRef(entryType).clear(entryName);
|
||||
}
|
||||
|
||||
PerformanceMark PerformanceEntryReporter::reportMark(
|
||||
void PerformanceEntryReporter::reportMark(
|
||||
const std::string& name,
|
||||
const std::optional<HighResTimeStamp>& startTime) {
|
||||
// Resolve timings
|
||||
auto startTimeVal = startTime ? *startTime : getCurrentTimeStamp();
|
||||
const auto entry = PerformanceMark{{.name = name, .startTime = startTimeVal}};
|
||||
const HighResTimeStamp startTime) {
|
||||
const auto entry = PerformanceMark{{.name = name, .startTime = startTime}};
|
||||
|
||||
traceMark(entry);
|
||||
|
||||
@@ -183,18 +181,14 @@ PerformanceMark PerformanceEntryReporter::reportMark(
|
||||
}
|
||||
|
||||
observerRegistry_->queuePerformanceEntry(entry);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
PerformanceMeasure PerformanceEntryReporter::reportMeasure(
|
||||
void PerformanceEntryReporter::reportMeasure(
|
||||
const std::string& name,
|
||||
HighResTimeStamp startTime,
|
||||
HighResTimeStamp endTime,
|
||||
HighResDuration duration,
|
||||
const std::optional<jsinspector_modern::DevToolsTrackEntryPayload>&
|
||||
trackMetadata) {
|
||||
HighResDuration duration = endTime - startTime;
|
||||
|
||||
const auto entry = PerformanceMeasure{
|
||||
{.name = std::string(name),
|
||||
.startTime = startTime,
|
||||
@@ -209,8 +203,6 @@ PerformanceMeasure PerformanceEntryReporter::reportMeasure(
|
||||
}
|
||||
|
||||
observerRegistry_->queuePerformanceEntry(entry);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::clearEventCounts() {
|
||||
@@ -275,7 +267,7 @@ void PerformanceEntryReporter::reportLongTask(
|
||||
observerRegistry_->queuePerformanceEntry(entry);
|
||||
}
|
||||
|
||||
PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming(
|
||||
void PerformanceEntryReporter::reportResourceTiming(
|
||||
const std::string& url,
|
||||
HighResTimeStamp fetchStart,
|
||||
HighResTimeStamp requestStart,
|
||||
@@ -302,8 +294,6 @@ PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming(
|
||||
}
|
||||
|
||||
observerRegistry_->queuePerformanceEntry(entry);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const {
|
||||
|
||||
+4
-6
@@ -86,14 +86,12 @@ class PerformanceEntryReporter {
|
||||
std::optional<HighResTimeStamp> getMarkTime(
|
||||
const std::string& markName) const;
|
||||
|
||||
PerformanceMark reportMark(
|
||||
const std::string& name,
|
||||
const std::optional<HighResTimeStamp>& startTime = std::nullopt);
|
||||
void reportMark(const std::string& name, HighResTimeStamp startTime);
|
||||
|
||||
PerformanceMeasure reportMeasure(
|
||||
void reportMeasure(
|
||||
const std::string& name,
|
||||
HighResTimeStamp startTime,
|
||||
HighResTimeStamp endTime,
|
||||
HighResDuration duration,
|
||||
const std::optional<jsinspector_modern::DevToolsTrackEntryPayload>&
|
||||
trackMetadata = std::nullopt);
|
||||
|
||||
@@ -107,7 +105,7 @@ class PerformanceEntryReporter {
|
||||
|
||||
void reportLongTask(HighResTimeStamp startTime, HighResDuration duration);
|
||||
|
||||
PerformanceResourceTiming reportResourceTiming(
|
||||
void reportResourceTiming(
|
||||
const std::string& url,
|
||||
HighResTimeStamp fetchStart,
|
||||
HighResTimeStamp requestStart,
|
||||
|
||||
+16
-28
@@ -129,20 +129,16 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) {
|
||||
"mark2", timeOrigin + HighResDuration::fromMilliseconds(2));
|
||||
|
||||
reporter->reportMeasure(
|
||||
"measure0",
|
||||
timeOrigin,
|
||||
timeOrigin + HighResDuration::fromMilliseconds(2));
|
||||
"measure0", timeOrigin, HighResDuration::fromMilliseconds(2));
|
||||
reporter->reportMeasure(
|
||||
"measure1",
|
||||
timeOrigin,
|
||||
timeOrigin + HighResDuration::fromMilliseconds(3));
|
||||
"measure1", timeOrigin, HighResDuration::fromMilliseconds(3));
|
||||
|
||||
reporter->reportMark(
|
||||
"mark3", timeOrigin + HighResDuration::fromNanoseconds(2.5 * 1e6));
|
||||
reporter->reportMeasure(
|
||||
"measure2",
|
||||
timeOrigin + HighResDuration::fromMilliseconds(2),
|
||||
timeOrigin + HighResDuration::fromMilliseconds(2));
|
||||
HighResDuration::fromMilliseconds(2));
|
||||
reporter->reportMark(
|
||||
"mark4", timeOrigin + HighResDuration::fromMilliseconds(3));
|
||||
|
||||
@@ -172,7 +168,7 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) {
|
||||
PerformanceMeasure{
|
||||
{.name = "measure2",
|
||||
.startTime = timeOrigin + HighResDuration::fromMilliseconds(2),
|
||||
.duration = HighResDuration::zero()}},
|
||||
.duration = HighResDuration::fromMilliseconds(2)}},
|
||||
PerformanceMark{
|
||||
{.name = "mark3",
|
||||
.startTime =
|
||||
@@ -203,21 +199,17 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) {
|
||||
"mark2", timeOrigin + HighResDuration::fromMilliseconds(2));
|
||||
|
||||
reporter->reportMeasure(
|
||||
"common_name",
|
||||
timeOrigin,
|
||||
timeOrigin + HighResDuration::fromMilliseconds(2));
|
||||
"common_name", timeOrigin, HighResDuration::fromMilliseconds(2));
|
||||
reporter->reportMeasure(
|
||||
"measure1",
|
||||
timeOrigin,
|
||||
timeOrigin + HighResDuration::fromMilliseconds(3));
|
||||
"measure1", timeOrigin, HighResDuration::fromMilliseconds(3));
|
||||
reporter->reportMeasure(
|
||||
"measure2",
|
||||
timeOrigin + HighResDuration::fromMilliseconds(1),
|
||||
timeOrigin + HighResDuration::fromMilliseconds(6));
|
||||
HighResDuration::fromMilliseconds(6));
|
||||
reporter->reportMeasure(
|
||||
"measure3",
|
||||
timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6),
|
||||
timeOrigin + HighResDuration::fromMilliseconds(2));
|
||||
HighResDuration::fromMilliseconds(2));
|
||||
|
||||
{
|
||||
const auto allEntries = toSorted(reporter->getEntries());
|
||||
@@ -241,12 +233,12 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) {
|
||||
PerformanceMeasure{
|
||||
{.name = "measure2",
|
||||
.startTime = timeOrigin + HighResDuration::fromMilliseconds(1),
|
||||
.duration = HighResDuration::fromMilliseconds(5)}},
|
||||
.duration = HighResDuration::fromMilliseconds(6)}},
|
||||
PerformanceMeasure{
|
||||
{.name = "measure3",
|
||||
.startTime =
|
||||
timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6),
|
||||
.duration = HighResDuration::fromNanoseconds(0.5 * 1e6)}},
|
||||
.duration = HighResDuration::fromMilliseconds(2)}},
|
||||
PerformanceMark{
|
||||
{.name = "mark2",
|
||||
.startTime = timeOrigin + HighResDuration::fromMilliseconds(2),
|
||||
@@ -288,12 +280,12 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) {
|
||||
PerformanceMeasure{
|
||||
{.name = "measure2",
|
||||
.startTime = timeOrigin + HighResDuration::fromMilliseconds(1),
|
||||
.duration = HighResDuration::fromMilliseconds(5)}},
|
||||
.duration = HighResDuration::fromMilliseconds(6)}},
|
||||
PerformanceMeasure{
|
||||
{.name = "measure3",
|
||||
.startTime =
|
||||
timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6),
|
||||
.duration = HighResDuration::fromNanoseconds(0.5 * 1e6)}}};
|
||||
.duration = HighResDuration::fromMilliseconds(2)}}};
|
||||
ASSERT_EQ(expected, measures);
|
||||
}
|
||||
|
||||
@@ -330,21 +322,17 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestClearMarks) {
|
||||
"mark2", timeOrigin + HighResDuration::fromMilliseconds(2));
|
||||
|
||||
reporter->reportMeasure(
|
||||
"common_name",
|
||||
timeOrigin,
|
||||
timeOrigin + HighResDuration::fromMilliseconds(2));
|
||||
"common_name", timeOrigin, HighResDuration::fromMilliseconds(2));
|
||||
reporter->reportMeasure(
|
||||
"measure1",
|
||||
timeOrigin,
|
||||
timeOrigin + HighResDuration::fromMilliseconds(3));
|
||||
"measure1", timeOrigin, HighResDuration::fromMilliseconds(3));
|
||||
reporter->reportMeasure(
|
||||
"measure2",
|
||||
timeOrigin + HighResDuration::fromMilliseconds(1),
|
||||
timeOrigin + HighResDuration::fromMilliseconds(6));
|
||||
HighResDuration::fromMilliseconds(6));
|
||||
reporter->reportMeasure(
|
||||
"measure3",
|
||||
timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6),
|
||||
timeOrigin + HighResDuration::fromMilliseconds(2));
|
||||
HighResDuration::fromMilliseconds(2));
|
||||
|
||||
reporter->clearEntries(PerformanceEntryType::MARK, "common_name");
|
||||
|
||||
|
||||
+2
-4
@@ -190,7 +190,7 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveTakeRecords) {
|
||||
reporter->reportMeasure(
|
||||
"off",
|
||||
timeOrigin + HighResDuration::fromMilliseconds(10),
|
||||
timeOrigin + HighResDuration::fromMilliseconds(20));
|
||||
HighResDuration::fromMilliseconds(20));
|
||||
reporter->reportMark(
|
||||
"test2", timeOrigin + HighResDuration::fromMilliseconds(20));
|
||||
reporter->reportMark(
|
||||
@@ -371,9 +371,7 @@ TEST(PerformanceObserver, PerformanceObserverTestMultiple) {
|
||||
{.durationThreshold = HighResDuration::fromMilliseconds(80)});
|
||||
|
||||
reporter->reportMeasure(
|
||||
"measure",
|
||||
timeOrigin,
|
||||
timeOrigin + HighResDuration::fromMilliseconds(50));
|
||||
"measure", timeOrigin, HighResDuration::fromMilliseconds(50));
|
||||
reporter->reportEvent(
|
||||
"event1",
|
||||
timeOrigin,
|
||||
|
||||
+156
-111
@@ -58,8 +58,9 @@ export type PerformanceMeasureOptions =
|
||||
const ENTRY_TYPES_AVAILABLE_FROM_TIMELINE: $ReadOnlyArray<PerformanceEntryType> =
|
||||
['mark', 'measure'];
|
||||
|
||||
const cachedNativeMark = NativePerformance?.markWithResult;
|
||||
const cachedNativeMeasure = NativePerformance?.measure;
|
||||
const cachedReportMark = NativePerformance?.reportMark;
|
||||
const cachedReportMeasure = NativePerformance?.reportMeasure;
|
||||
const cachedGetMarkTime = NativePerformance?.getMarkTime;
|
||||
const cachedNativeClearMarks = NativePerformance?.clearMarks;
|
||||
const cachedNativeClearMeasures = NativePerformance?.clearMeasures;
|
||||
|
||||
@@ -74,6 +75,19 @@ const MEASURE_OPTIONS_REUSABLE_OBJECT: {...PerformanceMeasureInit} = {
|
||||
detail: undefined,
|
||||
};
|
||||
|
||||
const getMarkTimeForMeasure = cachedGetMarkTime
|
||||
? (markName: string): number => {
|
||||
const markTime = cachedGetMarkTime(markName);
|
||||
if (markTime == null) {
|
||||
throw new DOMException(
|
||||
`Failed to execute 'measure' on 'Performance': The mark '${markName}' does not exist.`,
|
||||
'SyntaxError',
|
||||
);
|
||||
}
|
||||
return markTime;
|
||||
}
|
||||
: undefined;
|
||||
|
||||
/**
|
||||
* Partial implementation of the Performance interface for RN,
|
||||
* corresponding to the standard in
|
||||
@@ -141,6 +155,11 @@ export default class Performance {
|
||||
// Please run the benchmarks in `Performance-benchmarks-itest` to ensure
|
||||
// changes do not regress performance.
|
||||
|
||||
if (cachedReportMark === undefined) {
|
||||
warnNoNativePerformance();
|
||||
return new PerformanceMark(markName, {startTime: 0});
|
||||
}
|
||||
|
||||
if (markName === undefined) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'mark' on 'Performance': 1 argument required, but only 0 present.`,
|
||||
@@ -150,46 +169,51 @@ export default class Performance {
|
||||
const resolvedMarkName =
|
||||
typeof markName === 'string' ? markName : String(markName);
|
||||
|
||||
let resolvedStartTime;
|
||||
let resolvedDetail;
|
||||
const detail = markOptions?.detail;
|
||||
|
||||
let startTime;
|
||||
let detail;
|
||||
if (markOptions != null) {
|
||||
({startTime, detail} = markOptions);
|
||||
}
|
||||
|
||||
if (startTime !== undefined) {
|
||||
resolvedStartTime =
|
||||
typeof startTime === 'number' ? startTime : Number(startTime);
|
||||
if (resolvedStartTime < 0) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'mark' on 'Performance': '${resolvedMarkName}' cannot have a negative start time.`,
|
||||
);
|
||||
} else if (
|
||||
// This is faster than calling Number.isFinite()
|
||||
// eslint-disable-next-line no-self-compare
|
||||
resolvedStartTime !== resolvedStartTime ||
|
||||
resolvedStartTime === Infinity
|
||||
) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'mark' on 'Performance': Failed to read the 'startTime' property from 'PerformanceMarkOptions': The provided double value is non-finite.`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
resolvedStartTime = getCurrentTimeStamp();
|
||||
}
|
||||
|
||||
if (detail !== undefined) {
|
||||
resolvedDetail = structuredClone(detail);
|
||||
}
|
||||
|
||||
let computedStartTime;
|
||||
if (cachedNativeMark) {
|
||||
let resolvedStartTime;
|
||||
|
||||
const startTime = markOptions?.startTime;
|
||||
if (startTime !== undefined) {
|
||||
resolvedStartTime =
|
||||
typeof startTime === 'number' ? startTime : Number(startTime);
|
||||
if (resolvedStartTime < 0) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'mark' on 'Performance': '${resolvedMarkName}' cannot have a negative start time.`,
|
||||
);
|
||||
} else if (
|
||||
// This is faster than calling Number.isFinite()
|
||||
// eslint-disable-next-line no-self-compare
|
||||
resolvedStartTime !== resolvedStartTime ||
|
||||
resolvedStartTime === Infinity
|
||||
) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'mark' on 'Performance': Failed to read the 'startTime' property from 'PerformanceMarkOptions': The provided double value is non-finite.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
computedStartTime = cachedNativeMark(resolvedMarkName, resolvedStartTime);
|
||||
} else {
|
||||
warnNoNativePerformance();
|
||||
computedStartTime = getCurrentTimeStamp();
|
||||
}
|
||||
|
||||
MARK_OPTIONS_REUSABLE_OBJECT.startTime = computedStartTime;
|
||||
MARK_OPTIONS_REUSABLE_OBJECT.startTime = resolvedStartTime;
|
||||
MARK_OPTIONS_REUSABLE_OBJECT.detail = resolvedDetail;
|
||||
|
||||
return new PerformanceMark(resolvedMarkName, MARK_OPTIONS_REUSABLE_OBJECT);
|
||||
const entry = new PerformanceMark(
|
||||
resolvedMarkName,
|
||||
MARK_OPTIONS_REUSABLE_OBJECT,
|
||||
);
|
||||
|
||||
cachedReportMark(resolvedMarkName, resolvedStartTime, entry);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
clearMarks(markName?: string): void {
|
||||
@@ -210,20 +234,27 @@ export default class Performance {
|
||||
// Please run the benchmarks in `Performance-benchmarks-itest` to ensure
|
||||
// changes do not regress performance.
|
||||
|
||||
if (
|
||||
getMarkTimeForMeasure === undefined ||
|
||||
cachedReportMeasure === undefined
|
||||
) {
|
||||
warnNoNativePerformance();
|
||||
return new PerformanceMeasure(measureName, {startTime: 0, duration: 0});
|
||||
}
|
||||
|
||||
let resolvedMeasureName: string;
|
||||
let resolvedStartTime: number;
|
||||
let resolvedDuration: number;
|
||||
let resolvedDetail: mixed;
|
||||
|
||||
if (measureName === undefined) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 present.`,
|
||||
);
|
||||
}
|
||||
|
||||
let resolvedMeasureName =
|
||||
typeof measureName === 'string' ? measureName : String(measureName);
|
||||
let resolvedStartTime: number | void;
|
||||
let resolvedStartMark: string | void;
|
||||
let resolvedEndTime: number | void;
|
||||
let resolvedEndMark: string | void;
|
||||
let resolvedDuration: number | void;
|
||||
let resolvedDetail: mixed;
|
||||
resolvedMeasureName =
|
||||
measureName === 'string' ? measureName : String(measureName);
|
||||
|
||||
if (startMarkOrOptions != null) {
|
||||
switch (typeof startMarkOrOptions) {
|
||||
@@ -234,50 +265,65 @@ export default class Performance {
|
||||
);
|
||||
}
|
||||
|
||||
const start = startMarkOrOptions.start;
|
||||
const {start, end, duration, detail} = startMarkOrOptions;
|
||||
|
||||
let resolvedEndTime;
|
||||
|
||||
if (
|
||||
start !== undefined &&
|
||||
end !== undefined &&
|
||||
duration !== undefined
|
||||
) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'measure' on 'Performance': If a non-empty PerformanceMeasureOptions object was passed, it must not have all of its 'start', 'duration', and 'end' properties defined`,
|
||||
);
|
||||
}
|
||||
|
||||
switch (typeof start) {
|
||||
case 'undefined': {
|
||||
// This will be handled after all options have been processed.
|
||||
break;
|
||||
}
|
||||
case 'number': {
|
||||
resolvedStartTime = start;
|
||||
break;
|
||||
}
|
||||
case 'string': {
|
||||
resolvedStartMark = start;
|
||||
break;
|
||||
}
|
||||
case 'undefined': {
|
||||
resolvedStartTime = getMarkTimeForMeasure(start);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
resolvedStartMark = String(start);
|
||||
resolvedStartTime = getMarkTimeForMeasure(String(start));
|
||||
}
|
||||
}
|
||||
|
||||
const end = startMarkOrOptions.end;
|
||||
switch (typeof end) {
|
||||
case 'undefined': {
|
||||
// This will be handled after all options have been processed.
|
||||
break;
|
||||
}
|
||||
case 'number': {
|
||||
resolvedEndTime = end;
|
||||
break;
|
||||
}
|
||||
case 'string': {
|
||||
resolvedEndMark = end;
|
||||
break;
|
||||
}
|
||||
case 'undefined': {
|
||||
resolvedEndTime = getMarkTimeForMeasure(end);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
resolvedEndMark = String(end);
|
||||
resolvedEndTime = getMarkTimeForMeasure(String(end));
|
||||
}
|
||||
}
|
||||
|
||||
const duration = startMarkOrOptions.duration;
|
||||
switch (typeof duration) {
|
||||
case 'undefined': {
|
||||
// This will be handled after all options have been processed.
|
||||
break;
|
||||
}
|
||||
case 'number': {
|
||||
resolvedDuration = duration;
|
||||
break;
|
||||
}
|
||||
case 'undefined':
|
||||
break;
|
||||
default: {
|
||||
resolvedDuration = Number(duration);
|
||||
if (!Number.isFinite(resolvedDuration)) {
|
||||
@@ -288,16 +334,28 @@ export default class Performance {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
resolvedDuration !== undefined &&
|
||||
(resolvedEndMark !== undefined || resolvedEndTime !== undefined)
|
||||
) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'measure' on 'Performance': If a non-empty PerformanceMeasureOptions object was passed, it must not have all of its 'start', 'duration', and 'end' properties defined`,
|
||||
);
|
||||
if (resolvedStartTime === undefined) {
|
||||
if (
|
||||
resolvedEndTime !== undefined &&
|
||||
resolvedDuration !== undefined
|
||||
) {
|
||||
resolvedStartTime = resolvedEndTime - resolvedDuration;
|
||||
} else {
|
||||
resolvedStartTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (resolvedDuration === undefined) {
|
||||
if (
|
||||
resolvedStartTime !== undefined &&
|
||||
resolvedEndTime !== undefined
|
||||
) {
|
||||
resolvedDuration = resolvedEndTime - resolvedStartTime;
|
||||
} else {
|
||||
resolvedDuration = getCurrentTimeStamp() - resolvedStartTime;
|
||||
}
|
||||
}
|
||||
|
||||
const detail = startMarkOrOptions.detail;
|
||||
if (detail !== undefined) {
|
||||
resolvedDetail = structuredClone(detail);
|
||||
}
|
||||
@@ -305,67 +363,54 @@ export default class Performance {
|
||||
break;
|
||||
}
|
||||
case 'string': {
|
||||
resolvedStartMark = startMarkOrOptions;
|
||||
resolvedStartTime = getMarkTimeForMeasure(startMarkOrOptions);
|
||||
|
||||
if (endMark !== undefined) {
|
||||
resolvedEndMark = String(endMark);
|
||||
resolvedDuration =
|
||||
getMarkTimeForMeasure(endMark) - resolvedStartTime;
|
||||
} else {
|
||||
resolvedDuration = getCurrentTimeStamp() - resolvedStartTime;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
resolvedStartMark = String(startMarkOrOptions);
|
||||
resolvedStartTime = getMarkTimeForMeasure(String(startMarkOrOptions));
|
||||
|
||||
if (endMark !== undefined) {
|
||||
resolvedDuration =
|
||||
getMarkTimeForMeasure(endMark) - resolvedStartTime;
|
||||
} else {
|
||||
resolvedDuration = getCurrentTimeStamp() - resolvedStartTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let computedStartTime = 0;
|
||||
let computedDuration = 0;
|
||||
|
||||
if (cachedNativeMeasure) {
|
||||
try {
|
||||
[computedStartTime, computedDuration] = cachedNativeMeasure(
|
||||
resolvedMeasureName,
|
||||
resolvedStartTime,
|
||||
resolvedEndTime,
|
||||
resolvedDuration,
|
||||
resolvedStartMark,
|
||||
resolvedEndMark,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new DOMException(
|
||||
"Failed to execute 'measure' on 'Performance': " + error.message,
|
||||
'SyntaxError',
|
||||
);
|
||||
}
|
||||
} else if (NativePerformance?.measureWithResult) {
|
||||
try {
|
||||
[computedStartTime, computedDuration] =
|
||||
NativePerformance.measureWithResult(
|
||||
resolvedMeasureName,
|
||||
resolvedStartTime ?? 0,
|
||||
resolvedEndTime ?? 0,
|
||||
resolvedDuration,
|
||||
resolvedStartMark,
|
||||
resolvedEndMark,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new DOMException(
|
||||
"Failed to execute 'measure' on 'Performance': " + error.message,
|
||||
'SyntaxError',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
warnNoNativePerformance();
|
||||
resolvedStartTime = 0;
|
||||
|
||||
if (endMark !== undefined) {
|
||||
resolvedDuration = getMarkTimeForMeasure(endMark) - resolvedStartTime;
|
||||
} else {
|
||||
resolvedDuration = getCurrentTimeStamp() - resolvedStartTime;
|
||||
}
|
||||
}
|
||||
|
||||
MEASURE_OPTIONS_REUSABLE_OBJECT.startTime = computedStartTime;
|
||||
MEASURE_OPTIONS_REUSABLE_OBJECT.duration = computedDuration ?? 0;
|
||||
MEASURE_OPTIONS_REUSABLE_OBJECT.startTime = resolvedStartTime;
|
||||
MEASURE_OPTIONS_REUSABLE_OBJECT.duration = resolvedDuration;
|
||||
MEASURE_OPTIONS_REUSABLE_OBJECT.detail = resolvedDetail;
|
||||
|
||||
return new PerformanceMeasure(
|
||||
const entry = new PerformanceMeasure(
|
||||
resolvedMeasureName,
|
||||
MEASURE_OPTIONS_REUSABLE_OBJECT,
|
||||
);
|
||||
|
||||
cachedReportMeasure(
|
||||
resolvedMeasureName,
|
||||
resolvedStartTime,
|
||||
resolvedDuration,
|
||||
entry,
|
||||
);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
clearMeasures(measureName?: string): void {
|
||||
|
||||
+2
-6
@@ -293,9 +293,7 @@ describe('User Timing', () => {
|
||||
expect(measure.detail).toBe(null);
|
||||
});
|
||||
|
||||
// TODO fix case
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('works with an end timestamp and a duration', () => {
|
||||
it('works with an end timestamp and a duration', () => {
|
||||
const measure = performance.measure(
|
||||
'measure-with-end-timestamp-and-duration',
|
||||
{
|
||||
@@ -312,9 +310,7 @@ describe('User Timing', () => {
|
||||
expect(measure.detail).toBe(null);
|
||||
});
|
||||
|
||||
// TODO fix case
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('works with an end mark and a duration', () => {
|
||||
it('works with an end mark and a duration', () => {
|
||||
performance.mark('end-mark', {
|
||||
startTime: 40,
|
||||
});
|
||||
|
||||
+6
-19
@@ -54,27 +54,14 @@ export type PerformanceObserverInit = {
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
+now?: () => number;
|
||||
+markWithResult?: (
|
||||
name: string,
|
||||
startTime?: number,
|
||||
) => NativePerformanceMarkResult;
|
||||
+measure?: (
|
||||
name: string,
|
||||
startTime?: number,
|
||||
endTime?: number,
|
||||
duration?: number,
|
||||
startMark?: string,
|
||||
endMark?: string,
|
||||
) => NativePerformanceMeasureResult;
|
||||
// DEPRECATED: Use measure instead.
|
||||
+measureWithResult?: (
|
||||
+reportMark?: (name: string, startTime: number, entry: mixed) => void;
|
||||
+reportMeasure?: (
|
||||
name: string,
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
duration?: number,
|
||||
startMark?: string,
|
||||
endMark?: string,
|
||||
) => NativePerformanceMeasureResult;
|
||||
duration: number,
|
||||
entry: mixed,
|
||||
) => void;
|
||||
+getMarkTime?: (name: string) => ?number;
|
||||
+clearMarks?: (entryName?: string) => void;
|
||||
+clearMeasures?: (entryName?: string) => void;
|
||||
+getEntries?: () => $ReadOnlyArray<RawPerformanceEntry>;
|
||||
|
||||
Reference in New Issue
Block a user