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 @@ -71,12 +71,15 @@ void render(const TraceModel&, ViewState&);
void on_model_changed();
```

## search_panel.h / search_panel.cpp — text search over event names; populates `ViewState::search_results`; shows per-name Count and Avg duration
## search_panel.h / search_panel.cpp — text search over event names; populates `ViewState::search_results`; shows per-name Count and Avg duration; "Unique by name" checkbox (default on) deduplicates results
```
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;
bool unique_by_name() const;
void set_unique_by_name(bool);
static std::vector<uint32_t> filter_unique_by_name(const TraceModel&, const std::vector<uint32_t>& results);
// NameStats: count, total_dur, avg_dur
```

Expand Down
41 changes: 34 additions & 7 deletions src/ui/search_panel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,32 @@
#include "imgui.h"
#include <algorithm>
#include <cstdio>
#include <unordered_set>

void SearchPanel::reset() {
TRACE_FUNCTION_CAT("ui");
search_buf_[0] = '\0';
needs_search_ = false;
unique_by_name_ = true;
sorted_results_.clear();
needs_sort_ = false;
scroll_to_top_ = false;
name_stats_.clear();
}

std::vector<uint32_t> SearchPanel::filter_unique_by_name(const TraceModel& model,
const std::vector<uint32_t>& results) {
std::unordered_set<uint32_t> seen;
std::vector<uint32_t> filtered;
for (uint32_t idx : results) {
const auto& ev = model.events()[idx];
if (seen.insert(ev.name_idx).second) {
filtered.push_back(idx);
}
}
return filtered;
}

void SearchPanel::build_name_stats(const TraceModel& model, const std::vector<uint32_t>& results) {
name_stats_.clear();
for (uint32_t idx : results) {
Expand Down Expand Up @@ -71,14 +86,26 @@ void SearchPanel::render(const TraceModel& model, ViewState& view) {
}
sorted_results_ = view.search_results();
build_name_stats(model, sorted_results_);
if (unique_by_name_) {
sorted_results_ = filter_unique_by_name(model, sorted_results_);
}
needs_sort_ = true;
}

if (ImGui::Checkbox("Unique by name", &unique_by_name_)) {
// Rebuild sorted_results_ from full results when toggling
sorted_results_ = view.search_results();
if (unique_by_name_) {
sorted_results_ = filter_unique_by_name(model, sorted_results_);
}
needs_sort_ = true;
}

ImGui::Text("%zu results", view.search_results().size());
ImGui::Text("%zu results", sorted_results_.size());

// Navigation
// Navigation (indexes into sorted_results_ which respects the unique filter)
bool navigate = false;
if (!view.search_results().empty()) {
if (!sorted_results_.empty()) {
ImGui::SameLine();
if (ImGui::Button("<") && view.search_current() > 0) {
view.set_search_current(view.search_current() - 1);
Expand All @@ -87,13 +114,13 @@ void SearchPanel::render(const TraceModel& model, ViewState& view) {
ImGui::SameLine();
if (ImGui::Button(">")) {
view.set_search_current(view.search_current() + 1);
if (view.search_current() >= (int32_t)view.search_results().size())
view.set_search_current((int32_t)view.search_results().size() - 1);
if (view.search_current() >= (int32_t)sorted_results_.size())
view.set_search_current((int32_t)sorted_results_.size() - 1);
navigate = true;
}

if (navigate && view.search_current() >= 0 && view.search_current() < (int32_t)view.search_results().size()) {
uint32_t ev_idx = view.search_results()[view.search_current()];
if (navigate && view.search_current() >= 0 && view.search_current() < (int32_t)sorted_results_.size()) {
uint32_t ev_idx = sorted_results_[view.search_current()];
view.navigate_to_event(ev_idx, model.events()[ev_idx], 2.0, 1000.0);
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/ui/search_panel.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@ class SearchPanel {
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_; }

bool unique_by_name() const { return unique_by_name_; }
void set_unique_by_name(bool v) { unique_by_name_ = v; }

// Filter results to keep one event per unique name (first occurrence).
static std::vector<uint32_t> filter_unique_by_name(const TraceModel& model, const std::vector<uint32_t>& results);

private:
// NOTE: update reset() when adding cached fields
void reset();
char search_buf_[256] = {};
bool needs_search_ = false;
bool unique_by_name_ = true;

// Sorted view of search results
// Sorted view of search results (may be filtered by unique_by_name_)
std::vector<uint32_t> sorted_results_;
bool needs_sort_ = false;
bool scroll_to_top_ = false;
Expand Down
48 changes: 48 additions & 0 deletions tests/test_search_panel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,51 @@ TEST(SearchPanel, BuildNameStatsEmpty) {

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

TEST(SearchPanel, UniqueByNameDefaultOn) {
SearchPanel panel;
EXPECT_TRUE(panel.unique_by_name());
}

TEST(SearchPanel, FilterUniqueByNameKeepsOnePerName) {
TraceModel m = make_search_model();

// All 4 events: foo(0), foo(1), foo(2), bar(3)
std::vector<uint32_t> results = {0, 1, 2, 3};
auto filtered = SearchPanel::filter_unique_by_name(m, results);

// Should keep first foo (idx 0) and first bar (idx 3)
ASSERT_EQ(filtered.size(), 2u);
EXPECT_EQ(filtered[0], 0u);
EXPECT_EQ(filtered[1], 3u);
}

TEST(SearchPanel, FilterUniqueByNameEmpty) {
TraceModel m = make_search_model();
std::vector<uint32_t> results = {};
auto filtered = SearchPanel::filter_unique_by_name(m, results);
EXPECT_TRUE(filtered.empty());
}

TEST(SearchPanel, FilterUniqueByNameAllSameName) {
TraceModel m = make_search_model();

// Only foo events
std::vector<uint32_t> results = {0, 1, 2};
auto filtered = SearchPanel::filter_unique_by_name(m, results);

ASSERT_EQ(filtered.size(), 1u);
EXPECT_EQ(filtered[0], 0u);
}

TEST(SearchPanel, FilterUniqueByNameAllUnique) {
TraceModel m = make_search_model();

// One foo and one bar - already unique
std::vector<uint32_t> results = {0, 3};
auto filtered = SearchPanel::filter_unique_by_name(m, results);

ASSERT_EQ(filtered.size(), 2u);
EXPECT_EQ(filtered[0], 0u);
EXPECT_EQ(filtered[1], 3u);
}
Loading