Skip to content

Commit

Permalink
Fix broken overlapping measures in Perfetto integration (#45567)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #45567

Changelog: [internal]

(internal because our integration for Perfetto hasn't been released in OSS yet)

In our current React integration for Perfetto we're logging arbitrary time spans via `performance.measure` in specific tracks (that can be custom based on a naming scheme).

For a given track, Perfetto doesn't allow partially overlapping segments (as it's considered to always be a stack of time spans). When logging arbitrary time spans that partially overlap, Perfetto cuts the nested ones to make sure they fit into their suspected parent. This makes the logged data incorrect and makes it hard to understand the performance of an application using this data.

There's a fix for this problem: logging these arbitrary segments/time spans in separate tracks that only share the name. In this case, Perfetto groups the data in the UI but allows overlapping (as they're not really on the same track).

Reviewed By: sammy-SC

Differential Revision: D60010696

fbshipit-source-id: 378ea492c4fafbe55ef97fa91e4fa50bbc1893ae
  • Loading branch information
rubennorte authored and facebook-github-bot committed Jul 22, 2024
1 parent df9b2ce commit 43c32e1
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const std::string TRACK_PREFIX = "Track:";
const std::string DEFAULT_TRACK_NAME = "Web Performance";
const std::string CUSTOM_TRACK_NAME_PREFIX = "Web Performance: ";

std::tuple<perfetto::Track, std::string_view> parsePerfettoTrack(
std::tuple<std::string, std::string_view> parsePerfettoTrack(
const std::string& name) {
// Until there's a standard way to pass through track information, parse it
// manually, e.g., "Track:Foo:Event name"
Expand All @@ -58,7 +58,7 @@ std::tuple<perfetto::Track, std::string_view> parsePerfettoTrack(
}

auto& trackNameRef = trackName.has_value() ? *trackName : DEFAULT_TRACK_NAME;
return std::make_tuple(getPerfettoWebPerfTrack(trackNameRef), eventName);
return std::make_tuple(trackNameRef, eventName);
}

#endif
Expand All @@ -82,11 +82,11 @@ void NativePerformance::mark(
double startTime) {
#ifdef WITH_PERFETTO
if (TRACE_EVENT_CATEGORY_ENABLED("react-native")) {
auto [track, eventName] = parsePerfettoTrack(name);
auto [trackName, eventName] = parsePerfettoTrack(name);
TRACE_EVENT_INSTANT(
"react-native",
perfetto::DynamicString(eventName.data(), eventName.size()),
track,
getPerfettoWebPerfTrackSync(trackName),
performanceNowToPerfettoTraceTime(startTime));
}
#endif
Expand All @@ -105,7 +105,8 @@ void NativePerformance::measure(
if (TRACE_EVENT_CATEGORY_ENABLED("react-native")) {
// TODO T190600850 support startMark/endMark
if (!startMark && !endMark) {
auto [track, eventName] = parsePerfettoTrack(name);
auto [trackName, eventName] = parsePerfettoTrack(name);
auto track = getPerfettoWebPerfTrackAsync(trackName);
TRACE_EVENT_BEGIN(
"react-native",
perfetto::DynamicString(eventName.data(), eventName.size()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ void flushSample(
const std::vector<folly::dynamic>& stack,
uint64_t start,
uint64_t end) {
auto track = getPerfettoWebPerfTrack("JS Sampling");
auto track = getPerfettoWebPerfTrackSync("JS Sampling");
size_t i = 0;
for (const auto& frame : stack) {
if (++i >= 50) {
Expand Down Expand Up @@ -103,7 +103,7 @@ void HermesPerfettoDataSource::OnStart(const StartArgs&) {
TRACE_EVENT_INSTANT(
"react-native",
perfetto::DynamicString{"Profiling Started"},
getPerfettoWebPerfTrack("JS Sampling"),
getPerfettoWebPerfTrackSync("JS Sampling"),
performanceNowToPerfettoTraceTime(0));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,49 @@ void initializePerfetto() {
HermesPerfettoDataSource::RegisterDataSource();
}

perfetto::Track getPerfettoWebPerfTrack(const std::string& trackName) {
static std::unordered_map<std::string, perfetto::Track> tracks;
static perfetto::Track createTrack(const std::string& trackName) {
// Offset for custom perfetto tracks
static uint64_t trackId = 0x5F3759DF;
auto track = perfetto::Track(trackId++);
auto desc = track.Serialize();
desc.set_name(trackName);
perfetto::TrackEvent::SetTrackDescriptor(track, desc);
return track;
}

perfetto::Track getPerfettoWebPerfTrackSync(const std::string& trackName) {
// In the case of marks we can reuse the same track saving some resources,
// because there's no risk of partial overlap that would break the timings.
static std::unordered_map<std::string, perfetto::Track> tracks;

auto it = tracks.find(trackName);
if (it == tracks.end()) {
auto track = perfetto::Track(trackId++);
auto desc = track.Serialize();
desc.set_name(trackName);
perfetto::TrackEvent::SetTrackDescriptor(track, desc);
auto track = createTrack(trackName);
tracks.emplace(trackName, track);
return track;
} else {
return it->second;
}
}

perfetto::Track getPerfettoWebPerfTrackAsync(const std::string& trackName) {
// Note that, in the case of measures, we don't cache and reuse a track for a
// given name because Perfetto does not support partially overlapping measures
// in the same track.
//
// E.g.:
// [.....]
// [......]
// In that case, Perfetto would just cut subsequent measures as:
// [.....]
// [..] <-- Part of this section is gone, so the timing is incorrect.
//
// There's a solution though. Perfetto does group different tracks with the
// same name together, so having a separate track for each async event allows
// overlap.
return createTrack(trackName);
}

// Perfetto's monotonic clock seems to match the std::chrono::steady_clock we
// use in JSExecutor::performanceNow on Android platforms, but if that
// assumption is incorrect we may need to manually offset perfetto timestamps.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

void initializePerfetto();

perfetto::Track getPerfettoWebPerfTrack(const std::string& trackName);
perfetto::Track getPerfettoWebPerfTrackSync(const std::string& trackName);
perfetto::Track getPerfettoWebPerfTrackAsync(const std::string& trackName);

uint64_t performanceNowToPerfettoTraceTime(double perfNowTime);

Expand Down

0 comments on commit 43c32e1

Please sign in to comment.