Skip to content

Commit bb860b8

Browse files
Fix Perfetto JS event timestamps on Apple platforms (#57095)
Summary: `highResTimeStampToPerfettoTraceTime` returned the raw `HighResTimeStamp` nanoseconds as the Perfetto trace timestamp. That is only correct on Android, where Perfetto's clock and the `std::chrono::steady_clock` backing `HighResTimeStamp` happen to share an epoch. On Apple platforms the assumption is false: `HighResTimeStamp` is backed by `mach_absolute_time` (`CLOCK_UPTIME_RAW`, which excludes time the device spent asleep) while Perfetto stamps native events with `CLOCK_MONOTONIC` (which includes time asleep). The two clocks have different epochs, so JS/Web-Performance events were placed far off the native timeline, producing a nonsensical trace bounds span. This converts JS timestamps into Perfetto's clock domain on Apple by capturing the offset between `CLOCK_MONOTONIC` and the steady clock once and applying it. Slice durations were already correct (both endpoints live in the same domain); only the absolute placement of JS events was wrong, and that is what this fixes. The captured offset is exact while the device is awake and can drift across sleep, which is negligible for short cold-start traces; a fully sleep-proof approach would register a custom Perfetto clock with a `ClockSnapshot` and let `trace_processor` reconcile. Changelog: [Internal] Differential Revision: D107715256
1 parent 2ff3b81 commit bb860b8

1 file changed

Lines changed: 35 additions & 3 deletions

File tree

packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@
1010
#include "ReactPerfetto.h"
1111

1212
#include <perfetto.h>
13+
#include <chrono>
1314
#include <unordered_map>
1415

16+
#if defined(__APPLE__)
17+
#include <ctime>
18+
#endif
19+
1520
#include "FuseboxPerfettoDataSource.h"
1621
#include "HermesPerfettoDataSource.h"
1722
#include "ReactPerfettoCategories.h"
@@ -79,16 +84,43 @@ perfetto::Track getPerfettoWebPerfTrackAsync(const std::string& trackName) {
7984
return createTrack(trackName);
8085
}
8186

82-
// Perfetto's monotonic clock seems to match the std::chrono::steady_clock we
83-
// use in HighResTimeStamp on Android platforms, but if that
84-
// assumption is incorrect we may need to manually offset perfetto timestamps.
87+
// Perfetto's monotonic clock matches the std::chrono::steady_clock we use in
88+
// HighResTimeStamp on Android platforms, so no adjustment is needed there. On
89+
// Apple platforms that assumption is false: HighResTimeStamp is backed by
90+
// mach_absolute_time (CLOCK_UPTIME_RAW, which excludes time the device spent
91+
// asleep) while Perfetto stamps native events with CLOCK_MONOTONIC (which
92+
// includes time asleep). The two clocks have different epochs, so without
93+
// adjustment JS web-performance events land far off the native timeline. Apply
94+
// the offset between the clocks to bring JS events into Perfetto's trace clock
95+
// domain.
8596
uint64_t highResTimeStampToPerfettoTraceTime(HighResTimeStamp timestamp) {
8697
auto chronoDurationSinceSteadyClockEpoch =
8798
timestamp.toChronoSteadyClockTimePoint().time_since_epoch();
8899
auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(
89100
chronoDurationSinceSteadyClockEpoch);
90101

102+
#if defined(__APPLE__)
103+
// Capture the offset between CLOCK_MONOTONIC and the steady_clock
104+
// (CLOCK_UPTIME_RAW) backing HighResTimeStamp once. This is exact while the
105+
// device stays awake; it can drift across sleep because CLOCK_MONOTONIC keeps
106+
// advancing while mach time does not. For short cold-start traces that drift
107+
// is negligible.
108+
static const int64_t offsetNanos = []() {
109+
timespec ts{};
110+
clock_gettime(CLOCK_MONOTONIC, &ts);
111+
int64_t monotonicNanos =
112+
static_cast<int64_t>(ts.tv_sec) * 1'000'000'000LL + ts.tv_nsec;
113+
int64_t steadyNanos =
114+
std::chrono::duration_cast<std::chrono::nanoseconds>(
115+
std::chrono::steady_clock::now().time_since_epoch())
116+
.count();
117+
return monotonicNanos - steadyNanos;
118+
}();
119+
120+
return static_cast<uint64_t>(nanoseconds.count() + offsetNanos);
121+
#else
91122
return static_cast<uint64_t>(nanoseconds.count());
123+
#endif
92124
}
93125

94126
} // namespace facebook::react

0 commit comments

Comments
 (0)