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:
Rubén Norte
2025-07-16 02:48:43 -07:00
committed by Facebook GitHub Bot
parent 1b7c9ea523
commit 4edc33e4e1
9 changed files with 217 additions and 305 deletions
@@ -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(
@@ -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(
@@ -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 {
@@ -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,
@@ -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");
@@ -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,
@@ -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 {
@@ -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,
});
@@ -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>;