Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/ui/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,13 @@ void render(const TraceModel&, ViewState&);
void on_model_changed();
```

## search_panel.h / search_panel.cpp — text search over event names; populates `ViewState::search_results`
## search_panel.h / search_panel.cpp — text search over event names; populates `ViewState::search_results`; shows per-name Count and Avg duration
```
void render(const TraceModel&, ViewState&);
void on_model_changed();
void build_name_stats(const TraceModel&, const std::vector<uint32_t>& results);
const std::unordered_map<uint32_t, NameStats>& name_stats() const;
// NameStats: count, total_dur, avg_dur
```

## filter_panel.h / filter_panel.cpp — hide/show processes, threads, categories via `ViewState::hidden_*`
Expand Down
48 changes: 47 additions & 1 deletion src/ui/search_panel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ void SearchPanel::reset() {
sorted_results_.clear();
needs_sort_ = false;
scroll_to_top_ = false;
name_stats_.clear();
}

void SearchPanel::build_name_stats(const TraceModel& model, const std::vector<uint32_t>& results) {
name_stats_.clear();
for (uint32_t idx : results) {
const auto& ev = model.events()[idx];
auto& stats = name_stats_[ev.name_idx];
stats.count++;
stats.total_dur += ev.dur;
}
for (auto& [name_idx, stats] : name_stats_) {
stats.avg_dur = stats.count > 0 ? stats.total_dur / stats.count : 0.0;
}
}

void SearchPanel::on_model_changed() {
Expand Down Expand Up @@ -56,6 +70,7 @@ void SearchPanel::render(const TraceModel& model, ViewState& view) {
}
}
sorted_results_ = view.search_results();
build_name_stats(model, sorted_results_);
needs_sort_ = true;
}

Expand Down Expand Up @@ -87,14 +102,16 @@ void SearchPanel::render(const TraceModel& model, ViewState& view) {

// Results table
if (!sorted_results_.empty() &&
ImGui::BeginTable("SearchResults", 3,
ImGui::BeginTable("SearchResults", 5,
ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti | ImGuiTableFlags_RowBg |
ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_ScrollY |
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable,
ImVec2(0, 0))) {
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_DefaultSort, 0.0f, 0);
ImGui::TableSetupColumn("Duration", ImGuiTableColumnFlags_None, 0.0f, 1);
ImGui::TableSetupColumn("Count", ImGuiTableColumnFlags_None, 0.0f, 3);
ImGui::TableSetupColumn("Avg", ImGuiTableColumnFlags_None, 0.0f, 4);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_None, 0.0f, 2);
ImGui::TableHeadersRow();

Expand Down Expand Up @@ -126,6 +143,22 @@ void SearchPanel::render(const TraceModel& model, ViewState& view) {
cmp = na.compare(nb);
break;
}
case 3: { // Count
auto it_a = name_stats_.find(a.name_idx);
auto it_b = name_stats_.find(b.name_idx);
uint32_t ca = it_a != name_stats_.end() ? it_a->second.count : 0;
uint32_t cb = it_b != name_stats_.end() ? it_b->second.count : 0;
cmp = sort_utils::three_way_cmp(ca, cb);
break;
}
case 4: { // Avg
auto it_a = name_stats_.find(a.name_idx);
auto it_b = name_stats_.find(b.name_idx);
double aa = it_a != name_stats_.end() ? it_a->second.avg_dur : 0.0;
double ab = it_b != name_stats_.end() ? it_b->second.avg_dur : 0.0;
cmp = sort_utils::three_way_cmp(aa, ab);
break;
}
}
return ascending ? (cmp < 0) : (cmp > 0);
});
Expand Down Expand Up @@ -171,6 +204,19 @@ void SearchPanel::render(const TraceModel& model, ViewState& view) {
ImGui::TableNextColumn();
ImGui::TextUnformatted(dur_buf);

ImGui::TableNextColumn();
auto stats_it = name_stats_.find(ev.name_idx);
if (stats_it != name_stats_.end()) {
ImGui::Text("%u", stats_it->second.count);
}

ImGui::TableNextColumn();
if (stats_it != name_stats_.end()) {
char avg_buf[64];
format_time(stats_it->second.avg_dur, avg_buf, sizeof(avg_buf));
ImGui::TextUnformatted(avg_buf);
}

ImGui::TableNextColumn();
ImGui::TextUnformatted(name.c_str());
}
Expand Down
12 changes: 12 additions & 0 deletions src/ui/search_panel.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
#pragma once
#include "model/trace_model.h"
#include "ui/view_state.h"
#include <unordered_map>

struct NameStats {
uint32_t count = 0;
double total_dur = 0.0;
double avg_dur = 0.0;
};

class SearchPanel {
public:
void render(const TraceModel& model, ViewState& view);
void on_model_changed();
void build_name_stats(const TraceModel& model, const std::vector<uint32_t>& results);
const std::unordered_map<uint32_t, NameStats>& name_stats() const { return name_stats_; }

private:
// NOTE: update reset() when adding cached fields
Expand All @@ -17,4 +26,7 @@ class SearchPanel {
std::vector<uint32_t> sorted_results_;
bool needs_sort_ = false;
bool scroll_to_top_ = false;

// Per-name aggregates (name_idx -> stats)
std::unordered_map<uint32_t, NameStats> name_stats_;
};
89 changes: 89 additions & 0 deletions tests/test_search_panel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include <gtest/gtest.h>
#include "ui/search_panel.h"

static TraceModel make_search_model() {
TraceModel m;
uint32_t n_foo = m.intern_string("foo");
uint32_t n_bar = m.intern_string("bar");
uint32_t cat = m.intern_string("test");

auto& t = m.get_or_create_process(1).get_or_create_thread(1);

auto push = [&](uint32_t name, double ts, double dur) {
TraceEvent e;
e.name_idx = name;
e.cat_idx = cat;
e.ph = Phase::Complete;
e.ts = ts;
e.dur = dur;
e.pid = 1;
e.tid = 1;
e.depth = 0;
e.parent_idx = -1;
e.self_time = dur;
t.event_indices.push_back(m.add_event(e));
};

// 3 "foo" events with durations 10, 20, 30 => count=3, avg=20
push(n_foo, 0, 10);
push(n_foo, 100, 20);
push(n_foo, 200, 30);
// 1 "bar" event with duration 50 => count=1, avg=50
push(n_bar, 300, 50);

m.build_index();
return m;
}

TEST(SearchPanel, BuildNameStatsCountAndAvg) {
TraceModel m = make_search_model();
SearchPanel panel;

// All 4 events
std::vector<uint32_t> results = {0, 1, 2, 3};
panel.build_name_stats(m, results);

uint32_t n_foo = m.intern_string("foo");
uint32_t n_bar = m.intern_string("bar");

const auto& stats = panel.name_stats();
ASSERT_EQ(stats.size(), 2u);

auto foo_it = stats.find(n_foo);
ASSERT_NE(foo_it, stats.end());
EXPECT_EQ(foo_it->second.count, 3u);
EXPECT_DOUBLE_EQ(foo_it->second.avg_dur, 20.0);

auto bar_it = stats.find(n_bar);
ASSERT_NE(bar_it, stats.end());
EXPECT_EQ(bar_it->second.count, 1u);
EXPECT_DOUBLE_EQ(bar_it->second.avg_dur, 50.0);
}

TEST(SearchPanel, BuildNameStatsSubsetOfResults) {
TraceModel m = make_search_model();
SearchPanel panel;

// Only first 2 foo events
std::vector<uint32_t> results = {0, 1};
panel.build_name_stats(m, results);

uint32_t n_foo = m.intern_string("foo");
const auto& stats = panel.name_stats();
ASSERT_EQ(stats.size(), 1u);

auto foo_it = stats.find(n_foo);
ASSERT_NE(foo_it, stats.end());
EXPECT_EQ(foo_it->second.count, 2u);
EXPECT_DOUBLE_EQ(foo_it->second.avg_dur, 15.0);
}

TEST(SearchPanel, BuildNameStatsEmpty) {
TraceModel m = make_search_model();
SearchPanel panel;

std::vector<uint32_t> results = {};
panel.build_name_stats(m, results);

EXPECT_TRUE(panel.name_stats().empty());
}
Loading