diff --git a/DevTools/ProfileViewer/Profile.cpp b/DevTools/ProfileViewer/Profile.cpp index 4c24ae4394..cbd0caf683 100644 --- a/DevTools/ProfileViewer/Profile.cpp +++ b/DevTools/ProfileViewer/Profile.cpp @@ -4,13 +4,14 @@ #include #include -Profile::Profile(const JsonArray& json, Vector>&& roots, u64 first_timestamp, u64 last_timestamp) +Profile::Profile(const JsonArray& json) : m_json(json) - , m_roots(move(roots)) - , m_first_timestamp(first_timestamp) - , m_last_timestamp(last_timestamp) { + m_first_timestamp = m_json.at(0).as_object().get("timestamp").to_number(); + m_last_timestamp = m_json.at(m_json.size() - 1).as_object().get("timestamp").to_number(); + m_model = ProfileModel::create(*this); + rebuild_tree(); } Profile::~Profile() @@ -22,24 +23,8 @@ GModel& Profile::model() return *m_model; } -OwnPtr Profile::load_from_file(const StringView& path) +void Profile::rebuild_tree() { - auto file = CFile::construct(path); - if (!file->open(CIODevice::ReadOnly)) { - fprintf(stderr, "Unable to open %s, error: %s\n", String(path).characters(), file->error_string()); - return nullptr; - } - - auto json = JsonValue::from_string(file->read_all()); - if (!json.is_array()) { - fprintf(stderr, "Invalid format (not a JSON array)\n"); - return nullptr; - } - - auto& samples = json.as_array(); - if (samples.is_empty()) - return nullptr; - NonnullRefPtrVector roots; auto find_or_create_root = [&roots](const String& symbol, u32 address, u32 offset, u64 timestamp) -> ProfileNode& { @@ -54,10 +39,13 @@ OwnPtr Profile::load_from_file(const StringView& path) return new_root; }; - u64 first_timestamp = samples.at(0).as_object().get("timestamp").to_number(); - u64 last_timestamp = samples.at(samples.size() - 1).as_object().get("timestamp").to_number(); + m_json.for_each([&](const JsonValue& sample) { + if (has_timestamp_filter_range()) { + auto timestamp = sample.as_object().get("timestamp").to_number(); + if (timestamp < m_timestamp_filter_range_start || timestamp > m_timestamp_filter_range_end) + return; + } - samples.for_each([&](const JsonValue& sample) { auto frames_value = sample.as_object().get("frames"); auto& frames = frames_value.as_array(); ProfileNode* node = nullptr; @@ -85,7 +73,29 @@ OwnPtr Profile::load_from_file(const StringView& path) root.sort_children(); } - return NonnullOwnPtr(NonnullOwnPtr::Adopt, *new Profile(move(samples), move(roots), first_timestamp, last_timestamp)); + m_roots = move(roots); + m_model->update(); +} + +OwnPtr Profile::load_from_file(const StringView& path) +{ + auto file = CFile::construct(path); + if (!file->open(CIODevice::ReadOnly)) { + fprintf(stderr, "Unable to open %s, error: %s\n", String(path).characters(), file->error_string()); + return nullptr; + } + + auto json = JsonValue::from_string(file->read_all()); + if (!json.is_array()) { + fprintf(stderr, "Invalid format (not a JSON array)\n"); + return nullptr; + } + + auto& samples = json.as_array(); + if (samples.is_empty()) + return nullptr; + + return NonnullOwnPtr(NonnullOwnPtr::Adopt, *new Profile(move(samples))); } void ProfileNode::sort_children() @@ -97,3 +107,23 @@ void ProfileNode::sort_children() for (auto& child : m_children) child->sort_children(); } + +void Profile::set_timestamp_filter_range(u64 start, u64 end) +{ + if (m_has_timestamp_filter_range && m_timestamp_filter_range_start == start && m_timestamp_filter_range_end == end) + return; + m_has_timestamp_filter_range = true; + + m_timestamp_filter_range_start = min(start, end); + m_timestamp_filter_range_end = max(start, end); + + rebuild_tree(); +} + +void Profile::clear_timestamp_filter_range() +{ + if (!m_has_timestamp_filter_range) + return; + m_has_timestamp_filter_range = false; + rebuild_tree(); +} diff --git a/DevTools/ProfileViewer/Profile.h b/DevTools/ProfileViewer/Profile.h index 13c83beb46..9305fc4d35 100644 --- a/DevTools/ProfileViewer/Profile.h +++ b/DevTools/ProfileViewer/Profile.h @@ -94,12 +94,22 @@ public: u64 first_timestamp() const { return m_first_timestamp; } u64 last_timestamp() const { return m_first_timestamp; } + void set_timestamp_filter_range(u64 start, u64 end); + void clear_timestamp_filter_range(); + bool has_timestamp_filter_range() const { return m_has_timestamp_filter_range; } + private: - explicit Profile(const JsonArray&, Vector>&&, u64 first_timestamp, u64 last_timestamp); + explicit Profile(const JsonArray&); + + void rebuild_tree(); JsonArray m_json; RefPtr m_model; Vector> m_roots; u64 m_first_timestamp { 0 }; u64 m_last_timestamp { 0 }; + + bool m_has_timestamp_filter_range { false }; + u64 m_timestamp_filter_range_start { 0 }; + u64 m_timestamp_filter_range_end { 0 }; }; diff --git a/DevTools/ProfileViewer/ProfileTimelineWidget.cpp b/DevTools/ProfileViewer/ProfileTimelineWidget.cpp index 6637d196c0..e841cb95cc 100644 --- a/DevTools/ProfileViewer/ProfileTimelineWidget.cpp +++ b/DevTools/ProfileViewer/ProfileTimelineWidget.cpp @@ -38,16 +38,50 @@ void ProfileTimelineWidget::paint_event(GPaintEvent& event) for (int i = 0; i < cw; ++i) painter.draw_line({ x + i, frame_thickness() }, { x + i, height() - frame_thickness() * 2 }, color); }); + + u64 normalized_start_time = min(m_select_start_time, m_select_end_time); + u64 normalized_end_time = max(m_select_start_time, m_select_end_time); + + int select_start_x = (int)((float)(normalized_start_time - m_profile.first_timestamp()) * column_width); + int select_end_x = (int)((float)(normalized_end_time - m_profile.first_timestamp()) * column_width); + painter.fill_rect({ select_start_x, frame_thickness(), select_end_x - select_start_x, height() - frame_thickness() * 2 }, Color(0, 0, 0, 60)); } -void ProfileTimelineWidget::mousedown_event(GMouseEvent&) +u64 ProfileTimelineWidget::timestamp_at_x(int x) const { + float column_width = (float)frame_inner_rect().width() / (float)m_profile.length_in_ms(); + float ms_into_profile = (float)x / column_width; + return m_profile.first_timestamp() + (u64)ms_into_profile; } -void ProfileTimelineWidget::mousemove_event(GMouseEvent&) +void ProfileTimelineWidget::mousedown_event(GMouseEvent& event) { + if (event.button() != GMouseButton::Left) + return; + + m_selecting = true; + m_select_start_time = timestamp_at_x(event.x()); + m_select_end_time = m_select_start_time; + m_profile.set_timestamp_filter_range(m_select_start_time, m_select_end_time); + update(); } -void ProfileTimelineWidget::mouseup_event(GMouseEvent&) +void ProfileTimelineWidget::mousemove_event(GMouseEvent& event) { + if (!m_selecting) + return; + + m_select_end_time = timestamp_at_x(event.x()); + m_profile.set_timestamp_filter_range(m_select_start_time, m_select_end_time); + update(); +} + +void ProfileTimelineWidget::mouseup_event(GMouseEvent& event) +{ + if (event.button() != GMouseButton::Left) + return; + + m_selecting = false; + if (m_select_start_time == m_select_end_time) + m_profile.clear_timestamp_filter_range(); } diff --git a/DevTools/ProfileViewer/ProfileTimelineWidget.h b/DevTools/ProfileViewer/ProfileTimelineWidget.h index eca99d3c58..6e438b2130 100644 --- a/DevTools/ProfileViewer/ProfileTimelineWidget.h +++ b/DevTools/ProfileViewer/ProfileTimelineWidget.h @@ -15,5 +15,11 @@ private: ProfileTimelineWidget(Profile&, GWidget* parent); + u64 timestamp_at_x(int x) const; + Profile& m_profile; + + bool m_selecting { false }; + u64 m_select_start_time { 0 }; + u64 m_select_end_time { 0 }; };