From 33850d5abf807f99cd37b41106817c2f5338d7e0 Mon Sep 17 00:00:00 2001 From: Victor Zarubkin Date: Sat, 26 Oct 2019 01:04:21 +0300 Subject: [PATCH] [ui] histogram update: added avg, median times, added color square for block name, added borders --- profiler_gui/blocks_graphics_view.cpp | 10 +- profiler_gui/blocks_graphics_view.h | 1 + profiler_gui/common_functions.cpp | 125 +++++- profiler_gui/common_functions.h | 11 + profiler_gui/common_types.h | 11 + profiler_gui/globals.cpp | 1 + profiler_gui/globals.h | 3 + profiler_gui/graphics_block_item.cpp | 13 +- profiler_gui/graphics_scrollbar.cpp | 612 ++++++++++++++++++++++---- profiler_gui/graphics_scrollbar.h | 18 +- profiler_gui/graphics_slider_area.cpp | 14 +- profiler_gui/graphics_slider_area.h | 2 +- profiler_gui/main_window.cpp | 43 +- profiler_gui/main_window.h | 1 + profiler_gui/tree_widget_loader.cpp | 95 +--- profiler_gui/tree_widget_loader.h | 4 +- 16 files changed, 759 insertions(+), 205 deletions(-) diff --git a/profiler_gui/blocks_graphics_view.cpp b/profiler_gui/blocks_graphics_view.cpp index b33c065..1a237a9 100644 --- a/profiler_gui/blocks_graphics_view.cpp +++ b/profiler_gui/blocks_graphics_view.cpp @@ -727,6 +727,14 @@ void BlocksGraphicsView::onWindowActivationChanged() } } +void BlocksGraphicsView::repaintHistogramImage() +{ + if (m_pScrollbar != nullptr) + { + m_pScrollbar->repaintHistogramImage(); + } +} + void BlocksGraphicsView::removePopup() { delete m_popupWidget; @@ -2692,7 +2700,7 @@ void BlocksGraphicsView::onHierarchyFlagChange(bool) void BlocksGraphicsView::onSelectedThreadChange(profiler::thread_id_t id) { - if (m_pScrollbar == nullptr || m_pScrollbar->hystThread() == id) + if (m_pScrollbar == nullptr || m_pScrollbar->histThread() == id) { return; } diff --git a/profiler_gui/blocks_graphics_view.h b/profiler_gui/blocks_graphics_view.h index f0e06c5..ac5c941 100644 --- a/profiler_gui/blocks_graphics_view.h +++ b/profiler_gui/blocks_graphics_view.h @@ -265,6 +265,7 @@ public: public slots: void onWindowActivationChanged(); + void repaintHistogramImage(); signals: diff --git a/profiler_gui/common_functions.cpp b/profiler_gui/common_functions.cpp index 706ff91..8de2155 100644 --- a/profiler_gui/common_functions.cpp +++ b/profiler_gui/common_functions.cpp @@ -81,7 +81,7 @@ inline EASY_CONSTEXPR_FCN uint16_t sizeOf() { return static_cast(sizeof(typename profiler::StdType::value_type)); } -QString arrayToString(const profiler::ArbitraryValue& _serializedValue, int _index) +static QString arrayToString(const profiler::ArbitraryValue& _serializedValue, int _index) { switch (_serializedValue.type()) { @@ -107,7 +107,7 @@ QString arrayToString(const profiler::ArbitraryValue& _serializedValue, int _ind } } -QString singleValueToString(const profiler::ArbitraryValue& _serializedValue) +static QString singleValueToString(const profiler::ArbitraryValue& _serializedValue) { switch (_serializedValue.type()) { @@ -128,6 +128,32 @@ QString singleValueToString(const profiler::ArbitraryValue& _serializedValue) } } +template +static QString shortenCountStringUnsigned(T count, int precision) +{ + if (count >= 1000000) + return QStringLiteral("%1m").arg(QString::number(count * 1e-6, 'f', precision)); + + if (count >= 1000) + return QStringLiteral("%1k").arg(QString::number(count * 1e-3, 'f', precision)); + + return QString::number(count); +} + +template +static QString shortenCountStringSigned(T count, int precision) +{ + const auto absCount = std::abs(count); + + if (absCount >= 1000000) + return QStringLiteral("%1m").arg(QString::number(count * 1e-6, 'f', precision)); + + if (absCount >= 1000) + return QStringLiteral("%1k").arg(QString::number(count * 1e-3, 'f', precision)); + + return QString::number(count); +} + namespace profiler_gui { ////////////////////////////////////////////////////////////////////////// @@ -293,6 +319,28 @@ namespace profiler_gui { ////////////////////////////////////////////////////////////////////////// + QString shortenCountString(size_t count, int precision) + { + return shortenCountStringUnsigned(count, precision); + } + + QString shortenCountString(uint32_t count, int precision) + { + return shortenCountStringUnsigned(count, precision); + } + + QString shortenCountString(int64_t count, int precision) + { + return shortenCountStringSigned(count, precision); + } + + QString shortenCountString(int count, int precision) + { + return shortenCountStringSigned(count, precision); + } + + ////////////////////////////////////////////////////////////////////////// + QFont EFont(QFont::StyleHint _hint, const char* _family, int _size, int _weight) { QFont f(_family, _size, _weight); @@ -479,4 +527,77 @@ namespace profiler_gui { } } + ////////////////////////////////////////////////////////////////////////// + + profiler::timestamp_t calculateMedian(const DurationsCountMap& durations) + { + if (durations.empty()) + { + return 0; + } + + profiler::timestamp_t median = 0; + + size_t total_count = 0; + for (auto& kv : durations) + { + total_count += kv.second.count; + } + + if (total_count & 1) + { + const auto index = total_count >> 1; + size_t i = 0; + for (auto& kv : durations) + { + const auto count = kv.second.count; + + i += count; + if (i < index) + { + continue; + } + + median = kv.first; + break; + } + } + else + { + const auto index2 = total_count >> 1; + const auto index1 = index2 - 1; + + size_t i = 0; + bool i1 = false; + for (auto& kv : durations) + { + const auto count = kv.second.count; + + i += count; + if (i < index1) + { + continue; + } + + if (!i1) + { + i1 = true; + median = kv.first; + } + + if (i < index2) + { + continue; + } + + median += kv.first; + median >>= 1; + + break; + } + } + + return median; + } + } // end of namespace profiler_gui. diff --git a/profiler_gui/common_functions.h b/profiler_gui/common_functions.h index e440684..2f875e8 100644 --- a/profiler_gui/common_functions.h +++ b/profiler_gui/common_functions.h @@ -198,6 +198,13 @@ QString timeStringIntNs(TimeUnits _units, ::profiler::timestamp_t _interval); ////////////////////////////////////////////////////////////////////////// +QString shortenCountString(size_t count, int precision = 1); +QString shortenCountString(uint32_t count, int precision = 1); +QString shortenCountString(int64_t count, int precision = 1); +QString shortenCountString(int count, int precision = 1); + +////////////////////////////////////////////////////////////////////////// + inline double percentReal(::profiler::timestamp_t _partial, ::profiler::timestamp_t _total) { return _total != 0 ? 100. * static_cast(_partial) / static_cast(_total) : 0.; } @@ -243,6 +250,10 @@ void updateProperty(QWidget* widget, const char* name, T&& property) void deleteTreeItem(QTreeWidgetItem* item); +/////////////////////////////////////////////////////////////////////// + +profiler::timestamp_t calculateMedian(const DurationsCountMap& durations); + } // END of namespace profiler_gui. ////////////////////////////////////////////////////////////////////////// diff --git a/profiler_gui/common_types.h b/profiler_gui/common_types.h index 51acd63..2b457a5 100644 --- a/profiler_gui/common_types.h +++ b/profiler_gui/common_types.h @@ -56,6 +56,7 @@ #ifndef EASY_PROFILER__GUI_COMMON_TYPES_H #define EASY_PROFILER__GUI_COMMON_TYPES_H +#include #include #include #include @@ -168,6 +169,16 @@ public: void restore() { m_ref = m_restore; } }; +////////////////////////////////////////////////////////////////////////// + +template +struct Counter +{ + T count = 0; +}; + +using DurationsCountMap = std::map >; + } // END of namespace profiler_gui. template diff --git a/profiler_gui/globals.cpp b/profiler_gui/globals.cpp index e59c738..0cd552e 100644 --- a/profiler_gui/globals.cpp +++ b/profiler_gui/globals.cpp @@ -108,6 +108,7 @@ Globals::Globals() , enable_zero_length(true) , add_zero_blocks_to_hierarchy(false) , draw_graphics_items_borders(true) + , draw_histogram_borders(true) , hide_narrow_children(false) , hide_minsize_blocks(false) , display_only_relevant_stats(false) diff --git a/profiler_gui/globals.h b/profiler_gui/globals.h index aeca179..962d8cd 100644 --- a/profiler_gui/globals.h +++ b/profiler_gui/globals.h @@ -77,6 +77,8 @@ namespace profiler_gui { const QColor RULER_COLOR2 = QColor::fromRgba(0x40000000 | (::profiler::colors::Dark & 0x00ffffff));// 0x40408040); const QColor TEXT_COLOR = QColor::fromRgb(0xff504040); const QColor SYSTEM_BORDER_COLOR = QColor::fromRgb(0xffc4c4c4); + + EASY_CONSTEXPR QRgb BLOCK_BORDER_COLOR = profiler::colors::Grey600 & 0x00ffffff; EASY_CONSTEXPR QRgb SELECTED_THREAD_BACKGROUND = 0xffe0e060; EASY_CONSTEXPR QRgb SELECTED_THREAD_FOREGROUND = 0xffffffff - (SELECTED_THREAD_BACKGROUND & 0x00ffffff); @@ -240,6 +242,7 @@ namespace profiler_gui { bool enable_zero_length; ///< Enable zero length blocks (if true, then such blocks will have width == 1 pixel on each scale) bool add_zero_blocks_to_hierarchy; ///< Enable adding zero blocks into hierarchy tree bool draw_graphics_items_borders; ///< Draw borders for graphics blocks or not + bool draw_histogram_borders; ///< Draw borders for histogram columns or not bool hide_narrow_children; ///< Hide children for narrow graphics blocks (See blocks_narrow_size) bool hide_minsize_blocks; ///< Hide blocks which screen size is less than blocks_size_min bool display_only_relevant_stats; ///< Display only relevant information in ProfTreeWidget (excludes min, max, average times if there are only 1 calls number) diff --git a/profiler_gui/graphics_block_item.cpp b/profiler_gui/graphics_block_item.cpp index bbd6148..f8d322c 100644 --- a/profiler_gui/graphics_block_item.cpp +++ b/profiler_gui/graphics_block_item.cpp @@ -58,6 +58,8 @@ ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// +namespace { + enum BlockItemState : int8_t { BLOCK_ITEM_DO_PAINT_FIRST = -2, @@ -71,7 +73,7 @@ enum BlockItemState : int8_t EASY_CONSTEXPR int MIN_SYNC_SPACING = 1; EASY_CONSTEXPR int MIN_SYNC_SIZE = 3; EASY_CONSTEXPR int EVENT_HEIGHT = 4; -EASY_CONSTEXPR QRgb BORDERS_COLOR = profiler::colors::Grey600 & 0x00ffffff;// 0x00686868; +EASY_CONSTEXPR auto BORDERS_COLOR = profiler_gui::BLOCK_BORDER_COLOR; inline QRgb selectedItemBorderColor(profiler::color_t _color) { return ::profiler_gui::isLightColor(_color, 192) ? profiler::colors::Black : profiler::colors::RichRed; @@ -91,7 +93,14 @@ EASY_FORCE_INLINE void restoreItemFont(QPainter* /*painter*/) //painter->setFont(EASY_GLOBALS.font.item); } -const QPen HIGHLIGHTER_PEN = ([]() -> QPen { QPen p(profiler::colors::Black); p.setStyle(Qt::DotLine); p.setWidth(2); return p; })(); +const QPen HIGHLIGHTER_PEN = ([]() -> QPen { + QPen p(profiler::colors::Black); + p.setStyle(Qt::DotLine); + p.setWidth(2); + return p; +})(); + +} // end of namespace . #ifdef max #undef max diff --git a/profiler_gui/graphics_scrollbar.cpp b/profiler_gui/graphics_scrollbar.cpp index e026e4e..ff267e9 100644 --- a/profiler_gui/graphics_scrollbar.cpp +++ b/profiler_gui/graphics_scrollbar.cpp @@ -61,9 +61,12 @@ #include "graphics_scrollbar.h" #include "globals.h" +namespace { + ////////////////////////////////////////////////////////////////////////// EASY_CONSTEXPR int HIST_COLUMN_MIN_HEIGHT = 2; +EASY_CONSTEXPR qreal HIST_COLUMN_MIN_WIDTH_WITH_BORDER = 3; ////////////////////////////////////////////////////////////////////////// @@ -78,16 +81,24 @@ inline qreal calculate_color2(qreal, qreal duration, qreal k) return std::min(sqr(sqr(duration)) * k, 0.9999999); } +} // end of namespace . + ////////////////////////////////////////////////////////////////////////// GraphicsHistogramItem::GraphicsHistogramItem() : Parent() - , m_threadDuration(0) - , m_threadProfiledTime(0) - , m_threadWaitTime(0) - , m_pSource(nullptr) , m_workerTopDuration(0) , m_workerBottomDuration(0) , m_blockTotalDuraion(0) + , m_threadDuration(0) + , m_threadProfiledTime(0) + , m_threadWaitTime(0) + , m_medianDuration(0) + , m_avgDuration(0) + , m_medianDurationFull(0) + , m_avgDurationFull(0) + , m_workerMedianDuration(0) + , m_workerAvgDuration(0) + , m_pSource(nullptr) , m_pProfilerThread(nullptr) , m_threadId(0) , m_blockId(profiler_gui::numeric_max()) @@ -161,7 +172,6 @@ void GraphicsHistogramItem::paintByPtr(QPainter* _painter) const auto dtime = m_topValue - m_bottomValue; const auto coeff = m_boundingRect.height() / (dtime > 1e-3 ? dtime : 1.); - QRectF rect; QBrush brush(Qt::SolidPattern); //QRgb previousColor = 0; @@ -178,11 +188,33 @@ void GraphicsHistogramItem::paintByPtr(QPainter* _painter) qreal top_width = width, bottom_width = width; const auto font_h = widget->fontHeight(); - rect.setRect(0, m_boundingRect.top() - widget->margin(), width - 3, font_h); - _painter->setPen(profiler_gui::TEXT_COLOR); - _painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip, bindMode ? " Mode: Zoom" : " Mode: Overview"); + QRectF bottomRect(3, bottom + 2, width - 3, font_h); + QRectF topRect(3, m_boundingRect.top() - widget->margin(), width - 3, font_h); + QRectF textBounds; + // MODE + { + _painter->setPen(Qt::blue); + _painter->drawText( + topRect, + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip | Qt::TextIncludeTrailingSpaces, + QStringLiteral("MODE: "), + &textBounds + ); + topRect.adjust(textBounds.width(), 0, 0, 0); + + _painter->setPen(profiler_gui::TEXT_COLOR); + _painter->drawText( + topRect, + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip, + bindMode ? "Zoom" : "Overview", + &textBounds + ); + topRect.adjust(textBounds.width() + 3, 0, 0, 0); + } + + // TOP & BOTTOM duration if (!isEmpty() && !m_topDurationStr.isEmpty()) { if (m_timeUnits != EASY_GLOBALS.time_units) @@ -190,17 +222,24 @@ void GraphicsHistogramItem::paintByPtr(QPainter* _painter) m_timeUnits = EASY_GLOBALS.time_units; m_topDurationStr = profiler_gui::timeStringReal(m_timeUnits, m_topValue, 3); m_bottomDurationStr = profiler_gui::timeStringReal(m_timeUnits, m_bottomValue, 3); + + if (m_avgDuration != 0 || m_medianDuration != 0) + { + m_medianDurationStr = profiler_gui::timeStringRealNs(m_timeUnits, m_medianDuration, 3); + m_avgDurationStr = profiler_gui::timeStringRealNs(m_timeUnits, m_avgDuration, 3); + } } //auto fm = _painter->fontMetrics(); //top_width -= fm.width(m_topDurationStr) + 7; _painter->setPen(m_topValue < m_maxValue ? QColor(Qt::darkRed) : profiler_gui::TEXT_COLOR); - _painter->drawText(rect, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, m_topDurationStr); + _painter->drawText(topRect, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, m_topDurationStr, &textBounds); + topRect.adjust(0, 0, -textBounds.width() - 3, 0); - rect.setRect(0, bottom + 2, width - 3, font_h); _painter->setPen(m_bottomValue > m_minValue ? QColor(Qt::darkRed) : profiler_gui::TEXT_COLOR); - _painter->drawText(rect, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, m_bottomDurationStr); + _painter->drawText(bottomRect, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, m_bottomDurationStr, &textBounds); + bottomRect.adjust(0, 0, -textBounds.width() - 3, 0); } _painter->setPen(Qt::darkGray); @@ -209,35 +248,70 @@ void GraphicsHistogramItem::paintByPtr(QPainter* _painter) paintMouseIndicator(_painter, m_boundingRect.top(), bottom, width, m_boundingRect.height(), top_width, m_mousePos.y(), dtime, font_h); - if (!isEmpty() && m_bottomValue < EASY_GLOBALS.frame_time && EASY_GLOBALS.frame_time < m_topValue) + // MEDIAN & EXPECTED FRAME TIME + if (!isEmpty()) { - // Draw marker displaying expected frame_time step - const auto h = bottom - (EASY_GLOBALS.frame_time - m_bottomValue) * coeff; - _painter->setPen(Qt::DashLine); + const qreal mdn = m_medianDuration * 1e-3; - auto w = width; - const auto boundary = widget->margin() - font_h; - if (h < (m_boundingRect.top() - boundary)) - w = top_width; - else if (h > (bottom + boundary)) - w = bottom_width; + if (m_bottomValue < mdn && mdn < m_topValue) + { + // Draw marker displaying expected frame_time step + const auto h = bottom - (mdn - m_bottomValue) * coeff; + _painter->setPen(Qt::DashDotDotLine); - _painter->drawLine(QLineF(0, h, w, h)); + auto w = width; + const auto boundary = widget->margin() - font_h; + if (h < (m_boundingRect.top() - boundary)) + w = top_width; + else if (h > (bottom + boundary)) + w = bottom_width; + + _painter->drawLine(QLineF(0, h, w, h)); + } + + if (m_bottomValue < EASY_GLOBALS.frame_time && EASY_GLOBALS.frame_time < m_topValue) + { + // Draw marker displaying expected frame_time step + const auto h = bottom - (EASY_GLOBALS.frame_time - m_bottomValue) * coeff; + _painter->setPen(Qt::DashLine); + + auto w = width; + const auto boundary = widget->margin() - font_h; + if (h < (m_boundingRect.top() - boundary)) + w = top_width; + else if (h > (bottom + boundary)) + w = bottom_width; + + _painter->drawLine(QLineF(0, h, w, h)); + } } _painter->setPen(profiler_gui::TEXT_COLOR); - rect.setRect(0, bottom + 2, width, font_h); - const auto eventsSize = m_pProfilerThread->events.size(); - _painter->drawText(rect, Qt::AlignCenter | Qt::TextDontClip, QString("%1 | duration: %2 | profiled: %3 (%4%) | wait: %5 (%6%) | %7 frames | %8 blocks | %9 markers") - .arg(m_threadName) - .arg(profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, m_threadDuration)) - .arg(profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, m_threadProfiledTime)) - .arg(m_threadDuration ? QString::number(100. * (double)m_threadProfiledTime / (double)m_threadDuration, 'f', 2) : QString("0")) - .arg(profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, m_threadWaitTime)) - .arg(m_threadDuration ? QString::number(100. * (double)m_threadWaitTime / (double)m_threadDuration, 'f', 2) : QString("0")) - .arg(m_pProfilerThread->frames_number) - .arg(m_pProfilerThread->blocks_number - eventsSize) - .arg(eventsSize)); + const auto eventsCount = m_pProfilerThread->events.size(); + const auto blocksCount = m_pProfilerThread->blocks_number - eventsCount; + + QString durationsStr; + if (!m_medianDurationStr.isEmpty() || !m_avgDurationStr.isEmpty()) + { + durationsStr = QString("avg: %1 | mdn: %2 | ").arg(m_avgDurationStr).arg(m_medianDurationStr); + } + + _painter->drawText(topRect, Qt::AlignCenter, m_threadName); + + _painter->drawText(bottomRect, Qt::AlignCenter, + QStringLiteral("time: %1 | profiled: %2 (%3%) | wait: %4 (%5%) | %6%7 frames | %8 blocks | %9 markers") + .arg(profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, m_threadDuration)) + .arg(profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, m_threadProfiledTime)) + .arg(m_threadDuration ? QString::number(100. * (double) m_threadProfiledTime / (double) m_threadDuration, 'f', 2) + : QString("0")) + .arg(profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, m_threadWaitTime)) + .arg(m_threadDuration ? QString::number(100. * (double) m_threadWaitTime / (double) m_threadDuration, 'f', 2) + : QString("0")) + .arg(durationsStr) + .arg(profiler_gui::shortenCountString(m_pProfilerThread->frames_number)) + .arg(profiler_gui::shortenCountString(blocksCount)) + .arg(profiler_gui::shortenCountString(eventsCount)) + ); _painter->restore(); } @@ -252,7 +326,6 @@ void GraphicsHistogramItem::paintById(QPainter* _painter) const auto dtime = m_topValue - m_bottomValue; const auto coeff = m_boundingRect.height() / (dtime > 1e-3 ? dtime : 1.); - QRectF rect; QBrush brush(Qt::SolidPattern); //QRgb previousColor = 0; @@ -270,11 +343,33 @@ void GraphicsHistogramItem::paintById(QPainter* _painter) qreal top_width = width, bottom_width = width; const auto font_h = widget->fontHeight(); - rect.setRect(0, m_boundingRect.top() - widget->margin(), width - 3, font_h); - _painter->setPen(profiler_gui::TEXT_COLOR); - _painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip, bindMode ? " Mode: Zoom" : " Mode: Overview"); + QRectF bottomRect(3, bottom + 2, width - 3, font_h); + QRectF topRect(3, m_boundingRect.top() - widget->margin(), width - 3, font_h); + QRectF textBounds; + // MODE + { + _painter->setPen(Qt::blue); + _painter->drawText( + topRect, + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip | Qt::TextIncludeTrailingSpaces, + QStringLiteral("MODE: "), + &textBounds + ); + topRect.adjust(textBounds.width(), 0, 0, 0); + + _painter->setPen(profiler_gui::TEXT_COLOR); + _painter->drawText( + topRect, + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip, + bindMode ? "Zoom" : "Overview", + &textBounds + ); + topRect.adjust(textBounds.width() + 3, 0, 0, 0); + } + + // TOP & BOTTOM duration if (!isEmpty() && !m_topDurationStr.isEmpty()) { if (m_timeUnits != EASY_GLOBALS.time_units) @@ -282,17 +377,24 @@ void GraphicsHistogramItem::paintById(QPainter* _painter) m_timeUnits = EASY_GLOBALS.time_units; m_topDurationStr = profiler_gui::timeStringReal(m_timeUnits, m_topValue, 3); m_bottomDurationStr = profiler_gui::timeStringReal(m_timeUnits, m_bottomValue, 3); + + if (m_avgDuration != 0 || m_medianDuration != 0) + { + m_medianDurationStr = profiler_gui::timeStringRealNs(m_timeUnits, m_medianDuration, 3); + m_avgDurationStr = profiler_gui::timeStringRealNs(m_timeUnits, m_avgDuration, 3); + } } //auto fm = _painter->fontMetrics(); //top_width -= fm.width(m_topDurationStr) + 7; _painter->setPen(m_topValue < m_maxValue ? QColor(Qt::darkRed) : profiler_gui::TEXT_COLOR); - _painter->drawText(rect, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, m_topDurationStr); + _painter->drawText(topRect, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, m_topDurationStr, &textBounds); + topRect.adjust(0, 0, -textBounds.width() - 3, 0); - rect.setRect(0, bottom + 2, width - 3, font_h); _painter->setPen(m_bottomValue > m_minValue ? QColor(Qt::darkRed) : profiler_gui::TEXT_COLOR); - _painter->drawText(rect, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, m_bottomDurationStr); + _painter->drawText(bottomRect, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, m_bottomDurationStr, &textBounds); + bottomRect.adjust(0, 0, -textBounds.width() - 3, 0); } _painter->setPen(Qt::darkGray); @@ -301,44 +403,89 @@ void GraphicsHistogramItem::paintById(QPainter* _painter) paintMouseIndicator(_painter, m_boundingRect.top(), bottom, width, m_boundingRect.height(), top_width, m_mousePos.y(), dtime, font_h); - if (!isEmpty() && m_bottomValue < EASY_GLOBALS.frame_time && EASY_GLOBALS.frame_time < m_topValue) + // MEDIAN & EXPECTED FRAME TIME + if (!isEmpty()) { - // Draw marker displaying required frame_time step - const auto h = bottom - (EASY_GLOBALS.frame_time - m_bottomValue) * coeff; - _painter->setPen(Qt::DashLine); + const qreal mdn = m_medianDuration * 1e-3; - auto w = width; - const auto boundary = widget->margin() - font_h; - if (h < (m_boundingRect.top() - boundary)) - w = top_width; - else if (h >(bottom + boundary)) - w = bottom_width; + if (m_bottomValue < mdn && mdn < m_topValue) + { + // Draw marker displaying expected frame_time step + const auto h = bottom - (mdn - m_bottomValue) * coeff; + _painter->setPen(Qt::DashDotDotLine); - _painter->drawLine(QLineF(0, h, w, h)); + auto w = width; + const auto boundary = widget->margin() - font_h; + if (h < (m_boundingRect.top() - boundary)) + w = top_width; + else if (h > (bottom + boundary)) + w = bottom_width; + + _painter->drawLine(QLineF(0, h, w, h)); + } + + if (m_bottomValue < EASY_GLOBALS.frame_time && EASY_GLOBALS.frame_time < m_topValue) + { + // Draw marker displaying required frame_time step + const auto h = bottom - (EASY_GLOBALS.frame_time - m_bottomValue) * coeff; + _painter->setPen(Qt::DashLine); + + auto w = width; + const auto boundary = widget->margin() - font_h; + if (h < (m_boundingRect.top() - boundary)) + w = top_width; + else if (h > (bottom + boundary)) + w = bottom_width; + + _painter->drawLine(QLineF(0, h, w, h)); + } } + const auto colorIndicatorSize = font_h * 2 / 3; + + _painter->setBrush(QColor::fromRgba(easyDescriptor(m_blockId).color())); + _painter->setPen(profiler_gui::BLOCK_BORDER_COLOR); + _painter->drawRect(QRectF(bottomRect.left(), bottomRect.top() + 1 + font_h / 6., colorIndicatorSize, colorIndicatorSize)); + bottomRect.adjust(colorIndicatorSize + 3, 0, 0, 0); + _painter->setPen(profiler_gui::TEXT_COLOR); - rect.setRect(0, bottom + 2, width, font_h); + _painter->drawText(topRect, Qt::AlignCenter, m_threadName); + + _painter->drawText(bottomRect, Qt::AlignLeft, m_blockName, &textBounds); + bottomRect.adjust(textBounds.width() + 3, 0, 0, 0); + + _painter->drawText(bottomRect, Qt::AlignLeft, QStringLiteral("| %1").arg(m_blockType), &textBounds); + bottomRect.adjust(textBounds.width() + 3, 0, 0, 0); if (!items.empty()) { + QString durationsStr; + if (!m_medianDurationStr.isEmpty() || !m_avgDurationStr.isEmpty()) + { + durationsStr = QString("avg: %1 | mdn: %2 | ").arg(m_avgDurationStr).arg(m_medianDurationStr); + } + if (m_threadProfiledTime != 0) { - _painter->drawText(rect, Qt::AlignCenter | Qt::TextDontClip, - QString("%1 | %2 | %3 calls | %4% of thread profiled time") - .arg(m_threadName).arg(m_blockName).arg(items.size()) - .arg(QString::number(100. * (double)m_blockTotalDuraion / (double)m_threadProfiledTime, 'f', 2))); + _painter->drawText(bottomRect, Qt::AlignCenter, + QStringLiteral("%1 calls | %2%3% of thread profiled time") + .arg(items.size()) + .arg(durationsStr) + .arg(QString::number(100. * (double) m_blockTotalDuraion / (double) m_threadProfiledTime, 'f', 2)) + ); } else { - _painter->drawText(rect, Qt::AlignCenter | Qt::TextDontClip, - QString("%1 | %2 | %3 calls | 100% of thread profiled time") - .arg(m_threadName).arg(m_blockName).arg(items.size())); + _painter->drawText(bottomRect, Qt::AlignCenter, + QStringLiteral("%1 calls | %2100% of thread profiled time") + .arg(items.size()) + .arg(durationsStr) + ); } } else { - _painter->drawText(rect, Qt::AlignCenter | Qt::TextDontClip, QString("%1 | %2 | 0 calls").arg(m_threadName).arg(m_blockName)); + _painter->drawText(bottomRect, Qt::AlignCenter, QStringLiteral("0 calls")); } _painter->restore(); @@ -379,7 +526,12 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, const pr m_boundaryTimer.stop(); m_blockName.clear(); + m_blockType = QStringLiteral("Thread"); m_blockTotalDuraion = 0; + m_medianDuration = 0; + m_avgDuration = 0; + m_medianDurationFull = 0; + m_avgDurationFull = 0; m_imageOriginUpdate = m_imageOrigin = 0; m_imageScaleUpdate = m_imageScale = 1; @@ -388,6 +540,11 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, const pr setEmpty(true); { profiler::BlocksTree::children_t().swap(m_selectedBlocks); } + m_topDurationStr.clear(); + m_bottomDurationStr.clear(); + m_medianDurationStr.clear(); + m_avgDurationStr.clear(); + setImageUpdatePermitted(false); m_regime = Hist_Pointer; m_pSource = _items; @@ -423,15 +580,25 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, const pr m_maxValue = 0; m_minValue = 1e30; + size_t totalCount = 0; + profiler_gui::DurationsCountMap durations; bool empty = true; for (const auto& item : *source) { if (isReady()) return; - if (easyDescriptor(easyBlock(item.block).tree.node->id()).type() == profiler::BlockType::Event) + auto& block = easyBlock(item.block).tree; + auto& desc = easyDescriptor(block.node->id()); + if (desc.type() != profiler::BlockType::Block) continue; + const auto duration = block.node->duration(); + + ++totalCount; + ++durations[duration].count; + m_avgDuration += duration; + const auto w = item.width(); if (w > m_maxValue) @@ -443,6 +610,12 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, const pr empty = false; } + if (!empty) + { + m_avgDuration /= totalCount; + m_medianDuration = profiler_gui::calculateMedian(durations); + } + if ((m_maxValue - m_minValue) < 1e-3) { if (m_minValue > 0.1) @@ -464,10 +637,13 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, const pr m_topDurationStr = profiler_gui::timeStringReal(m_timeUnits, m_topValue, 3); m_bottomDurationStr = profiler_gui::timeStringReal(m_timeUnits, m_bottomValue, 3); } - else + + if (m_avgDuration != 0 || m_medianDuration != 0) { - m_topDurationStr.clear(); - m_bottomDurationStr.clear(); + m_avgDurationStr = profiler_gui::timeStringRealNs(m_timeUnits, m_avgDuration, 3); + m_medianDurationStr = profiler_gui::timeStringRealNs(m_timeUnits, m_medianDuration, 3); + m_medianDurationFull = m_medianDuration; + m_avgDurationFull = m_avgDuration; } setEmpty(empty); @@ -483,8 +659,6 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, const pr if (m_pSource == nullptr) { m_pProfilerThread = nullptr; - m_topDurationStr.clear(); - m_bottomDurationStr.clear(); m_threadName.clear(); hide(); } @@ -505,8 +679,15 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, profiler m_pSource = nullptr; m_topDurationStr.clear(); m_bottomDurationStr.clear(); + m_medianDurationStr.clear(); + m_avgDurationStr.clear(); m_blockName.clear(); + m_blockType.clear(); m_blockTotalDuraion = 0; + m_medianDuration = 0; + m_avgDuration = 0; + m_medianDurationFull = 0; + m_avgDurationFull = 0; m_imageOriginUpdate = m_imageOrigin = 0; m_imageScaleUpdate = m_imageScale = 1; @@ -520,7 +701,31 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, profiler if (m_threadId != 0 && !profiler_gui::is_max(m_blockId)) { - m_blockName = profiler_gui::toUnicode(easyDescriptor(m_blockId).name()); + auto& desc = easyDescriptor(m_blockId); + m_blockName = profiler_gui::toUnicode(desc.name()); + + switch (desc.type()) + { + case profiler::BlockType::Block: + { + m_blockType = QStringLiteral("Block"); + break; + } + + case profiler::BlockType::Event: + { + m_blockType = QStringLiteral("Event"); + break; + } + + case profiler::BlockType::Value: + { + m_blockType = QStringLiteral("Value"); + break; + } + + default: break; + } const auto& root = EASY_GLOBALS.profiler_blocks[_thread_id]; m_threadName = profiler_gui::decoratedThreadName(EASY_GLOBALS.use_decorated_thread_name, root, EASY_GLOBALS.hex_thread_id); @@ -564,6 +769,8 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, profiler const bool has_selected_block = !profiler_gui::is_max(selected_block); + size_t totalCount = 0; + profiler_gui::DurationsCountMap durations; for (auto frame : profiler_thread.children) { const auto& frame_block = easyBlock(frame).tree; @@ -578,6 +785,8 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, profiler if (w < m_minValue) m_minValue = w; + ++totalCount; + ++durations[w].count; m_blockTotalDuraion += w; } @@ -611,6 +820,8 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, profiler if (w < m_minValue) m_minValue = w; + ++totalCount; + ++durations[w].count; m_blockTotalDuraion += w; } @@ -633,6 +844,8 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, profiler { m_topDurationStr.clear(); m_bottomDurationStr.clear(); + m_medianDurationStr.clear(); + m_avgDurationStr.clear(); } else { @@ -659,8 +872,20 @@ void GraphicsHistogramItem::setSource(profiler::thread_id_t _thread_id, profiler } } + m_avgDuration = m_blockTotalDuraion / totalCount; + m_medianDuration = profiler_gui::calculateMedian(durations); + + m_medianDurationFull = m_medianDuration; + m_avgDurationFull = m_avgDuration; + m_topDurationStr = profiler_gui::timeStringReal(m_timeUnits, m_maxValue, 3); m_bottomDurationStr = profiler_gui::timeStringReal(m_timeUnits, m_minValue, 3); + + if (m_avgDuration != 0 || m_medianDuration != 0) + { + m_avgDurationStr = profiler_gui::timeStringRealNs(m_timeUnits, m_avgDuration, 3); + m_medianDurationStr = profiler_gui::timeStringRealNs(m_timeUnits, m_medianDuration, 3); + } } m_topValue = m_maxValue; @@ -831,9 +1056,10 @@ bool GraphicsHistogramItem::updateImage() const auto frameTime = EASY_GLOBALS.frame_time; const auto beginTime = EASY_GLOBALS.begin_time; const auto autoHeight = EASY_GLOBALS.auto_adjust_histogram_height; + const auto drawBorders = EASY_GLOBALS.draw_histogram_borders; m_worker.enqueue([=] { updateImageAsync(rect, regime, scale, left, right, right - left, value, window, top, bottom, bindMode, - frameTime, beginTime, autoHeight); + frameTime, beginTime, autoHeight, drawBorders); }, m_bReady); return true; @@ -849,12 +1075,29 @@ void GraphicsHistogramItem::onImageUpdated() m_topDurationStr = profiler_gui::timeStringReal(m_timeUnits, m_topValue, 3); m_bottomDurationStr = profiler_gui::timeStringReal(m_timeUnits, m_bottomValue, 3); } + + if (static_cast(scene()->parent())->bindMode()) + { + m_medianDuration = m_workerMedianDuration; + m_avgDuration = m_workerAvgDuration; + } + else + { + m_medianDuration = m_medianDurationFull; + m_avgDuration = m_avgDurationFull; + } + + if (m_avgDuration != 0 || m_medianDuration != 0) + { + m_medianDurationStr = profiler_gui::timeStringRealNs(m_timeUnits, m_medianDuration, 3); + m_avgDurationStr = profiler_gui::timeStringRealNs(m_timeUnits, m_avgDuration, 3); + } } void GraphicsHistogramItem::updateImageAsync(QRectF _boundingRect, HistRegime _regime, qreal _current_scale, qreal _minimum, qreal _maximum, qreal _range, qreal _value, qreal _width, qreal _top_duration, qreal _bottom_duration, - bool _bindMode, float _frame_time, profiler::timestamp_t _begin_time, bool _autoAdjustHist) + bool _bindMode, float _frame_time, profiler::timestamp_t _begin_time, bool _autoAdjustHist, bool _drawBorders) { const auto bottom = _boundingRect.height();//_boundingRect.bottom(); const auto screenWidth = _boundingRect.width() * _current_scale; @@ -899,6 +1142,11 @@ void GraphicsHistogramItem::updateImageAsync(QRectF _boundingRect, HistRegime _r auto const calculate_color = gotFrame ? calculate_color2 : calculate_color1; auto const k = gotFrame ? sqr(sqr(frameCoeff)) : 1.0 / _boundingRect.height(); + size_t totalCount = 0; + m_workerMedianDuration = 0; + m_workerAvgDuration = 0; + profiler_gui::DurationsCountMap durations; + if (_regime == Hist_Pointer) { const auto& items = *m_pSource; @@ -914,7 +1162,7 @@ void GraphicsHistogramItem::updateImageAsync(QRectF _boundingRect, HistRegime _r realScale *= viewScale; offset = _minimum * realScale; - first = std::lower_bound(items.begin(), items.end(), _minimum, [](const profiler_gui::EasyBlockItem& _item, qreal _value) + first = std::lower_bound(items.begin(), items.end(), _minimum, [] (const profiler_gui::EasyBlockItem& _item, qreal _value) { return _item.left() < _value; }); @@ -977,6 +1225,10 @@ void GraphicsHistogramItem::updateImageAsync(QRectF _boundingRect, HistRegime _r const auto dtime = _top_duration - _bottom_duration; const auto coeff = _boundingRect.height() / (dtime > 1e-3 ? dtime : 1.); + const qreal minWidth = _drawBorders ? HIST_COLUMN_MIN_WIDTH_WITH_BORDER : 1.; + if (_drawBorders) + p.setPen(profiler_gui::BLOCK_BORDER_COLOR); + for (auto it = first, end = items.end(); it != end; ++it) { // Draw rectangle @@ -986,16 +1238,97 @@ void GraphicsHistogramItem::updateImageAsync(QRectF _boundingRect, HistRegime _r if (it->right() < _minimum) continue; - const qreal item_x = it->left() * realScale - offset; - const qreal item_w = std::max(it->width() * realScale, 1.0); - const qreal item_r = item_x + item_w; - const qreal h = it->width() <= _bottom_duration ? HIST_COLUMN_MIN_HEIGHT : - (it->width() > _top_duration ? maxColumnHeight : (it->width() - _bottom_duration) * coeff); + if (_bindMode) + { + // calculate avg and median + const auto duration = easyBlock(it->block).tree.node->duration(); + m_workerAvgDuration += duration; + ++totalCount; + ++durations[duration].count; + } + + auto maxItemWidth = it->width(); + + // calculate column width and height + qreal item_x = it->left() * realScale - offset; + qreal item_w = it->width() * realScale; + qreal item_r = item_x + item_w; + const auto width = it->width(); + qreal h = width <= _bottom_duration ? HIST_COLUMN_MIN_HEIGHT : + (width > _top_duration ? maxColumnHeight : (width - _bottom_duration) * coeff); + + if (_drawBorders) + { + if (item_r < previous_x) + { + item_w -= previous_x - item_r; + item_x = previous_x; + item_r = item_x + item_w; + } + + // if column width < minWidth then try to merge several columns together + auto jt = it; + while (item_w < minWidth && jt != end) + { + if (jt->left() > _maximum) + break; + + const qreal jx = jt->left() * realScale - offset; + auto dx = jx - item_r; + if (dx > std::max(item_w, minWidth)) + { + item_w = minWidth; + break; + } + + const qreal jw = jt->width() * realScale; + if (jw > (minWidth + item_w)) + { + item_w = minWidth; + break; + } + + const auto jwidth = jt->width(); + const qreal jh = jwidth <= _bottom_duration ? HIST_COLUMN_MIN_HEIGHT : + (jwidth > _top_duration ? maxColumnHeight : (jwidth - _bottom_duration) * coeff); + + item_w += jw; + h = std::max(h, jh); + maxItemWidth = std::max(maxItemWidth, jwidth); + ++jt; + } + + item_r = item_x + item_w; + + if (_bindMode) + { + // if merged several columns then avg and median should be calculated for these columns too + for (auto it2 = it; it2 != jt; ++it2) + { + const auto duration = easyBlock(it2->block).tree.node->duration(); + m_workerAvgDuration += duration; + ++totalCount; + ++durations[duration].count; + } + } + + if (jt != it) + { + // bypass merged columns + it = jt; + --it; + } + } + else if (item_w < 1) + { + item_w = 1; + item_r = item_x + 1; + } if (h < previous_h && item_r < previous_x) continue; - const auto col = calculate_color(h, it->width(), k); + const auto col = calculate_color(h, maxItemWidth, k); const auto color = 0x00ffffff & QColor::fromHsvF((1.0 - col) * 0.375, 0.85, 0.85).rgb(); if (previousColor != color) @@ -1100,6 +1433,10 @@ void GraphicsHistogramItem::updateImageAsync(QRectF _boundingRect, HistRegime _r const auto dtime = _top_duration - _bottom_duration; const auto coeff = _boundingRect.height() / (dtime > 1e-3 ? dtime : 1.); + const qreal minWidth = _drawBorders ? HIST_COLUMN_MIN_WIDTH_WITH_BORDER : 1.; + if (_drawBorders) + p.setPen(profiler_gui::BLOCK_BORDER_COLOR); + for (auto it = first, end = m_selectedBlocks.end(); it != end; ++it) { // Draw rectangle @@ -1113,17 +1450,100 @@ void GraphicsHistogramItem::updateImageAsync(QRectF _boundingRect, HistRegime _r if (endTime < _minimum) continue; + if (_bindMode) + { + // calculate avg and median + const auto duration = item->duration(); + m_workerAvgDuration += duration; + ++totalCount; + ++durations[duration].count; + } + const qreal duration = item->duration() * 1e-3; - const qreal item_x = (beginTime * realScale - offset) * 1e-3; - const qreal item_w = std::max(duration * realScale, 1.0); - const qreal item_r = item_x + item_w; - const auto h = duration <= _bottom_duration ? HIST_COLUMN_MIN_HEIGHT : + auto maxItemDuration = duration; + + // calculate column width and height + qreal item_x = (beginTime * realScale - offset) * 1e-3; + qreal item_w = duration * realScale; + qreal item_r = item_x + item_w; + auto h = duration <= _bottom_duration ? HIST_COLUMN_MIN_HEIGHT : (duration > _top_duration ? maxColumnHeight : (duration - _bottom_duration) * coeff); + if (_drawBorders) + { + if (item_r < previous_x) + { + item_w -= previous_x - item_r; + item_x = previous_x; + item_r = item_x + item_w; + } + + // if column width < minWidth then try to merge several columns together + auto jt = it; + while (item_w < minWidth && jt != end) + { + const auto jtem = easyBlock(*jt).tree.node; + const auto jbeginTime = jtem->begin() - _begin_time; + if (jbeginTime > _maximum) + break; + + const qreal jduration = jtem->duration() * 1e-3; + + const qreal jx = ((jtem->begin() - _begin_time) * realScale - offset) * 1e-3; + auto dx = jx - item_r; + if (dx > std::max(item_w, minWidth)) + { + item_w = minWidth; + break; + } + + const qreal jw = jduration * realScale; + if (jw > (minWidth + item_w)) + { + item_w = minWidth; + break; + } + + const qreal jh = jduration <= _bottom_duration ? HIST_COLUMN_MIN_HEIGHT : + (jduration > _top_duration ? maxColumnHeight : (jduration - _bottom_duration) * coeff); + + item_w += jw; + h = std::max(h, jh); + maxItemDuration = std::max(maxItemDuration, jduration); + ++jt; + } + + item_r = item_x + item_w; + + if (_bindMode) + { + // if merged several columns then avg and median should be calculated for these columns too + for (auto it2 = it; it2 != jt; ++it2) + { + const auto duration = easyBlock(*it2).tree.node->duration(); + m_workerAvgDuration += duration; + ++totalCount; + ++durations[duration].count; + } + } + + if (jt != it) + { + // bypass merged columns + it = jt; + --it; + } + } + else if (item_w < 1) + { + item_w = 1; + item_r = item_x + 1; + } + if (h < previous_h && item_r < previous_x) continue; - const auto col = calculate_color(h, duration, k); + const auto col = calculate_color(h, maxItemDuration, k); const auto color = 0x00ffffff & QColor::fromHsvF((1.0 - col) * 0.375, 0.85, 0.85).rgb(); if (previousColor != color) @@ -1145,6 +1565,12 @@ void GraphicsHistogramItem::updateImageAsync(QRectF _boundingRect, HistRegime _r m_workerTopDuration = _top_duration; m_workerBottomDuration = _bottom_duration; + if (_bindMode) + { + m_workerAvgDuration /= totalCount; + m_workerMedianDuration = profiler_gui::calculateMedian(durations); + } + setReady(true); } @@ -1166,7 +1592,7 @@ GraphicsScrollbar::GraphicsScrollbar(int _initialHeight, QWidget* _parent) m_histogramItem->hide(); connect(&EASY_GLOBALS.events, &profiler_gui::GlobalSignals::expectedFrameTimeChanged, - this, &This::onExpectedFrameTimeChanged); + this, &This::repaintHistogramImage); connect(&EASY_GLOBALS.events, &profiler_gui::GlobalSignals::autoAdjustHistogramChanged, this, &This::onAutoAdjustHistogramChanged); @@ -1187,6 +1613,15 @@ GraphicsScrollbar::~GraphicsScrollbar() ////////////////////////////////////////////////////////////////////////// +void GraphicsScrollbar::repaintHistogramImage() +{ + if (m_histogramItem->isVisible()) + { + m_histogramItem->updateImage(); + scene()->update(); + } +} + void GraphicsScrollbar::onThreadViewChanged() { if (m_histogramItem->isVisible()) @@ -1196,15 +1631,6 @@ void GraphicsScrollbar::onThreadViewChanged() } } -void GraphicsScrollbar::onExpectedFrameTimeChanged() -{ - if (m_histogramItem->isVisible()) - { - m_histogramItem->updateImage(); - scene()->update(); - } -} - void GraphicsScrollbar::onAutoAdjustHistogramChanged() { if (m_histogramItem->isVisible()) @@ -1225,7 +1651,7 @@ void GraphicsScrollbar::clear() Parent::clear(); } -profiler::thread_id_t GraphicsScrollbar::hystThread() const +profiler::thread_id_t GraphicsScrollbar::histThread() const { return m_histogramItem->threadId(); } diff --git a/profiler_gui/graphics_scrollbar.h b/profiler_gui/graphics_scrollbar.h index 94fc295..baf4070 100644 --- a/profiler_gui/graphics_scrollbar.h +++ b/profiler_gui/graphics_scrollbar.h @@ -80,12 +80,21 @@ private: profiler::timestamp_t m_blockTotalDuraion; QString m_topDurationStr; QString m_bottomDurationStr; + QString m_medianDurationStr; + QString m_avgDurationStr; QString m_threadName; QString m_blockName; + QString m_blockType; profiler::BlocksTree::children_t m_selectedBlocks; profiler::timestamp_t m_threadDuration; profiler::timestamp_t m_threadProfiledTime; profiler::timestamp_t m_threadWaitTime; + profiler::timestamp_t m_medianDuration; + profiler::timestamp_t m_avgDuration; + profiler::timestamp_t m_medianDurationFull; + profiler::timestamp_t m_avgDurationFull; + profiler::timestamp_t m_workerMedianDuration; + profiler::timestamp_t m_workerAvgDuration; const profiler_gui::EasyItems* m_pSource; const profiler::BlocksTreeRoot* m_pProfilerThread; profiler::thread_id_t m_threadId; @@ -136,7 +145,7 @@ private: void updateImageAsync(QRectF _boundingRect, HistRegime _regime, qreal _current_scale, qreal _minimum, qreal _maximum, qreal _range, qreal _value, qreal _width, qreal _top_duration, qreal _bottom_duration, bool _bindMode, - float _frame_time, profiler::timestamp_t _begin_time, bool _autoAdjustHist); + float _frame_time, profiler::timestamp_t _begin_time, bool _autoAdjustHist, bool _drawBorders); }; // END of class GraphicsHistogramItem. @@ -165,7 +174,7 @@ public: // Public non-virtual methods - profiler::thread_id_t hystThread() const; + profiler::thread_id_t histThread() const; void setHistogramSource(profiler::thread_id_t _thread_id, const profiler_gui::EasyItems* _items); void setHistogramSource(profiler::thread_id_t _thread_id, profiler::block_id_t _block_id); @@ -173,10 +182,13 @@ public: setHistogramSource(_thread_id, &_items); } +public slots: + + void repaintHistogramImage(); + private slots: void onThreadViewChanged(); - void onExpectedFrameTimeChanged(); void onAutoAdjustHistogramChanged(); void onDisplayOnlyFramesOnHistogramChanged(); diff --git a/profiler_gui/graphics_slider_area.cpp b/profiler_gui/graphics_slider_area.cpp index 0c0167e..069b33e 100644 --- a/profiler_gui/graphics_slider_area.cpp +++ b/profiler_gui/graphics_slider_area.cpp @@ -360,10 +360,10 @@ int GraphicsSliderArea::margins() const ////////////////////////////////////////////////////////////////////////// -void GraphicsSliderArea::setValue(qreal _value) +bool GraphicsSliderArea::setValue(qreal _value) { if (m_bUpdatingPos) - return; + return false; const profiler_gui::BoolFlagGuard guard(m_bUpdatingPos, true); @@ -371,7 +371,7 @@ void GraphicsSliderArea::setValue(qreal _value) if (fabs(m_value - newValue) < 2 * std::numeric_limits::epsilon()) { m_slider->setX(m_value + m_slider->halfwidth()); - return; + return false; } m_value = newValue; @@ -384,6 +384,8 @@ void GraphicsSliderArea::setValue(qreal _value) if (m_imageItem->isVisible()) m_imageItem->onValueChanged(); + + return true; } void GraphicsSliderArea::setRange(qreal _minValue, qreal _maxValue) @@ -413,7 +415,11 @@ void GraphicsSliderArea::setRange(qreal _minValue, qreal _maxValue) void GraphicsSliderArea::setSliderWidth(qreal _width) { m_slider->setWidth(_width); - setValue(m_value); + if (!setValue(m_value)) + { + if (m_imageItem->isVisible()) + m_imageItem->onValueChanged(); + } } ////////////////////////////////////////////////////////////////////////// diff --git a/profiler_gui/graphics_slider_area.h b/profiler_gui/graphics_slider_area.h index a41faed..6ce48aa 100644 --- a/profiler_gui/graphics_slider_area.h +++ b/profiler_gui/graphics_slider_area.h @@ -145,7 +145,7 @@ public: int margin() const; int margins() const; - void setValue(qreal _value); + bool setValue(qreal _value); void setRange(qreal _minValue, qreal _maxValue); void setSliderWidth(qreal _width); void setSelectionPos(qreal _left, qreal _right); diff --git a/profiler_gui/main_window.cpp b/profiler_gui/main_window.cpp index e659cf2..011910c 100644 --- a/profiler_gui/main_window.cpp +++ b/profiler_gui/main_window.cpp @@ -518,11 +518,24 @@ MainWindow::MainWindow() : Parent(), m_theme("default"), m_lastAddress("localhos menu->addSeparator(); auto submenu = menu->addMenu("View"); submenu->setToolTipsVisible(true); - action = submenu->addAction("Draw borders"); + + action = submenu->addAction("Diagram borders"); action->setToolTip("Draw borders for blocks on diagram.\nThis slightly reduces performance."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.draw_graphics_items_borders); - connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.draw_graphics_items_borders = _checked; refreshDiagram(); }); + connect(action, &QAction::triggered, [this] (bool _checked) { + EASY_GLOBALS.draw_graphics_items_borders = _checked; + refreshDiagram(); + }); + + action = submenu->addAction("Histogram borders"); + action->setToolTip("Draw borders for histogram columns."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.draw_histogram_borders); + connect(action, &QAction::triggered, [this] (bool _checked) { + EASY_GLOBALS.draw_histogram_borders = _checked; + refreshHistogramImage(); + }); action = submenu->addAction("Overlap narrow children"); action->setToolTip("Children blocks will be overlaped by narrow\nparent blocks. See also \'Blocks narrow size\'.\nThis improves performance."); @@ -929,12 +942,13 @@ MainWindow::MainWindow() : Parent(), m_theme("default"), m_lastAddress("localhos toolbar->addWidget(lbl); m_frameTimeEdit = new QLineEdit(); - m_frameTimeEdit->setFixedWidth(px(70)); + m_frameTimeEdit->setFixedWidth(px(80)); auto val = new QDoubleValidator(m_frameTimeEdit); val->setLocale(QLocale::c()); val->setBottom(0); + val->setDecimals(3); m_frameTimeEdit->setValidator(val); - m_frameTimeEdit->setText(QString::number(EASY_GLOBALS.frame_time * 1e-3)); + m_frameTimeEdit->setText(QString::number(EASY_GLOBALS.frame_time * 1e-3, 'f', 3)); connect(m_frameTimeEdit, &QLineEdit::editingFinished, this, &This::onFrameTimeEditFinish); connect(&EASY_GLOBALS.events, &profiler_gui::GlobalSignals::expectedFrameTimeChanged, this, &This::onFrameTimeChanged); toolbar->addWidget(m_frameTimeEdit); @@ -1349,6 +1363,11 @@ void MainWindow::refreshDiagram() static_cast(m_graphicsView->widget())->view()->scene()->update(); } +void MainWindow::refreshHistogramImage() +{ + static_cast(m_graphicsView->widget())->view()->repaintHistogramImage(); +} + ////////////////////////////////////////////////////////////////////////// void MainWindow::onDeleteClicked(bool) @@ -1579,7 +1598,7 @@ void MainWindow::validateLineEdits() { m_addressEdit->setFixedWidth((m_addressEdit->fontMetrics().width(QString("255.255.255.255")) * 3) / 2); m_portEdit->setFixedWidth(m_portEdit->fontMetrics().width(QString("000000")) + 10); - m_frameTimeEdit->setFixedWidth(m_frameTimeEdit->fontMetrics().width(QString("000000"))); + m_frameTimeEdit->setFixedWidth(m_frameTimeEdit->fontMetrics().width(QString("000.000")) + 5); } ////////////////////////////////////////////////////////////////////////// @@ -1723,6 +1742,10 @@ void MainWindow::loadSettings() if (!flag.isNull()) EASY_GLOBALS.draw_graphics_items_borders = flag.toBool(); + flag = settings.value("draw_histogram_borders"); + if (!flag.isNull()) + EASY_GLOBALS.draw_histogram_borders = flag.toBool(); + flag = settings.value("hide_narrow_children"); if (!flag.isNull()) EASY_GLOBALS.hide_narrow_children = flag.toBool(); @@ -1888,6 +1911,7 @@ void MainWindow::saveSettingsAndGeometry() settings.setValue("blocks_size_min", EASY_GLOBALS.blocks_size_min); settings.setValue("blocks_narrow_size", EASY_GLOBALS.blocks_narrow_size); settings.setValue("draw_graphics_items_borders", EASY_GLOBALS.draw_graphics_items_borders); + settings.setValue("draw_histogram_borders", EASY_GLOBALS.draw_histogram_borders); settings.setValue("hide_narrow_children", EASY_GLOBALS.hide_narrow_children); settings.setValue("hide_minsize_blocks", EASY_GLOBALS.hide_minsize_blocks); settings.setValue("collapse_items_on_tree_close", EASY_GLOBALS.collapse_items_on_tree_close); @@ -2571,19 +2595,12 @@ void MainWindow::onFrameTimeEditFinish() } EASY_GLOBALS.frame_time = text.toFloat() * 1e3f; - - disconnect(&EASY_GLOBALS.events, &profiler_gui::GlobalSignals::expectedFrameTimeChanged, - this, &This::onFrameTimeChanged); - emit EASY_GLOBALS.events.expectedFrameTimeChanged(); - - connect(&EASY_GLOBALS.events, &profiler_gui::GlobalSignals::expectedFrameTimeChanged, - this, &This::onFrameTimeChanged); } void MainWindow::onFrameTimeChanged() { - m_frameTimeEdit->setText(QString::number(EASY_GLOBALS.frame_time * 1e-3)); + m_frameTimeEdit->setText(QString::number(EASY_GLOBALS.frame_time * 1e-3, 'f', 3)); } ////////////////////////////////////////////////////////////////////////// diff --git a/profiler_gui/main_window.h b/profiler_gui/main_window.h index bcdf374..cc12d27 100644 --- a/profiler_gui/main_window.h +++ b/profiler_gui/main_window.h @@ -393,6 +393,7 @@ private: void clear(); void refreshDiagram(); + void refreshHistogramImage(); void addFileToList(const QString& filename, bool changeWindowTitle = true); void loadFile(const QString& filename); diff --git a/profiler_gui/tree_widget_loader.cpp b/profiler_gui/tree_widget_loader.cpp index d6eaf19..b422cda 100644 --- a/profiler_gui/tree_widget_loader.cpp +++ b/profiler_gui/tree_widget_loader.cpp @@ -89,7 +89,7 @@ struct ThreadData using ThreadDataMap = std::unordered_map >; -void calculate_medians(StatsMap::iterator begin, StatsMap::iterator end) +void calculateMedians(StatsMap::iterator begin, StatsMap::iterator end) { for (auto it = begin; it != end; ++it) { @@ -99,65 +99,7 @@ void calculate_medians(StatsMap::iterator begin, StatsMap::iterator end) continue; } - size_t total_count = 0; - for (auto& kv : durations) - { - total_count += kv.second.count; - } - - auto& stats = it->second.stats; - if (total_count & 1) - { - const auto index = total_count >> 1; - size_t i = 0; - for (auto& kv : durations) - { - const auto count = kv.second.count; - - i += count; - if (i < index) - { - continue; - } - - stats.median_duration = kv.first; - break; - } - } - else - { - const auto index2 = total_count >> 1; - const auto index1 = index2 - 1; - - size_t i = 0; - bool i1 = false; - for (auto& kv : durations) - { - const auto count = kv.second.count; - - i += count; - if (i < index1) - { - continue; - } - - if (!i1) - { - i1 = true; - stats.median_duration = kv.first; - } - - if (i < index2) - { - continue; - } - - stats.median_duration += kv.first; - stats.median_duration >>= 1; - - break; - } - } + it->second.stats.median_duration = profiler_gui::calculateMedian(durations); decltype(it->second.durations) dummy; dummy.swap(durations); @@ -502,29 +444,16 @@ void TreeWidgetLoader::setTreeInternalTop( if (total_count > _maxCount) { - if (_maxCount > 10000) - { - m_error = QString( - "Exceeded maximum rows count = %1k.\n" - "Actual rows count: %2k (%3%).\n" - "Please, reduce selected area width\n" - "or increase maximum count in settings\n" - "or change the tree mode." - ).arg(_maxCount / 1000).arg(total_count / 1000).arg(profiler_gui::percent(total_count, _maxCount)); - } - else - { - m_error = QString( - "Exceeded maximum rows count = %1.\n" - "Actual rows count: %2 (%3%).\n" - "Please, reduce selected area width\n" - "or increase maximum count in settings\n" - "or change the tree mode." - ).arg(_maxCount).arg(total_count).arg(profiler_gui::percent(total_count, _maxCount)); - } - + m_error = QString( + "Exceeded maximum rows count = %1.\n" + "Actual rows count: %2 (%3%).\n" + "Please, reduce selected area width\n" + "or increase maximum count in settings\n" + "or change the tree mode." + ).arg(profiler_gui::shortenCountString(_maxCount)) + .arg(profiler_gui::shortenCountString(total_count)) + .arg(profiler_gui::percent(total_count, _maxCount)); setDone(); - return; } @@ -1879,7 +1808,7 @@ void TreeWidgetLoader::fillStatsForTree(TreeWidgetItem* root, StatsMap& stats, p return; } - calculate_medians(stats.begin(), stats.end()); + calculateMedians(stats.begin(), stats.end()); std::deque queue; diff --git a/profiler_gui/tree_widget_loader.h b/profiler_gui/tree_widget_loader.h index 3e5e098..bbd1337 100644 --- a/profiler_gui/tree_widget_loader.h +++ b/profiler_gui/tree_widget_loader.h @@ -72,10 +72,8 @@ namespace loader { struct Stats { - struct Counter { uint32_t count = 0; }; - profiler::BlockStatistics stats; - std::map durations; + profiler_gui::DurationsCountMap durations; Stats(profiler::timestamp_t duration, profiler::block_index_t block_index, profiler::block_index_t parent_index) : stats(duration, block_index, parent_index)