Skip to content

Commit 81dd30a

Browse files
Sai Ganesh Muthuramancopybara-github
authored andcommitted
Add flow event visualization to Trace Viewer v2.
This change adds support for visualizing flow events. Flow events with the same ID are connected via cubic Bezier curves, allowing users to trace execution paths across different threads or processes. The parser is updated to handle flow event phases ('s', 't', 'f') and metadata, and new rendering logic is added to draw the connecting lines. Support for filtering flows by category and highlighting flows connected to a selected event is also included. PiperOrigin-RevId: 854049741
1 parent bb565c5 commit 81dd30a

File tree

11 files changed

+1454
-98
lines changed

11 files changed

+1454
-98
lines changed

frontend/app/components/trace_viewer_v2/application.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ class Application {
4343
};
4444
DataProvider& data_provider() { return data_provider_; };
4545

46+
void SetVisibleFlowCategory(int category_id) {
47+
timeline_->SetVisibleFlowCategory(category_id);
48+
}
49+
4650
private:
4751
friend class absl::NoDestructor<Application>;
4852

frontend/app/components/trace_viewer_v2/timeline/BUILD

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ cc_library(
4848
"//third_party/dear_imgui",
4949
"@com_google_absl//absl/algorithm:container",
5050
"@com_google_absl//absl/base:nullability",
51+
"@com_google_absl//absl/container:flat_hash_map",
5152
"@com_google_absl//absl/functional:any_invocable",
5253
"@com_google_absl//absl/log",
5354
"@com_google_absl//absl/strings",
@@ -58,6 +59,7 @@ cc_library(
5859
"@org_xprof//frontend/app/components/trace_viewer_v2/fonts",
5960
"@org_xprof//frontend/app/components/trace_viewer_v2/helper:time_formatter",
6061
"@org_xprof//frontend/app/components/trace_viewer_v2/trace_helper:trace_event",
62+
"@tsl//tsl/profiler/lib:context_types_hdrs",
6163
],
6264
)
6365

@@ -74,6 +76,7 @@ cc_test(
7476
"@org_xprof//frontend/app/components/trace_viewer_v2:animation",
7577
"@org_xprof//frontend/app/components/trace_viewer_v2:event_data",
7678
"@org_xprof//frontend/app/components/trace_viewer_v2/helper:time_formatter",
79+
"@tsl//tsl/profiler/lib:context_types_hdrs",
7780
],
7881
)
7982

@@ -86,12 +89,15 @@ cc_library(
8689
deps = [
8790
":time_range",
8891
":timeline",
92+
"//third_party/dear_imgui",
8993
"//util/gtl:comparator",
9094
"@com_google_absl//absl/algorithm:container",
9195
"@com_google_absl//absl/container:btree",
9296
"@com_google_absl//absl/container:flat_hash_map",
97+
"@com_google_absl//absl/container:flat_hash_set",
9398
"@com_google_absl//absl/strings",
9499
"@com_google_absl//absl/types:span",
100+
"@org_xprof//frontend/app/components/trace_viewer_v2/color:colors",
95101
"@org_xprof//frontend/app/components/trace_viewer_v2/helper:time_formatter",
96102
"@org_xprof//frontend/app/components/trace_viewer_v2/trace_helper:trace_event",
97103
"@org_xprof//frontend/app/components/trace_viewer_v2/trace_helper:trace_event_tree",
@@ -105,7 +111,9 @@ cc_test(
105111
":data_provider",
106112
":time_range",
107113
":timeline",
114+
"@com_google_absl//absl/algorithm:container",
108115
"@com_google_googletest//:gtest_main",
109116
"@org_xprof//frontend/app/components/trace_viewer_v2/trace_helper:trace_event",
117+
"@tsl//tsl/profiler/lib:context_types_hdrs",
110118
],
111119
)

frontend/app/components/trace_viewer_v2/timeline/data_provider.cc

Lines changed: 137 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#include <algorithm>
44
#include <cstddef>
5-
#include <cstdint>
5+
#include <iterator>
66
#include <limits>
77
#include <memory>
88
#include <string>
@@ -12,10 +12,13 @@
1212
#include "absl/algorithm/container.h"
1313
#include "absl/container/btree_map.h"
1414
#include "absl/container/flat_hash_map.h"
15+
#include "absl/container/flat_hash_set.h"
1516
#include "absl/strings/numbers.h"
1617
#include "absl/strings/str_cat.h"
1718
#include "absl/strings/string_view.h"
1819
#include "absl/types/span.h"
20+
#include "third_party/dear_imgui/imgui.h"
21+
#include "xprof/frontend/app/components/trace_viewer_v2/color/color_generator.h"
1922
#include "xprof/frontend/app/components/trace_viewer_v2/helper/time_formatter.h"
2023
#include "xprof/frontend/app/components/trace_viewer_v2/timeline/time_range.h"
2124
#include "xprof/frontend/app/components/trace_viewer_v2/timeline/timeline.h"
@@ -44,6 +47,8 @@ struct TraceInformation {
4447
absl::btree_map<std::pair<ProcessId, ThreadId>, std::string> thread_names;
4548
absl::btree_map<ProcessId, std::string> process_names;
4649
absl::flat_hash_map<ProcessId, uint32_t> process_sort_indices;
50+
absl::btree_map<std::string, std::vector<const TraceEvent*>>
51+
flow_events_by_id;
4752
};
4853

4954
std::string GetDefaultThreadName(ThreadId tid) {
@@ -119,6 +124,14 @@ void HandleCompleteEvent(const TraceEvent& event,
119124
trace_info.events_by_pid_tid[event.pid][event.tid].push_back(&event);
120125
}
121126

127+
void HandleFlowEvent(const TraceEvent& event, TraceInformation& trace_info,
128+
absl::flat_hash_set<int>& present_categories) {
129+
if (!event.id.empty()) {
130+
trace_info.flow_events_by_id[event.id].push_back(&event);
131+
present_categories.insert(static_cast<int>(event.category));
132+
}
133+
}
134+
122135
// Handles a counter event ('ph' == 'C'). These events represent a counter value
123136
// at a specific timestamp. The function groups events by process ID and counter
124137
// name.
@@ -151,6 +164,81 @@ struct TimeBounds {
151164
Microseconds max = std::numeric_limits<Microseconds>::min();
152165
};
153166

167+
struct ThreadLevelInfo {
168+
int start_level;
169+
int end_level;
170+
};
171+
172+
// Returns the flame chart level of the given event.
173+
int GetEventFlameChartLevel(
174+
const TraceEvent* e,
175+
const absl::btree_map<std::pair<ProcessId, ThreadId>, ThreadLevelInfo>&
176+
thread_levels,
177+
const FlameChartTimelineData& data) {
178+
auto it = thread_levels.find({e->pid, e->tid});
179+
if (it == thread_levels.end()) return 0;
180+
int start = it->second.start_level;
181+
int end = it->second.end_level;
182+
183+
// Search from deepest level up
184+
for (int lvl = end - 1; lvl >= start; --lvl) {
185+
const auto& indices = data.events_by_level[lvl];
186+
// Binary search for event covering e->ts
187+
// events are likely sorted by start time.
188+
auto it_idx = std::upper_bound(indices.begin(), indices.end(), e->ts,
189+
[&](Microseconds ts, int idx) {
190+
return ts < data.entry_start_times[idx];
191+
});
192+
193+
// it_idx points to first event starting AFTER e->ts.
194+
// Check the one before it.
195+
if (it_idx != indices.begin()) {
196+
int idx = *std::prev(it_idx);
197+
if (data.entry_start_times[idx] + data.entry_total_times[idx] >= e->ts) {
198+
return lvl;
199+
}
200+
}
201+
}
202+
return start; // Default to thread top
203+
}
204+
205+
void GenerateFlowLines(const TraceInformation& trace_info,
206+
const absl::btree_map<std::pair<ProcessId, ThreadId>,
207+
ThreadLevelInfo>& thread_levels,
208+
FlameChartTimelineData& data, TimeBounds& bounds) {
209+
for (const auto& [id, flow_events] : trace_info.flow_events_by_id) {
210+
const ImU32 flow_color =
211+
GetColorForId(id); // Use the flow ID to generate a consistent color
212+
213+
for (const TraceEvent* event : flow_events) {
214+
std::vector<std::string>& event_flow_ids =
215+
data.flow_ids_by_event_id[event->event_id];
216+
if (event_flow_ids.empty() || event_flow_ids.back() != id) {
217+
event_flow_ids.push_back(id);
218+
}
219+
}
220+
221+
for (size_t i = 0; i < flow_events.size() - 1; ++i) {
222+
const TraceEvent* u = flow_events[i];
223+
const TraceEvent* v = flow_events[i + 1];
224+
225+
FlowLine flow_line{
226+
.source_ts = u->ts,
227+
.target_ts = v->ts,
228+
.source_level = GetEventFlameChartLevel(u, thread_levels, data),
229+
.target_level = GetEventFlameChartLevel(v, thread_levels, data),
230+
.color = flow_color,
231+
.category = u->category};
232+
data.flow_lines.push_back(flow_line);
233+
data.flow_lines_by_flow_id[id].push_back(flow_line);
234+
bounds.min = std::min(bounds.min, u->ts);
235+
bounds.max = std::max(bounds.max, u->ts);
236+
bounds.min = std::min(bounds.min, v->ts);
237+
bounds.max = std::max(bounds.max, v->ts);
238+
}
239+
}
240+
}
241+
154242
// Appends the given nodes (an array of trees) to the data, starting at the
155243
// given level. Returns the maximum level of the nodes.
156244
int AppendNodesAtLevel(absl::Span<const std::unique_ptr<TraceEventNode>> nodes,
@@ -165,6 +253,7 @@ int AppendNodesAtLevel(absl::Span<const std::unique_ptr<TraceEventNode>> nodes,
165253
data.entry_total_times.push_back(event->dur);
166254
data.entry_levels.push_back(current_level);
167255
data.entry_names.push_back(event->name);
256+
data.entry_event_ids.push_back(event->event_id);
168257

169258
bounds.min = std::min(bounds.min, event->ts);
170259
bounds.max = std::max(bounds.max, event->ts + event->dur);
@@ -182,22 +271,28 @@ int AppendNodesAtLevel(absl::Span<const std::unique_ptr<TraceEventNode>> nodes,
182271
void PopulateThreadTrack(ProcessId pid, ThreadId tid,
183272
absl::Span<const TraceEvent* const> events,
184273
const TraceInformation& trace_info, int& current_level,
185-
FlameChartTimelineData& data, TimeBounds& bounds) {
274+
FlameChartTimelineData& data, TimeBounds& bounds,
275+
absl::btree_map<std::pair<ProcessId, ThreadId>,
276+
ThreadLevelInfo>& thread_levels) {
186277
const auto it = trace_info.thread_names.find({pid, tid});
187278
const std::string thread_group_name = it == trace_info.thread_names.end()
188279
? GetDefaultThreadName(tid)
189280
: it->second;
281+
190282
data.groups.push_back({.name = thread_group_name,
191283
.start_level = current_level,
192284
.nesting_level = kThreadNestingLevel});
193285

286+
int start_level = current_level;
287+
194288
TraceEventTree event_tree = BuildTree(events);
195289

196290
// Get the maximum level index used by events in this thread.
197291
int max_level =
198292
AppendNodesAtLevel(event_tree.roots, current_level, data, bounds);
199293

200294
current_level = max_level + 1;
295+
thread_levels[{pid, tid}] = {start_level, current_level};
201296
}
202297

203298
void PopulateCounterTrack(ProcessId pid, const std::string& name,
@@ -256,7 +351,9 @@ void PopulateCounterTrack(ProcessId pid, const std::string& name,
256351

257352
void PopulateProcessTrack(ProcessId pid, const TraceInformation& trace_info,
258353
int& current_level, FlameChartTimelineData& data,
259-
TimeBounds& bounds) {
354+
TimeBounds& bounds,
355+
absl::btree_map<std::pair<ProcessId, ThreadId>,
356+
ThreadLevelInfo>& thread_levels) {
260357
const auto it_events = trace_info.events_by_pid_tid.find(pid);
261358
const bool has_events = it_events != trace_info.events_by_pid_tid.end() &&
262359
!it_events->second.empty();
@@ -282,7 +379,7 @@ void PopulateProcessTrack(ProcessId pid, const TraceInformation& trace_info,
282379
if (has_events) {
283380
for (const auto& [tid, events] : it_events->second) {
284381
PopulateThreadTrack(pid, tid, events, trace_info, current_level, data,
285-
bounds);
382+
bounds, thread_levels);
286383
}
287384
}
288385

@@ -322,17 +419,20 @@ FlameChartTimelineData CreateTimelineData(const TraceInformation& trace_info,
322419
TimeBounds& bounds) {
323420
FlameChartTimelineData data;
324421
int current_level = 0;
422+
absl::btree_map<std::pair<ProcessId, ThreadId>, ThreadLevelInfo>
423+
thread_levels;
325424

326-
std::vector<ProcessId> pids = GetSortedProcessIds(trace_info);
327-
328-
for (ProcessId pid : pids) {
329-
PopulateProcessTrack(pid, trace_info, current_level, data, bounds);
425+
for (const ProcessId pid : GetSortedProcessIds(trace_info)) {
426+
PopulateProcessTrack(pid, trace_info, current_level, data, bounds,
427+
thread_levels);
330428
}
331429

332430
data.events_by_level.resize(current_level);
333431
for (int i = 0; i < data.entry_levels.size(); ++i) {
334432
data.events_by_level[data.entry_levels[i]].push_back(i);
335433
}
434+
435+
GenerateFlowLines(trace_info, thread_levels, data, bounds);
336436
return data;
337437
}
338438

@@ -343,7 +443,8 @@ FlameChartTimelineData CreateTimelineData(const TraceInformation& trace_info,
343443
void DataProvider::ProcessTraceEvents(const ParsedTraceEvents& parsed_events,
344444
Timeline& timeline) {
345445
if (parsed_events.flame_events.empty() &&
346-
parsed_events.counter_events.empty()) {
446+
parsed_events.counter_events.empty() &&
447+
parsed_events.flow_events.empty()) {
347448
timeline.set_timeline_data({});
348449
timeline.set_fetched_data_time_range(TimeRange::Zero());
349450
timeline.SetVisibleRange(TimeRange::Zero());
@@ -352,6 +453,7 @@ void DataProvider::ProcessTraceEvents(const ParsedTraceEvents& parsed_events,
352453

353454
timeline.set_mpmd_pipeline_view_enabled(parsed_events.mpmd_pipeline_view);
354455

456+
absl::flat_hash_set<int> present_flow_categories_set;
355457
TraceInformation trace_info;
356458
for (const auto& event : parsed_events.flame_events) {
357459
switch (event.ph) {
@@ -369,10 +471,27 @@ void DataProvider::ProcessTraceEvents(const ParsedTraceEvents& parsed_events,
369471
}
370472
}
371473

474+
for (const auto& event : parsed_events.flow_events) {
475+
HandleFlowEvent(event, trace_info, present_flow_categories_set);
476+
}
477+
present_flow_categories_.assign(present_flow_categories_set.begin(),
478+
present_flow_categories_set.end());
479+
absl::c_sort(present_flow_categories_);
480+
372481
for (const auto& event : parsed_events.counter_events) {
373482
HandleCounterEvent(event, trace_info);
374483
}
375484

485+
// Ensure all pids/tids from flow events are registered so that thread tracks
486+
// are created for them, which is required for level calculation.
487+
for (const auto& [id, events] : trace_info.flow_events_by_id) {
488+
for (const auto* event : events) {
489+
trace_info.process_names.try_emplace(event->pid,
490+
GetDefaultProcessName(event->pid));
491+
trace_info.events_by_pid_tid[event->pid].try_emplace(event->tid);
492+
}
493+
}
494+
376495
// Sort events, first by timestamp (ascending), then by duration
377496
// (descending).
378497
// Ensure all processes have a name in process_names.
@@ -395,6 +514,11 @@ void DataProvider::ProcessTraceEvents(const ParsedTraceEvents& parsed_events,
395514
}
396515
}
397516

517+
for (auto& [id, events] : trace_info.flow_events_by_id) {
518+
absl::c_stable_sort(
519+
events, gtl::OrderBy([](const TraceEvent* e) { return e->ts; }));
520+
}
521+
398522
for (auto& [pid, counters_by_name] : trace_info.counters_by_pid_name) {
399523
for (auto& [name, events] : counters_by_name) {
400524
absl::c_stable_sort(
@@ -457,4 +581,8 @@ std::vector<std::string> DataProvider::GetProcessList() const {
457581
return process_list_;
458582
}
459583

584+
const std::vector<int>& DataProvider::GetFlowCategories() const {
585+
return present_flow_categories_;
586+
}
587+
460588
} // namespace traceviewer

frontend/app/components/trace_viewer_v2/timeline/data_provider.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
#define THIRD_PARTY_XPROF_FRONTEND_APP_COMPONENTS_TRACE_VIEWER_V2_TIMELINE_DATA_PROVIDER_H_
33

44
#include <string>
5-
#include <tuple>
65
#include <vector>
76

87
#include "absl/strings/string_view.h"
@@ -29,12 +28,16 @@ class DataProvider {
2928
// Returns a list of process names.
3029
std::vector<std::string> GetProcessList() const;
3130

31+
// Returns a list of flow categories present in the trace.
32+
const std::vector<int>& GetFlowCategories() const;
33+
3234
// Processes vectors of TraceEvent structs.
3335
void ProcessTraceEvents(const ParsedTraceEvents& parsed_events,
3436
Timeline& timeline);
3537

3638
private:
3739
std::vector<std::string> process_list_;
40+
std::vector<int> present_flow_categories_;
3841
};
3942

4043
} // namespace traceviewer

0 commit comments

Comments
 (0)