Skip to content

Commit 26520ad

Browse files
Profiler Teamcopybara-github
authored andcommitted
Add the time measurement feature (1/4)
Shift-clicking an event to add/remove a time selection equals to its range. PiperOrigin-RevId: 836451701
1 parent 11528c0 commit 26520ad

File tree

4 files changed

+251
-101
lines changed

4 files changed

+251
-101
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ inline constexpr Pixel kHoverCornerRounding = 8.0f;
6262
inline constexpr Pixel kSelectedBorderThickness = 2.0f;
6363
// go/keep-sorted end
6464

65+
// Time Range Selection Constants
66+
// go/keep-sorted start
67+
// A semi-transparent blue for the curtain border. #A1C9FF at 60% opacity.
68+
inline constexpr ImU32 kSelectedTimeRangeBorderColor = 0x99FFC9A1;
69+
// A semi-transparent blue for the curtain highlight. #A1C9FF at 30% opacity.
70+
inline constexpr ImU32 kSelectedTimeRangeColor = 0x4DFFC9A1;
71+
// go/keep-sorted end
72+
6573
// Zooming and Panning Constants
6674
// These constants control the zooming and panning behavior of the timeline.
6775
// go/keep-sorted start

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

Lines changed: 187 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <cfloat>
55
#include <cmath>
66
#include <numeric>
7+
#include <optional>
78
#include <string>
89

910
#include "xprof/frontend/app/components/trace_viewer_v2/color/color_generator.h"
@@ -56,15 +57,17 @@ void Timeline::Draw() {
5657
ImGui::SetNextWindowPos(viewport->Pos);
5758
ImGui::SetNextWindowSize(viewport->Size);
5859
ImGui::SetNextWindowViewport(viewport->ID);
60+
5961
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f);
62+
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.0f, 0.0f));
63+
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
64+
6065
ImGui::Begin("Timeline viewer", nullptr, kImGuiWindowFlags);
6166

6267
if (timeline_data_.groups.empty()) {
6368
DrawLoadingIndicator(viewport);
6469
}
6570

66-
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
67-
6871
const Pixel timeline_width =
6972
ImGui::GetContentRegionAvail().x - label_width_ - kTimelinePaddingRight;
7073
const double px_per_time_unit_val = px_per_time_unit(timeline_width);
@@ -97,71 +100,14 @@ void Timeline::Draw() {
97100
ImGui::Unindent((group.nesting_level + 1) * kIndentSize);
98101

99102
ImGui::TableNextColumn();
100-
const int start_level = group.start_level;
101-
int end_level = (group_index + 1 < timeline_data_.groups.size())
102-
? timeline_data_.groups[group_index + 1].start_level
103-
// If this is the last group, the end level is the total
104-
// number of levels.
105-
: timeline_data_.events_by_level.size();
106-
// Ensure end_level is not less than start_level, to avoid negative height.
107-
end_level = std::max(start_level, end_level);
108-
109-
// Calculate group height. Ensure a minimum height of one level to prevent
110-
// ImGui::BeginChild from auto-resizing, even if a group contains no levels.
111-
// This is important for parent groups (e.g., a process) that might not
112-
// contain any event levels directly.
113-
// TODO: b/453676716 - Add tests for group height calculation.
114-
const Pixel group_height = std::max(1, end_level - start_level) *
115-
(kEventHeight + kEventPaddingBottom);
116-
// Groups might have the same name. We add the index of the group to the ID
117-
// to ensure each ImGui::BeginChild call has a unique ID, otherwise ImGui
118-
// might ignore later calls with the same name.
119-
const std::string timeline_child_id =
120-
absl::StrCat("TimelineChild_", group.name, "_", group_index);
121-
122-
if (ImGui::BeginChild(timeline_child_id.c_str(), ImVec2(0, group_height),
123-
ImGuiChildFlags_None, kLaneFlags)) {
124-
const ImVec2 pos = ImGui::GetCursorScreenPos();
125-
const ImVec2 max = ImGui::GetContentRegionMax();
126-
127-
for (int level = start_level; level < end_level; ++level) {
128-
// This is a sanity check to ensure the level is within the bounds of
129-
// events_by_level.
130-
if (level < timeline_data_.events_by_level.size()) {
131-
// TODO: b/453676716 - Add boundary test cases for this function.
132-
DrawEventsForLevel(timeline_data_.events_by_level[level],
133-
px_per_time_unit_val,
134-
/*level_in_group=*/level - start_level, pos, max);
135-
}
136-
}
137-
}
138-
ImGui::EndChild();
139-
140-
if (group_index < timeline_data_.groups.size() - 1) {
141-
ImDrawList* draw_list = ImGui::GetWindowDrawList();
142-
float line_y =
143-
ImGui::GetItemRectMax().y + ImGui::GetStyle().CellPadding.y;
144-
draw_list->AddLine(ImVec2(viewport->Pos.x + label_width_ + 15, line_y),
145-
ImVec2(viewport->Pos.x + viewport->Size.x, line_y),
146-
kLightGrayColor);
147-
}
103+
DrawGroup(group_index, px_per_time_unit_val);
148104
}
149105

150106
ImGui::EndTable();
151107

152-
// If an event was selected, and the user clicks on an empty area
153-
// (i.e., not on any event), deselect the event.
154-
if (selected_event_index_ != -1 && ImGui::IsMouseClicked(0) &&
155-
ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) &&
156-
!event_clicked_this_frame_) {
157-
selected_event_index_ = -1;
158-
159-
EventData event_data;
160-
event_data[std::string(kEventSelectedIndex)] = -1;
161-
event_data[std::string(kEventSelectedName)] = std::string("");
108+
DrawSelectedTimeRange(timeline_width, px_per_time_unit_val);
162109

163-
event_callback_(kEventSelected, event_data);
164-
}
110+
HandleEventDeselection();
165111

166112
// Handle continuous keyboard and mouse wheel input for timeline navigation.
167113
// These functions are called every frame to ensure smooth and responsive
@@ -173,8 +119,9 @@ void Timeline::Draw() {
173119

174120
ImGui::EndChild();
175121
ImGui::PopStyleVar(); // ItemSpacing
122+
ImGui::PopStyleVar(); // CellPadding
176123
ImGui::PopStyleVar(); // WindowRounding
177-
ImGui::End();
124+
ImGui::End(); // Timeline viewer
178125
}
179126

180127
EventRect Timeline::CalculateEventRect(Microseconds start, Microseconds end,
@@ -388,6 +335,87 @@ double Timeline::px_per_time_unit(Pixel timeline_width) const {
388335
}
389336
}
390337

338+
// Draws the timeline ruler. This includes the main horizontal line,
339+
// vertical tick marks indicating time intervals, and their corresponding time
340+
// labels.
341+
void Timeline::DrawRuler(Pixel timeline_width) {
342+
if (ImGui::BeginTable("Ruler", 2, kImGuiTableFlags)) {
343+
ImGui::TableSetupColumn("Labels", ImGuiTableColumnFlags_WidthFixed,
344+
label_width_);
345+
ImGui::TableSetupColumn("Timeline", ImGuiTableColumnFlags_WidthStretch);
346+
ImGui::TableNextRow();
347+
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,
348+
ImGui::GetColorU32(ImGuiCol_WindowBg));
349+
ImGui::TableNextColumn();
350+
ImGui::TableNextColumn();
351+
352+
const ImVec2 pos = ImGui::GetCursorScreenPos();
353+
ImDrawList* const draw_list = ImGui::GetWindowDrawList();
354+
355+
const double px_per_time_unit_val = px_per_time_unit(timeline_width);
356+
if (px_per_time_unit_val > 0) {
357+
// Draw horizontal line
358+
const Pixel line_y = pos.y + kRulerHeight;
359+
draw_list->AddLine(ImVec2(pos.x, line_y),
360+
ImVec2(pos.x + timeline_width, line_y),
361+
kRulerLineColor);
362+
363+
const Microseconds min_time_interval =
364+
kMinTickDistancePx / px_per_time_unit_val;
365+
const Microseconds tick_interval =
366+
CalculateNiceInterval(min_time_interval);
367+
const Pixel major_tick_dist_px = tick_interval * px_per_time_unit_val;
368+
369+
const Microseconds view_start = visible_range().start();
370+
const Microseconds trace_start = data_time_range_.start();
371+
372+
const Microseconds view_start_relative = view_start - trace_start;
373+
const Microseconds first_tick_time_relative =
374+
std::floor(view_start_relative / tick_interval) * tick_interval;
375+
376+
const Pixel minor_tick_dist_px =
377+
major_tick_dist_px / static_cast<float>(kMinorTickDivisions);
378+
379+
Microseconds t_relative = first_tick_time_relative;
380+
Pixel x =
381+
TimeToScreenX(t_relative + trace_start, pos.x, px_per_time_unit_val);
382+
383+
for (;; t_relative += tick_interval, x += major_tick_dist_px) {
384+
if (x > pos.x + timeline_width + kRulerScreenBuffer) {
385+
break;
386+
}
387+
388+
// Draw major tick.
389+
if (x >= pos.x - kRulerScreenBuffer) {
390+
draw_list->AddLine(ImVec2(x, line_y - kRulerTickHeight),
391+
ImVec2(x, line_y), kRulerLineColor);
392+
393+
const std::string text = FormatTime(t_relative);
394+
draw_list->AddText(ImVec2(x + kRulerTextPadding, pos.y),
395+
kRulerTextColor, text.c_str());
396+
}
397+
398+
// Draw minor ticks for the current interval.
399+
for (int i = 1; i < kMinorTickDivisions; ++i) {
400+
const Pixel minor_x = x + i * minor_tick_dist_px;
401+
if (minor_x > pos.x + timeline_width + kRulerScreenBuffer) {
402+
break;
403+
}
404+
if (minor_x >= pos.x - kRulerScreenBuffer) {
405+
draw_list->AddLine(
406+
ImVec2(minor_x, line_y - kRulerTickHeight / 2.0f),
407+
ImVec2(minor_x, line_y), kRulerLineColor);
408+
}
409+
}
410+
}
411+
}
412+
413+
// Reserve space for the ruler
414+
ImGui::Dummy(ImVec2(0.0f, kRulerHeight + ImGui::GetStyle().CellPadding.y));
415+
ImGui::EndTable();
416+
}
417+
}
418+
391419
void Timeline::DrawEventName(absl::string_view event_name,
392420
const EventRect& event_rect,
393421
ImDrawList* absl_nonnull draw_list) const {
@@ -442,6 +470,20 @@ void Timeline::DrawEvent(int event_index, const EventRect& rect,
442470
// ImGuiMouseButton enum. We check if the left mouse button was clicked.
443471
if (ImGui::IsMouseClicked(0)) {
444472
event_clicked_this_frame_ = true;
473+
474+
if (ImGui::GetIO().KeyShift) {
475+
const Microseconds start =
476+
timeline_data_.entry_start_times[event_index];
477+
const Microseconds end =
478+
start + timeline_data_.entry_total_times[event_index];
479+
TimeRange event_range(start, end);
480+
if (selected_time_range_ == event_range) {
481+
selected_time_range_ = std::nullopt;
482+
} else {
483+
selected_time_range_ = event_range;
484+
}
485+
}
486+
445487
if (selected_event_index_ != event_index) {
446488
selected_event_index_ = event_index;
447489

@@ -492,41 +534,6 @@ void Timeline::DrawEventsForLevel(absl::Span<const int> event_indices,
492534
}
493535
}
494536

495-
void Timeline::HandleKeyboard() {
496-
const ImGuiIO& io = ImGui::GetIO();
497-
498-
// Pan left
499-
if (ImGui::IsKeyDown(ImGuiKey_A)) {
500-
float multiplier = GetSpeedMultiplier(io, ImGuiKey_A);
501-
Pan(-kPanningSpeed * io.DeltaTime * multiplier);
502-
}
503-
// Pan right
504-
if (ImGui::IsKeyDown(ImGuiKey_D)) {
505-
float multiplier = GetSpeedMultiplier(io, ImGuiKey_D);
506-
Pan(kPanningSpeed * io.DeltaTime * multiplier);
507-
}
508-
509-
// Scroll up
510-
if (ImGui::IsKeyDown(ImGuiKey_UpArrow)) {
511-
Scroll(-kScrollSpeed * io.DeltaTime);
512-
}
513-
// Scroll down
514-
if (ImGui::IsKeyDown(ImGuiKey_DownArrow)) {
515-
Scroll(kScrollSpeed * io.DeltaTime);
516-
}
517-
518-
// Zoom in
519-
if (ImGui::IsKeyDown(ImGuiKey_W)) {
520-
float multiplier = GetSpeedMultiplier(io, ImGuiKey_W);
521-
Zoom(1.0f - kZoomSpeed * io.DeltaTime * multiplier);
522-
}
523-
// Zoom out
524-
if (ImGui::IsKeyDown(ImGuiKey_S)) {
525-
float multiplier = GetSpeedMultiplier(io, ImGuiKey_S);
526-
Zoom(1.0f + kZoomSpeed * io.DeltaTime * multiplier);
527-
}
528-
}
529-
530537
// Draws the timeline ruler. This includes the main horizontal line,
531538
// vertical tick marks indicating time intervals, and their corresponding time
532539
// labels.
@@ -610,6 +617,72 @@ void Timeline::DrawRuler(Pixel timeline_width, Pixel viewport_bottom) {
610617
}
611618
}
612619

620+
void Timeline::DrawSelectedTimeRange(Pixel timeline_width,
621+
double px_per_time_unit_val) {
622+
if (!selected_time_range_) return;
623+
624+
const ImVec2 table_rect_min = ImGui::GetItemRectMin();
625+
const ImVec2 table_rect_max = ImGui::GetItemRectMax();
626+
const Pixel timeline_x_start = table_rect_min.x + label_width_;
627+
628+
const Pixel time_range_x1 = TimeToScreenX(
629+
selected_time_range_->start(), timeline_x_start, px_per_time_unit_val);
630+
const Pixel time_range_x2 = TimeToScreenX(
631+
selected_time_range_->end(), timeline_x_start, px_per_time_unit_val);
632+
const Pixel clipped_x1 = std::max(time_range_x1, timeline_x_start);
633+
const Pixel clipped_x2 =
634+
std::min(time_range_x2, timeline_x_start + timeline_width);
635+
636+
if (clipped_x2 > clipped_x1) {
637+
// Use the foreground draw list to render over all other timeline content.
638+
ImDrawList* const draw_list = ImGui::GetForegroundDrawList();
639+
draw_list->AddRectFilled(ImVec2(clipped_x1, table_rect_min.y),
640+
ImVec2(clipped_x2, table_rect_max.y),
641+
kSelectedTimeRangeColor);
642+
draw_list->AddLine(ImVec2(clipped_x1, table_rect_min.y),
643+
ImVec2(clipped_x1, table_rect_max.y),
644+
kSelectedTimeRangeBorderColor);
645+
draw_list->AddLine(ImVec2(clipped_x2, table_rect_min.y),
646+
ImVec2(clipped_x2, table_rect_max.y),
647+
kSelectedTimeRangeBorderColor);
648+
}
649+
}
650+
651+
void Timeline::HandleKeyboard() {
652+
const ImGuiIO& io = ImGui::GetIO();
653+
654+
// Pan left
655+
if (ImGui::IsKeyDown(ImGuiKey_A)) {
656+
float multiplier = GetSpeedMultiplier(io, ImGuiKey_A);
657+
Pan(-kPanningSpeed * io.DeltaTime * multiplier);
658+
}
659+
// Pan right
660+
if (ImGui::IsKeyDown(ImGuiKey_D)) {
661+
float multiplier = GetSpeedMultiplier(io, ImGuiKey_D);
662+
Pan(kPanningSpeed * io.DeltaTime * multiplier);
663+
}
664+
665+
// Scroll up
666+
if (ImGui::IsKeyDown(ImGuiKey_UpArrow)) {
667+
Scroll(-kScrollSpeed * io.DeltaTime);
668+
}
669+
// Scroll down
670+
if (ImGui::IsKeyDown(ImGuiKey_DownArrow)) {
671+
Scroll(kScrollSpeed * io.DeltaTime);
672+
}
673+
674+
// Zoom in
675+
if (ImGui::IsKeyDown(ImGuiKey_W)) {
676+
float multiplier = GetSpeedMultiplier(io, ImGuiKey_W);
677+
Zoom(1.0f - kZoomSpeed * io.DeltaTime * multiplier);
678+
}
679+
// Zoom out
680+
if (ImGui::IsKeyDown(ImGuiKey_S)) {
681+
float multiplier = GetSpeedMultiplier(io, ImGuiKey_S);
682+
Zoom(1.0f + kZoomSpeed * io.DeltaTime * multiplier);
683+
}
684+
}
685+
613686
void Timeline::HandleWheel() {
614687
const ImGuiIO& io = ImGui::GetIO();
615688

@@ -636,4 +709,20 @@ void Timeline::HandleWheel() {
636709
}
637710
}
638711

712+
void Timeline::HandleEventDeselection() {
713+
// If an event was selected, and the user clicks on an empty area
714+
// (i.e., not on any event), deselect the event.
715+
if (selected_event_index_ != -1 && ImGui::IsMouseClicked(0) &&
716+
ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) &&
717+
!event_clicked_this_frame_) {
718+
selected_event_index_ = -1;
719+
720+
EventData event_data;
721+
event_data[std::string(kEventSelectedIndex)] = -1;
722+
event_data[std::string(kEventSelectedName)] = std::string("");
723+
724+
event_callback_(kEventSelected, event_data);
725+
}
726+
}
727+
639728
} // namespace traceviewer

0 commit comments

Comments
 (0)