diff --git a/profiler_gui/CMakeLists.txt b/profiler_gui/CMakeLists.txt index 14e1f36..56a2130 100644 --- a/profiler_gui/CMakeLists.txt +++ b/profiler_gui/CMakeLists.txt @@ -24,6 +24,8 @@ add_executable(${PROJECT_NAME} easy_graphics_item.cpp easy_graphics_scrollbar.h easy_graphics_scrollbar.cpp + easy_qtimer.h + easy_qtimer.cpp globals.h globals.cpp globals_qobjects.cpp diff --git a/profiler_gui/blocks_graphics_view.cpp b/profiler_gui/blocks_graphics_view.cpp index b413a08..77f9ed6 100644 --- a/profiler_gui/blocks_graphics_view.cpp +++ b/profiler_gui/blocks_graphics_view.cpp @@ -428,6 +428,7 @@ void EasyGraphicsView::setTree(const ::profiler::thread_blocks_tree_t& _blocksTr finish += ADDITIONAL_OFFSET << 1; m_beginTime -= ::std::min(m_beginTime, ADDITIONAL_OFFSET); + EASY_GLOBALS.begin_time = m_beginTime; // Filling scene with items m_items.reserve(_blocksTree.size()); @@ -515,11 +516,11 @@ void EasyGraphicsView::setTree(const ::profiler::thread_blocks_tree_t& _blocksTr if (longestItem != nullptr) { - m_pScrollbar->setMinimapFrom(longestItem->threadId(), longestItem->items(0)); EASY_GLOBALS.selected_thread = longestItem->threadId(); emit EASY_GLOBALS.events.selectedThreadChanged(longestItem->threadId()); scrollTo(longestItem); + m_pScrollbar->setHystogramFrom(longestItem->threadId(), longestItem->items(0)); } m_idleTimer.start(IDLE_TIMER_INTERVAL); @@ -882,6 +883,7 @@ void EasyGraphicsView::mouseReleaseEvent(QMouseEvent* _event) } const ::profiler_gui::EasyBlock* selectedBlock = nullptr; + ::profiler::thread_id_t selectedBlockThread = 0; const auto previouslySelectedBlock = EASY_GLOBALS.selected_block; if (m_mouseButtons & Qt::LeftButton) { @@ -919,13 +921,14 @@ void EasyGraphicsView::mouseReleaseEvent(QMouseEvent* _event) { changedSelectedItem = true; selectedBlock = block; + selectedBlockThread = item->threadId(); EASY_GLOBALS.selected_block = i; EASY_GLOBALS.selected_block_id = easyBlock(i).tree.node->id(); break; } } - if (!changedSelectedItem && EASY_GLOBALS.selected_block != ::profiler_gui::numeric_max(EASY_GLOBALS.selected_block)) + if (!changedSelectedItem && !::profiler_gui::is_max(EASY_GLOBALS.selected_block)) { changedSelectedItem = true; ::profiler_gui::set_max(EASY_GLOBALS.selected_block); @@ -954,8 +957,31 @@ void EasyGraphicsView::mouseReleaseEvent(QMouseEvent* _event) emit EASY_GLOBALS.events.itemsExpandStateChanged(); } emit EASY_GLOBALS.events.selectedBlockChanged(EASY_GLOBALS.selected_block); + + if (EASY_GLOBALS.selecting_block_changes_thread && selectedBlock != nullptr && EASY_GLOBALS.selected_thread != selectedBlockThread) + { + EASY_GLOBALS.selected_thread = selectedBlockThread; + + m_pScrollbar->lock(); + emit EASY_GLOBALS.events.selectedThreadChanged(EASY_GLOBALS.selected_thread); + m_pScrollbar->unlock(); + } m_bUpdatingRect = false; + if (selectedBlock != nullptr && selectedBlockThread == EASY_GLOBALS.selected_thread) + m_pScrollbar->setHystogramFrom(EASY_GLOBALS.selected_thread, EASY_GLOBALS.selected_block_id); + else + { + for (auto item : m_items) + { + if (item->threadId() == EASY_GLOBALS.selected_thread) + { + m_pScrollbar->setHystogramFrom(EASY_GLOBALS.selected_thread, item->items(0)); + break; + } + } + } + repaintScene(); } else if (chronoHidden) @@ -1203,6 +1229,31 @@ void EasyGraphicsView::initMode() connect(globalSignals, &::profiler_gui::EasyGlobalSignals::selectedBlockChanged, this, &This::onSelectedBlockChange); connect(globalSignals, &::profiler_gui::EasyGlobalSignals::itemsExpandStateChanged, this, &This::onRefreshRequired); connect(globalSignals, &::profiler_gui::EasyGlobalSignals::refreshRequired, this, &This::onRefreshRequired); + + connect(globalSignals, &::profiler_gui::EasyGlobalSignals::selectedBlockIdChanged, [this](::profiler::block_id_t) + { + if (::profiler_gui::is_max(EASY_GLOBALS.selected_block_id)) + { + if (EASY_GLOBALS.selected_thread != 0) + { + for (auto item : m_items) + { + if (item->threadId() == EASY_GLOBALS.selected_thread) + { + m_pScrollbar->setHystogramFrom(EASY_GLOBALS.selected_thread, item->items(0)); + break; + } + } + } + else + { + m_pScrollbar->setHystogramFrom(0, nullptr); + } + } + else + m_pScrollbar->setHystogramFrom(EASY_GLOBALS.selected_thread, EASY_GLOBALS.selected_block_id); + onRefreshRequired(); + }); } ////////////////////////////////////////////////////////////////////////// @@ -1338,58 +1389,60 @@ void EasyGraphicsView::onIdleTimeout() { const auto duration = itemBlock.node->duration(); - lay->addWidget(new QLabel("%", widget), 2, 1, Qt::AlignHCenter); - lay->addWidget(new QLabel("%/N", widget), 2, 2, Qt::AlignHCenter); - lay->addWidget(new QLabel("N", widget), 2, 3, Qt::AlignHCenter); + lay->addWidget(new QLabel("-------- Statistics --------", widget), 2, 0, 1, 4, Qt::AlignHCenter); + lay->addWidget(new QLabel("per ", widget), 3, 0, Qt::AlignRight); + lay->addWidget(new QLabel("This %:", widget), 4, 0, Qt::AlignRight); + lay->addWidget(new QLabel("Sum %:", widget), 5, 0, Qt::AlignRight); + lay->addWidget(new QLabel("N Calls:", widget), 6, 0, Qt::AlignRight); - lay->addWidget(new QLabel("Thread:", widget), 3, 0, Qt::AlignRight); + lay->addWidget(new QLabel("Thread", widget), 3, 1, Qt::AlignHCenter); auto percent = ::profiler_gui::percentReal(duration, item->root()->active_time); - lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), 3, 1, Qt::AlignHCenter); + lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), 4, 1, Qt::AlignHCenter); - lay->addWidget(new QLabel(QString::number(::profiler_gui::percent(itemBlock.per_thread_stats->total_duration, item->root()->active_time)), widget), 3, 2, Qt::AlignHCenter); + lay->addWidget(new QLabel(QString::number(::profiler_gui::percent(itemBlock.per_thread_stats->total_duration, item->root()->active_time)), widget), 5, 1, Qt::AlignHCenter); - lay->addWidget(new QLabel(QString::number(itemBlock.per_thread_stats->calls_number), widget), 3, 3, Qt::AlignHCenter); + lay->addWidget(new QLabel(QString::number(itemBlock.per_thread_stats->calls_number), widget), 6, 1, Qt::AlignHCenter); - int r = 3; + int col = 1; if (itemBlock.per_frame_stats->parent_block != i) { - ++r; + ++col; auto frame_duration = blocksTree(itemBlock.per_frame_stats->parent_block).node->duration(); - lay->addWidget(new QLabel("Frame:", widget), r, 0, Qt::AlignRight); + lay->addWidget(new QLabel("Frame", widget), 3, col, Qt::AlignRight); percent = ::profiler_gui::percentReal(duration, frame_duration); - lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), r, 1, Qt::AlignHCenter); + lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), 4, col, Qt::AlignHCenter); percent = ::profiler_gui::percentReal(itemBlock.per_frame_stats->total_duration, frame_duration); - lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), r, 2, Qt::AlignHCenter); + lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), 5, col, Qt::AlignHCenter); - lay->addWidget(new QLabel(QString::number(itemBlock.per_frame_stats->calls_number), widget), r, 3, Qt::AlignHCenter); + lay->addWidget(new QLabel(QString::number(itemBlock.per_frame_stats->calls_number), widget), 6, col, Qt::AlignHCenter); } if (itemBlock.per_parent_stats->parent_block != item->threadId()) { - ++r; + ++col; auto parent_duration = blocksTree(itemBlock.per_parent_stats->parent_block).node->duration(); - lay->addWidget(new QLabel("Parent:", widget), r, 0, Qt::AlignRight); + lay->addWidget(new QLabel("Parent", widget), 3, col, Qt::AlignRight); percent = ::profiler_gui::percentReal(duration, parent_duration); - lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), r, 1, Qt::AlignHCenter); + lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), 4, col, Qt::AlignHCenter); percent = ::profiler_gui::percentReal(itemBlock.per_parent_stats->total_duration, parent_duration); - lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), r, 2, Qt::AlignHCenter); + lay->addWidget(new QLabel(0.005 < percent && percent < 0.5001 ? QString::number(percent, 'f', 2) : QString::number(static_cast(0.5 + percent)), widget), 5, col, Qt::AlignHCenter); - lay->addWidget(new QLabel(QString::number(itemBlock.per_parent_stats->calls_number), widget), r, 3, Qt::AlignHCenter); + lay->addWidget(new QLabel(QString::number(itemBlock.per_parent_stats->calls_number), widget), 6, col, Qt::AlignHCenter); - ++r; + ++col; } } else { - lay->addWidget(new QLabel("N/Thread:", widget), 1, 0, Qt::AlignRight); + lay->addWidget(new QLabel("N calls/Thread:", widget), 1, 0, Qt::AlignRight); lay->addWidget(new QLabel(QString::number(itemBlock.per_thread_stats->calls_number), widget), 1, 1, Qt::AlignLeft); } } @@ -1481,14 +1534,14 @@ void EasyGraphicsView::onHierarchyFlagChange(bool) void EasyGraphicsView::onSelectedThreadChange(::profiler::thread_id_t _id) { - if (m_pScrollbar == nullptr || m_pScrollbar->minimapThread() == _id) + if (m_pScrollbar == nullptr || m_pScrollbar->hystThread() == _id) { return; } if (_id == 0) { - m_pScrollbar->setMinimapFrom(0, nullptr); + m_pScrollbar->setHystogramFrom(0, nullptr); return; } @@ -1496,7 +1549,7 @@ void EasyGraphicsView::onSelectedThreadChange(::profiler::thread_id_t _id) { if (item->threadId() == _id) { - m_pScrollbar->setMinimapFrom(_id, item->items(0)); + m_pScrollbar->setHystogramFrom(_id, item->items(0)); bool changedSelection = false; if (EASY_GLOBALS.only_current_thread_hierarchy) @@ -1525,7 +1578,7 @@ void EasyGraphicsView::onSelectedThreadChange(::profiler::thread_id_t _id) } } - m_pScrollbar->setMinimapFrom(0, nullptr); + m_pScrollbar->setHystogramFrom(0, nullptr); repaintScene(); } @@ -1548,8 +1601,35 @@ void EasyGraphicsView::onSelectedBlockChange(unsigned int _block_index) m_bUpdatingRect = true; verticalScrollBar()->setValue(static_cast(thread_item->levelY(guiblock.graphics_item_level) - m_visibleSceneRect.height() * 0.5)); m_pScrollbar->setValue(item.left() + item.width() * 0.5 - m_pScrollbar->sliderHalfWidth()); + + if (EASY_GLOBALS.selecting_block_changes_thread && EASY_GLOBALS.selected_thread != thread_item->threadId()) + { + EASY_GLOBALS.selected_thread = thread_item->threadId(); + + m_pScrollbar->lock(); + emit EASY_GLOBALS.events.selectedThreadChanged(EASY_GLOBALS.selected_thread); + m_pScrollbar->unlock(); + } + + m_pScrollbar->setHystogramFrom(EASY_GLOBALS.selected_thread, guiblock.tree.node->id()); + m_bUpdatingRect = false; } + else if (EASY_GLOBALS.selected_thread != 0) + { + for (auto item : m_items) + { + if (item->threadId() == EASY_GLOBALS.selected_thread) + { + m_pScrollbar->setHystogramFrom(EASY_GLOBALS.selected_thread, item->items(0)); + break; + } + } + } + else + { + m_pScrollbar->setHystogramFrom(0, nullptr); + } updateVisibleSceneRect(); repaintScene(); @@ -1621,7 +1701,8 @@ void EasyThreadNameItem::paint(QPainter* _painter, const QStyleOptionGraphicsIte const auto visibleSceneRect = view->visibleSceneRect(); const auto h = visibleSceneRect.height() + TIMELINE_ROW_SIZE - 2; - const auto w = parentView->sceneRect().width(); + const auto sceneRect = parentView->sceneRect(); + const auto w = sceneRect.width(); static const uint16_t OVERLAP = ::profiler_gui::THREADS_ROW_SPACING >> 1; static const QBrush brushes[2] = {QColor::fromRgb(BACKGROUND_1), QColor::fromRgb(BACKGROUND_2)}; @@ -1632,6 +1713,7 @@ void EasyThreadNameItem::paint(QPainter* _painter, const QStyleOptionGraphicsIte _painter->resetTransform(); // Draw thread names + auto default_font = _painter->font(); _painter->setFont(BG_FONT); for (auto item : items) { @@ -1689,17 +1771,17 @@ void EasyThreadNameItem::paint(QPainter* _painter, const QStyleOptionGraphicsIte // Draw information _painter->setFont(CHRONOMETER_FONT); QFontMetricsF fm(CHRONOMETER_FONT, parentView); + const qreal th = fm.height(); // Calculate displayed text height const qreal time1 = view->chronoTime(); const qreal time2 = view->chronoTimeAux(); auto y = h + 2; - auto drawTimeText = [&rect, &w, &y, &fm, &_painter](qreal time, QRgb color) + auto drawTimeText = [&rect, &w, &y, &fm, &_painter](qreal time, qreal th, QRgb color) { if (time > 0) { const QString text = ::profiler_gui::autoTimeStringReal(time); // Displayed text - const auto th = fm.height(); // Calculate displayed text height rect.setRect(0, y, w, th); _painter->setPen(color); @@ -1709,8 +1791,8 @@ void EasyThreadNameItem::paint(QPainter* _painter, const QStyleOptionGraphicsIte } }; - drawTimeText(time1, ::profiler_gui::CHRONOMETER_COLOR.rgb() & 0x00ffffff); - drawTimeText(time2, ::profiler_gui::CHRONOMETER_COLOR2.rgb() & 0x00ffffff); + drawTimeText(time1, th, ::profiler_gui::CHRONOMETER_COLOR.rgb() & 0x00ffffff); + drawTimeText(time2, th, ::profiler_gui::CHRONOMETER_COLOR2.rgb() & 0x00ffffff); } ////////////////////////////////////////////////////////////////////////// @@ -1730,7 +1812,7 @@ EasyThreadNamesWidget::EasyThreadNamesWidget(EasyGraphicsView* _view, int _addit setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setFixedWidth(100); - connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::selectedThreadChanged, this, &This::onSelectedThreadChange); + connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::selectedThreadChanged, [this](::profiler::thread_id_t){ repaintScene(); }); connect(m_view, &EasyGraphicsView::treeChanged, this, &This::onTreeChange); connect(m_view, &EasyGraphicsView::sceneUpdated, this, &This::repaintScene); connect(m_view->verticalScrollBar(), &QScrollBar::valueChanged, verticalScrollBar(), &QScrollBar::setValue); @@ -1783,11 +1865,6 @@ void EasyThreadNamesWidget::onTreeChange() setFixedWidth(maxLength); } -void EasyThreadNamesWidget::onSelectedThreadChange(::profiler::thread_id_t) -{ - scene()->update(); -} - void EasyThreadNamesWidget::repaintScene() { scene()->update(); diff --git a/profiler_gui/blocks_graphics_view.h b/profiler_gui/blocks_graphics_view.h index 19a12fd..014d2e5 100644 --- a/profiler_gui/blocks_graphics_view.h +++ b/profiler_gui/blocks_graphics_view.h @@ -280,7 +280,6 @@ private slots: void setVerticalScrollbarRange(int _minValue, int _maxValue); void onTreeChange(); - void onSelectedThreadChange(::profiler::thread_id_t _id); void repaintScene(); }; // END of class EasyThreadNamesWidget. diff --git a/profiler_gui/descriptors_tree_widget.cpp b/profiler_gui/descriptors_tree_widget.cpp index 59d1827..48b9737 100644 --- a/profiler_gui/descriptors_tree_widget.cpp +++ b/profiler_gui/descriptors_tree_widget.cpp @@ -479,14 +479,14 @@ void EasyDescTreeWidget::onCurrentItemChange(QTreeWidgetItem* _item, QTreeWidget if (EASY_GLOBALS.selected_block_id != id) { EASY_GLOBALS.selected_block_id = id; - emit EASY_GLOBALS.events.refreshRequired(); + emit EASY_GLOBALS.events.selectedBlockIdChanged(id); } } } else if (::profiler_gui::is_max(EASY_GLOBALS.selected_block) && !::profiler_gui::is_max(EASY_GLOBALS.selected_block_id)) { ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id); - emit EASY_GLOBALS.events.refreshRequired(); + emit EASY_GLOBALS.events.selectedBlockIdChanged(EASY_GLOBALS.selected_block_id); } } diff --git a/profiler_gui/easy_graphics_item.cpp b/profiler_gui/easy_graphics_item.cpp index 1e4bddc..b76d8d8 100644 --- a/profiler_gui/easy_graphics_item.cpp +++ b/profiler_gui/easy_graphics_item.cpp @@ -70,13 +70,7 @@ inline QRgb selectedItemBorderColor(::profiler::color_t _color) { return ::profiler_gui::isLightColor(_color, 192) ? ::profiler::colors::Black : ::profiler::colors::RichRed; } -inline QRgb highlightItemColor(bool _is_light) { - return _is_light ? ::profiler::colors::RichRed : ::profiler::colors::White; -} - -#define HIGHLIGHT_COLOR(_is_light) ::profiler::colors::White -//#define HIGHLIGHT_COLOR(_is_light) highlightItemColor(_is_light) - +const QPen HIGHLIGHTER_PEN = ([]() -> QPen { QPen p(::profiler::colors::Black); p.setStyle(Qt::DotLine); p.setWidth(2); return p; })(); const auto ITEMS_FONT = ::profiler_gui::EFont("Helvetica", 10, QFont::Medium); const auto SELECTED_ITEM_FONT = ::profiler_gui::EFont("Helvetica", 10, QFont::Bold); @@ -362,7 +356,7 @@ void EasyGraphicsItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem* if (previousPenStyle != Qt::DotLine) { previousPenStyle = Qt::DotLine; - _painter->setPen(HIGHLIGHT_COLOR(is_light)); + _painter->setPen(HIGHLIGHTER_PEN); } } else if (EASY_GLOBALS.draw_graphics_items_borders) @@ -419,7 +413,7 @@ void EasyGraphicsItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem* if (previousPenStyle != Qt::DotLine) { previousPenStyle = Qt::DotLine; - _painter->setPen(HIGHLIGHT_COLOR(is_light)); + _painter->setPen(HIGHLIGHTER_PEN); } } else if (EASY_GLOBALS.draw_graphics_items_borders) @@ -497,7 +491,7 @@ void EasyGraphicsItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem* _painter->setPen(Qt::NoPen); else if (previousPenStyle == Qt::DotLine) { - _painter->setPen(HIGHLIGHT_COLOR(is_light)); + _painter->setPen(HIGHLIGHTER_PEN); } else _painter->setPen(BORDERS_COLOR);// BORDERS_COLOR & inverseColor); // restore pen for rectangle painting diff --git a/profiler_gui/easy_graphics_scrollbar.cpp b/profiler_gui/easy_graphics_scrollbar.cpp index 908ea41..e49c25e 100644 --- a/profiler_gui/easy_graphics_scrollbar.cpp +++ b/profiler_gui/easy_graphics_scrollbar.cpp @@ -52,12 +52,45 @@ #include "easy_graphics_scrollbar.h" #include "globals.h" + +// TODO: use profiler_core/spin_lock.h + +#if defined(_WIN32) && defined(EASY_GUI_USE_CRITICAL_SECTION) +# include +# ifdef min +# undef min +# endif +# ifdef max +# undef max +# endif + +namespace profiler_gui { + void spin_lock::lock() { + EnterCriticalSection((CRITICAL_SECTION*)m_lock); + } + + void spin_lock::unlock() { + LeaveCriticalSection((CRITICAL_SECTION*)m_lock); + } + + spin_lock::spin_lock() : m_lock(new CRITICAL_SECTION) { + InitializeCriticalSection((CRITICAL_SECTION*)m_lock); + } + + spin_lock::~spin_lock() { + DeleteCriticalSection((CRITICAL_SECTION*)m_lock); + delete ((CRITICAL_SECTION*)m_lock); + } +} +#endif + ////////////////////////////////////////////////////////////////////////// const int DEFAULT_TOP = -40; const int DEFAULT_HEIGHT = 80; const int INDICATOR_SIZE = 6; const int INDICATOR_SIZE_x2 = INDICATOR_SIZE << 1; +const int HYST_COLUMN_MIN_HEIGHT = 3; ////////////////////////////////////////////////////////////////////////// @@ -193,128 +226,183 @@ void EasyGraphicsSliderItem::setColor(const QColor& _color) ////////////////////////////////////////////////////////////////////////// -EasyMinimapItem::EasyMinimapItem() : Parent(nullptr), m_pSource(nullptr), m_maxDuration(0), m_minDuration(0), m_threadId(0), m_timeUnits(::profiler_gui::TimeUnits_auto) +EasyHystogramItem::EasyHystogramItem() : Parent(nullptr) + , m_threadDuration(0) + , m_threadActiveTime(0) + , m_pSource(nullptr) + , m_temporaryImage(nullptr) + , m_maxDuration(0) + , m_minDuration(0) + , m_timer(::std::bind(&This::onTimeout, this)) + , m_threadId(0) + , m_blockId(::profiler_gui::numeric_max()) + , m_timeouts(0) + , m_timeUnits(::profiler_gui::TimeUnits_auto) + , m_regime(Hyst_Pointer) + , m_bUpdatingImage(false) { - + m_bReady = ATOMIC_VAR_INIT(false); } -EasyMinimapItem::~EasyMinimapItem() +EasyHystogramItem::~EasyHystogramItem() { - + m_bReady.store(true, ::std::memory_order_release); + if (m_workerThread.joinable()) + m_workerThread.join(); + if (m_temporaryImage != nullptr) + delete m_temporaryImage; } -QRectF EasyMinimapItem::boundingRect() const +QRectF EasyHystogramItem::boundingRect() const { return m_boundingRect; } -void EasyMinimapItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem* _option, QWidget* _widget) +void EasyHystogramItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem* _option, QWidget* _widget) { - if (m_pSource == nullptr) + if ((m_regime == Hyst_Pointer && m_pSource == nullptr) || (m_regime == Hyst_Id && (m_threadId == 0 || ::profiler_gui::is_max(m_blockId)))) + return; + + if (!m_bReady.load(::std::memory_order_acquire)) { + const auto currentScale = static_cast(scene()->parent())->getWindowScale(); + const auto bottom = m_boundingRect.bottom(); + const auto width = m_boundingRect.width() * currentScale; + + _painter->save(); + _painter->setTransform(QTransform::fromScale(1.0 / currentScale, 1), true); + + const auto h = _painter->fontMetrics().height(); + + _painter->setPen(Qt::black); + _painter->drawText(QRectF(0, m_boundingRect.top(), m_boundingRect.width() * currentScale, m_boundingRect.height() - h), + Qt::AlignCenter, "Generating image"); + _painter->drawText(QRectF(0, m_boundingRect.top() + h, m_boundingRect.width() * currentScale, m_boundingRect.height() - h), + Qt::AlignCenter, QString(m_timeouts, QChar('.'))); + + _painter->setPen(Qt::darkGray); + _painter->drawLine(QLineF(0, bottom, width, bottom)); + _painter->drawLine(QLineF(0, m_boundingRect.top(), width, m_boundingRect.top())); + + _painter->restore(); + return; } + if (m_regime == Hyst_Pointer) + paintByPtr(_painter); + else + paintById(_painter); +} + +void EasyHystogramItem::paintByPtr(QPainter* _painter) +{ const auto widget = static_cast(scene()->parent()); const bool bindMode = widget->bindMode(); const auto currentScale = widget->getWindowScale(); const auto bottom = m_boundingRect.bottom(); const auto width = m_boundingRect.width() * currentScale; - const auto coeff = m_boundingRect.height() / (m_maxDuration - m_minDuration); + const auto dtime = m_maxDuration - m_minDuration; + const auto coeff = m_boundingRect.height() / (dtime > 1e-3 ? dtime : 1.); const auto heightRevert = 1.0 / m_boundingRect.height(); QRectF rect; QBrush brush(Qt::SolidPattern); QRgb previousColor = 0; - //brush.setColor(QColor::fromRgba(0x80808080)); - _painter->save(); _painter->setPen(Qt::NoPen); - //_painter->setBrush(brush); _painter->setTransform(QTransform::fromScale(1.0 / currentScale, 1), true); - const bool gotFrame = EASY_GLOBALS.frame_time > 1e-6f; - qreal frameCoeff = 1; - if (gotFrame) + if (!m_pSource->empty()) { - if (EASY_GLOBALS.frame_time <= m_minDuration) - frameCoeff = 0; + if (!bindMode) + _painter->drawImage(0, m_boundingRect.top(), m_mainImage); else - frameCoeff = 0.9 * (m_maxDuration - m_minDuration) / (EASY_GLOBALS.frame_time - m_minDuration); - } - - auto const calculate_color = gotFrame ? calculate_color2 : calculate_color1; - auto const k = gotFrame ? sqr(sqr(heightRevert * frameCoeff)) : heightRevert; - auto& items = *m_pSource; - - if (!items.empty()) - { - qreal previous_x = -1e30, previous_h = -1e30, offset = 0.; - auto first = items.begin(); - auto realScale = currentScale; - auto minimum = widget->minimum(); - auto maximum = widget->maximum(); - - if (bindMode) { const auto range = widget->sliderWidth(); - minimum = widget->value(); - maximum = minimum + range; - realScale *= widget->range() / range; - offset = minimum * realScale; + const auto minimum = widget->value(); + const auto slider_k = widget->range() / range; - auto first = ::std::lower_bound(items.begin(), items.end(), minimum, [](const ::profiler_gui::EasyBlockItem& _item, qreal _value) + if (slider_k < 8) { - return _item.left() < _value; - }); - - if (first != items.end()) - { - if (first != items.begin()) - --first; + _painter->setTransform(QTransform::fromScale(slider_k, 1), true); + _painter->drawImage(-minimum * currentScale, m_boundingRect.top(), m_mainImage); + _painter->setTransform(QTransform::fromScale(1./slider_k, 1), true); } else { - first = items.begin() + items.size() - 1; + const bool gotFrame = EASY_GLOBALS.frame_time > 1e-6f; + qreal frameCoeff = 1; + if (gotFrame) + { + if (EASY_GLOBALS.frame_time <= m_minDuration) + frameCoeff = m_boundingRect.height(); + else + frameCoeff = 0.9 * dtime / (EASY_GLOBALS.frame_time - m_minDuration); + } + + auto const calculate_color = gotFrame ? calculate_color2 : calculate_color1; + auto const k = gotFrame ? sqr(sqr(heightRevert * frameCoeff)) : heightRevert; + + const auto& items = *m_pSource; + const auto maximum = minimum + range; + const auto realScale = currentScale * slider_k; + const auto offset = minimum * realScale; + + auto first = ::std::lower_bound(items.begin(), items.end(), minimum, [](const ::profiler_gui::EasyBlockItem& _item, qreal _value) + { + return _item.left() < _value; + }); + + if (first != items.end()) + { + if (first != items.begin()) + --first; + } + else + { + first = items.begin() + items.size() - 1; + } + + qreal previous_x = -1e30, previous_h = -1e30; + for (auto it = first, end = items.end(); it != end; ++it) + { + // Draw rectangle + + if (it->left() > maximum) + break; + + 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 auto h = ::std::max((it->width() - m_minDuration) * coeff, 2.0); + + if (h < previous_h && item_r < previous_x) + continue; + + const auto col = calculate_color(h, k); + const auto color = 0x00ffffff & QColor::fromHsvF((1.0 - col) * 0.375, 0.85, 0.85).rgb(); + + if (previousColor != color) + { + // Set background color brush for rectangle + previousColor = color; + brush.setColor(QColor::fromRgba(0xc0000000 | color)); + _painter->setBrush(brush); + } + + rect.setRect(item_x, bottom - h, item_w, h); + _painter->drawRect(rect); + + previous_x = item_r; + previous_h = h; + } } } - - for (auto it = first, end = items.end(); it != end; ++it) - { - // Draw rectangle - - if (it->left() > maximum) - break; - - 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 auto h = ::std::max((it->width() - m_minDuration) * coeff, 2.0); - - if (h < previous_h && item_r < previous_x) - continue; - - const auto col = calculate_color(h, k); - const auto color = 0x00ffffff & QColor::fromHsvF((1.0 - col) * 0.375, 0.85, 0.85).rgb(); - - if (previousColor != color) - { - // Set background color brush for rectangle - previousColor = color; - brush.setColor(QColor::fromRgba(0xc0000000 | color)); - _painter->setBrush(brush); - } - - rect.setRect(item_x, bottom - h, item_w, h); - _painter->drawRect(rect); - - previous_x = item_r; - previous_h = h; - } } qreal top_width = width, bottom_width = width; @@ -332,12 +420,14 @@ void EasyMinimapItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem* auto fm = _painter->fontMetrics(); font_h = fm.height(); - bottom_width -= fm.width(m_minDurationStr) + 6; - top_width -= fm.width(m_maxDurationStr) + 6; + //bottom_width -= fm.width(m_minDurationStr) + 7; + top_width -= fm.width(m_maxDurationStr) + 7; _painter->setPen(Qt::black); _painter->drawText(rect, Qt::AlignRight | Qt::AlignTop, m_maxDurationStr); - _painter->drawText(rect, Qt::AlignRight | Qt::AlignBottom, m_minDurationStr); + + rect.setRect(0, bottom, width - 3, font_h); + _painter->drawText(rect, Qt::AlignRight | Qt::AlignTop, m_minDurationStr); } _painter->setPen(Qt::darkGray); @@ -360,28 +450,284 @@ void EasyMinimapItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem* _painter->drawLine(QLineF(0, h, w, h)); } + _painter->setPen(Qt::black); + rect.setRect(0, bottom + 2, width, widget->defaultFontHeight()); + _painter->drawText(rect, Qt::AlignHCenter | Qt::TextDontClip, QString("%1 | duration: %2 | active time: %3 (%4%)").arg(m_threadName) + .arg(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, m_threadDuration)) + .arg(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, m_threadActiveTime)) + .arg(QString::number(100. * (double)m_threadActiveTime / (double)m_threadDuration, 'f', 2))); + _painter->restore(); } -::profiler::thread_id_t EasyMinimapItem::threadId() const +void EasyHystogramItem::paintById(QPainter* _painter) +{ + const auto widget = static_cast(scene()->parent()); + const bool bindMode = widget->bindMode(); + const auto currentScale = widget->getWindowScale(); + const auto bottom = m_boundingRect.bottom(); + const auto width = m_boundingRect.width() * currentScale; + const auto dtime = m_maxDuration - m_minDuration; + const auto coeff = m_boundingRect.height() / (dtime > 1e-3 ? dtime : 1.); + const auto heightRevert = 1.0 / m_boundingRect.height(); + + QRectF rect; + QBrush brush(Qt::SolidPattern); + QRgb previousColor = 0; + + _painter->save(); + _painter->setPen(Qt::NoPen); + _painter->setTransform(QTransform::fromScale(1.0 / currentScale, 1), true); + + const bool gotFrame = EASY_GLOBALS.frame_time > 1e-6f; + qreal frameCoeff = 1; + if (gotFrame) + { + if (EASY_GLOBALS.frame_time <= m_minDuration) + frameCoeff = m_boundingRect.height(); + else + frameCoeff = 0.9 * dtime / (EASY_GLOBALS.frame_time - m_minDuration); + } + + auto const calculate_color = gotFrame ? calculate_color2 : calculate_color1; + auto const k = gotFrame ? sqr(sqr(heightRevert * frameCoeff)) : heightRevert; + const auto& items = m_selectedBlocks; + + if (!items.empty()) + { + if (!bindMode) + _painter->drawImage(0, m_boundingRect.top(), m_mainImage); + else + { + const auto range = widget->sliderWidth(); + auto minimum = widget->value(); + const auto slider_k = widget->range() / range; + + if (slider_k < 8) + { + _painter->setTransform(QTransform::fromScale(slider_k, 1), true); + _painter->drawImage(-minimum * currentScale, m_boundingRect.top(), m_mainImage); + _painter->setTransform(QTransform::fromScale(1. / slider_k, 1), true); + } + else + { + minimum *= 1e3; + const auto maximum = minimum + range * 1e3; + const auto realScale = currentScale * slider_k; + const auto offset = minimum * realScale; + + auto first = ::std::lower_bound(items.begin(), items.end(), minimum + EASY_GLOBALS.begin_time, [](::profiler::block_index_t _item, qreal _value) + { + return easyBlock(_item).tree.node->begin() < _value; + }); + + if (first != items.end()) + { + if (first != items.begin()) + --first; + } + else + { + first = items.begin() + items.size() - 1; + } + + const auto n = static_cast(::std::distance(first, items.end())); + + if (n > 0) + { + const auto draw = [this, &previousColor, &brush, &_painter](qreal x, qreal y, qreal w, qreal h, QRgb color) + { + m_spin.lock(); + + if (previousColor != color) + { + // Set background color brush for rectangle + previousColor = color; + brush.setColor(QColor::fromRgba(0xc0000000 | color)); + _painter->setBrush(brush); + } + + _painter->drawRect(QRectF(x, y, w, h)); + + m_spin.unlock(); + }; + + ::std::vector<::std::thread> threads; + const auto n_threads = ::std::min(n, ::std::thread::hardware_concurrency()); + threads.reserve(n_threads); + const auto n_items = n / n_threads; + for (uint32_t i = 0; i < n_threads; ++i) + { + auto begin = first + i * n_items; + threads.emplace_back([this, &draw, &maximum, &minimum, &realScale, &offset, &coeff, &calculate_color, &k, &bottom](decltype(begin) it, decltype(begin) end) + { + qreal previous_x = -1e30, previous_h = -1e30; + + //for (auto it = first, end = items.end(); it != end; ++it) + for (; it != end; ++it) + { + // Draw rectangle + const auto item = easyBlock(*it).tree.node; + + const auto beginTime = item->begin() - EASY_GLOBALS.begin_time; + if (beginTime > maximum) + break; + + const auto endTime = item->end() - EASY_GLOBALS.begin_time; + if (endTime < minimum) + continue; + + 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 = ::std::max((duration - m_minDuration) * coeff, 2.0); + + if (h < previous_h && item_r < previous_x) + continue; + + const auto col = calculate_color(h, k); + const auto color = 0x00ffffff & QColor::fromHsvF((1.0 - col) * 0.375, 0.85, 0.85).rgb(); + + draw(item_x, bottom - h, item_w, h, color); + //if (previousColor != color) + //{ + // // Set background color brush for rectangle + // previousColor = color; + // brush.setColor(QColor::fromRgba(0xc0000000 | color)); + // _painter->setBrush(brush); + //} + + //rect.setRect(item_x, bottom - h, item_w, h); + //_painter->drawRect(rect); + + previous_x = item_r; + previous_h = h; + } + }, begin, i == (n_threads - 1) ? items.end() : begin + n_items); + } + + for (auto& t : threads) + t.join(); + } + } + } + } + + qreal top_width = width, bottom_width = width; + int font_h = 0; + if (!m_maxDurationStr.isEmpty()) + { + rect.setRect(0, m_boundingRect.top() - INDICATOR_SIZE, width - 3, m_boundingRect.height() + INDICATOR_SIZE_x2); + + if (m_timeUnits != EASY_GLOBALS.time_units) + { + m_timeUnits = EASY_GLOBALS.time_units; + m_maxDurationStr = ::profiler_gui::timeStringReal(m_timeUnits, m_maxDuration, 3); + m_minDurationStr = ::profiler_gui::timeStringReal(m_timeUnits, m_minDuration, 3); + } + + auto fm = _painter->fontMetrics(); + font_h = fm.height(); + //bottom_width -= fm.width(m_minDurationStr) + 7; + top_width -= fm.width(m_maxDurationStr) + 7; + + _painter->setPen(Qt::black); + _painter->drawText(rect, Qt::AlignRight | Qt::AlignTop, m_maxDurationStr); + + rect.setRect(0, bottom, width - 3, font_h); + _painter->drawText(rect, Qt::AlignRight | Qt::AlignTop, m_minDurationStr); + } + + _painter->setPen(Qt::darkGray); + _painter->drawLine(QLineF(0, bottom, bottom_width, bottom)); + _painter->drawLine(QLineF(0, m_boundingRect.top(), top_width, m_boundingRect.top())); + + if (m_minDuration < EASY_GLOBALS.frame_time && EASY_GLOBALS.frame_time < m_maxDuration) + { + // Draw marker displaying required frame_time step + const auto h = bottom - (EASY_GLOBALS.frame_time - m_minDuration) * coeff; + _painter->setPen(Qt::DashLine); + + auto w = width; + const auto boundary = INDICATOR_SIZE - 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(Qt::black); + rect.setRect(0, bottom + 2, width, widget->defaultFontHeight()); + + const auto* item = !::profiler_gui::is_max(EASY_GLOBALS.selected_block) ? &easyBlock(EASY_GLOBALS.selected_block) : (!m_selectedBlocks.empty() ? &easyBlock(m_selectedBlocks.front()) : nullptr); + if (item != nullptr) + { + const auto name = *item->tree.node->name() != 0 ? item->tree.node->name() : easyDescriptor(item->tree.node->id()).name(); + if (item->tree.per_thread_stats != nullptr) + { + _painter->drawText(rect, Qt::AlignHCenter | Qt::TextDontClip, QString("%1 | %2 | %3 calls | %4% of thread active time").arg(m_threadName).arg(name) + .arg(item->tree.per_thread_stats->calls_number) + .arg(QString::number(100. * (double)item->tree.per_thread_stats->total_duration / (double)m_threadActiveTime, 'f', 2))); + } + else + { + _painter->drawText(rect, Qt::AlignHCenter | Qt::TextDontClip, QString("%1 | %2 | %3 calls").arg(m_threadName).arg(name) + .arg(m_selectedBlocks.size())); + } + } + else + { + _painter->drawText(rect, Qt::AlignHCenter | Qt::TextDontClip, QString("%1 | %2 | %3 calls").arg(m_threadName).arg(easyDescriptor(m_blockId).name()) + .arg(m_selectedBlocks.size())); + } + + _painter->restore(); +} + +::profiler::thread_id_t EasyHystogramItem::threadId() const { return m_threadId; } -void EasyMinimapItem::setBoundingRect(const QRectF& _rect) +void EasyHystogramItem::setBoundingRect(const QRectF& _rect) { m_boundingRect = _rect; } -void EasyMinimapItem::setBoundingRect(qreal x, qreal y, qreal w, qreal h) +void EasyHystogramItem::setBoundingRect(qreal x, qreal y, qreal w, qreal h) { m_boundingRect.setRect(x, y, w, h); } -void EasyMinimapItem::setSource(::profiler::thread_id_t _thread_id, const ::profiler_gui::EasyItems* _items) +void EasyHystogramItem::setSource(::profiler::thread_id_t _thread_id, const ::profiler_gui::EasyItems* _items) { + if (m_regime == Hyst_Pointer && m_threadId == _thread_id && m_pSource == _items) + return; + + if (m_timer.isActive()) + m_timer.stop(); + + m_bReady.store(true, ::std::memory_order_release); + if (m_workerThread.joinable()) + m_workerThread.join(); + + if (m_temporaryImage != nullptr) + { + delete m_temporaryImage; + m_temporaryImage = nullptr; + } + + m_selectedBlocks.clear(); + ::profiler::BlocksTree::children_t().swap(m_selectedBlocks); + + m_bUpdatingImage = true; + m_regime = Hyst_Pointer; m_pSource = _items; m_threadId = _thread_id; + ::profiler_gui::set_max(m_blockId); if (m_pSource != nullptr) { @@ -395,17 +741,11 @@ void EasyMinimapItem::setSource(::profiler::thread_id_t _thread_id, const ::prof m_minDuration = 1e30; for (const auto& item : *m_pSource) { - auto w = item.width(); - + const auto w = item.width(); if (w > m_maxDuration) - { - m_maxDuration = item.width(); - } - + m_maxDuration = w; if (w < m_minDuration) - { m_minDuration = w; - } } } } @@ -414,17 +754,383 @@ void EasyMinimapItem::setSource(::profiler::thread_id_t _thread_id, const ::prof { m_maxDurationStr.clear(); m_minDurationStr.clear(); + m_threadName.clear(); hide(); } else { + const auto& root = EASY_GLOBALS.profiler_blocks[_thread_id]; + if (root.got_name()) + m_threadName = ::std::move(QString("%1 Thread %2").arg(root.name()).arg(_thread_id)); + else + m_threadName = ::std::move(QString("Thread %1").arg(_thread_id)); + + if (root.children.empty()) + m_threadDuration = 0; + else + m_threadDuration = easyBlock(root.children.back()).tree.node->end() - easyBlock(root.children.front()).tree.node->begin(); + m_threadActiveTime = root.active_time; + m_timeUnits = EASY_GLOBALS.time_units; m_maxDurationStr = ::profiler_gui::timeStringReal(m_timeUnits, m_maxDuration, 3); m_minDurationStr = ::profiler_gui::timeStringReal(m_timeUnits, m_minDuration, 3); + updateImage(); show(); } } +void EasyHystogramItem::setSource(::profiler::thread_id_t _thread_id, ::profiler::block_id_t _block_id) +{ + if (m_regime == Hyst_Id && m_threadId == _thread_id && m_blockId == _block_id) + return; + + m_bUpdatingImage = false; + m_regime = Hyst_Id; + m_pSource = nullptr; + m_maxDurationStr.clear(); + m_minDurationStr.clear(); + + if (m_timer.isActive()) + m_timer.stop(); + + m_bReady.store(true, ::std::memory_order_release); + if (m_workerThread.joinable()) + m_workerThread.join(); + + if (m_temporaryImage != nullptr) + { + delete m_temporaryImage; + m_temporaryImage = nullptr; + } + + m_selectedBlocks.clear(); + ::profiler::BlocksTree::children_t().swap(m_selectedBlocks); + + m_threadId = _thread_id; + m_blockId = _block_id; + + if (m_threadId != 0 && !::profiler_gui::is_max(m_blockId)) + { + const auto& root = EASY_GLOBALS.profiler_blocks[_thread_id]; + if (root.got_name()) + m_threadName = ::std::move(QString("%1 Thread %2").arg(root.name()).arg(_thread_id)); + else + m_threadName = ::std::move(QString("Thread %1").arg(_thread_id)); + + if (root.children.empty()) + m_threadDuration = 0; + else + m_threadDuration = easyBlock(root.children.back()).tree.node->end() - easyBlock(root.children.front()).tree.node->begin(); + m_threadActiveTime = root.active_time; + + show(); + m_timeUnits = EASY_GLOBALS.time_units; + m_bReady.store(false, ::std::memory_order_release); + m_workerThread = ::std::move(::std::thread([this](decltype(root) profiler_thread) + { + typedef ::std::vector<::std::pair<::profiler::block_index_t, ::profiler::block_index_t> > Stack; + + m_maxDuration = 0; + m_minDuration = 1e30; + //const auto& profiler_thread = EASY_GLOBALS.profiler_blocks[m_threadId]; + Stack stack; + stack.reserve(profiler_thread.depth); + + for (auto frame : profiler_thread.children) + { + const auto& frame_block = easyBlock(frame).tree; + const char* frame_name = easyDescriptor(frame_block.node->id()).name(); + if (frame_block.node->id() == m_blockId) + { + m_selectedBlocks.push_back(frame); + + const auto w = frame_block.node->duration(); + if (w > m_maxDuration) + m_maxDuration = w; + if (w < m_minDuration) + m_minDuration = w; + } + + stack.push_back(::std::make_pair(frame, 0U)); + while (!stack.empty() && !m_bReady.load(::std::memory_order_acquire)) + { + auto& top = stack.back(); + const auto& top_children = easyBlock(top.first).tree.children; + const auto stack_size = stack.size(); + for (auto end = top_children.size(); top.second < end && !m_bReady.load(::std::memory_order_acquire); ++top.second) + { + const auto child_index = top_children[top.second]; + const auto& child = easyBlock(child_index).tree; + const char* child_name = easyDescriptor(child.node->id()).name(); + if (child.node->id() == m_blockId) + { + m_selectedBlocks.push_back(child_index); + + const auto w = child.node->duration(); + if (w > m_maxDuration) + m_maxDuration = w; + if (w < m_minDuration) + m_minDuration = w; + } + + if (!child.children.empty()) + { + ++top.second; + stack.push_back(::std::make_pair(child_index, 0U)); + break; + } + } + + if (stack_size == stack.size()) + { + stack.pop_back(); + } + } + } + + if (m_selectedBlocks.empty()) + { + m_maxDurationStr.clear(); + m_minDurationStr.clear(); + } + else + { + m_maxDuration *= 1e-3; + m_minDuration *= 1e-3; + m_maxDurationStr = ::profiler_gui::timeStringReal(m_timeUnits, m_maxDuration, 3); + m_minDurationStr = ::profiler_gui::timeStringReal(m_timeUnits, m_minDuration, 3); + } + + m_bReady.store(true, ::std::memory_order_release); + }, std::ref(root))); + + m_timeouts = 3; + m_timer.start(500); + } + else + { + m_threadName.clear(); + hide(); + } +} + +////////////////////////////////////////////////////////////////////////// + +void EasyHystogramItem::onTimeout() +{ + if (!isVisible()) + { + if (m_timer.isActive()) + m_timer.stop(); + return; + } + + if (++m_timeouts > 8) + m_timeouts = 3; + + if (m_bReady.load(::std::memory_order_acquire)) + { + m_timer.stop(); + if (!m_bUpdatingImage) + { + m_bUpdatingImage = true; + updateImage(); + } + else + { + m_temporaryImage->swap(m_mainImage); + delete m_temporaryImage; + m_temporaryImage = nullptr; + + if (m_workerThread.joinable()) + m_workerThread.join(); + } + } + + scene()->update(); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyHystogramItem::updateImage() +{ + if (!m_bUpdatingImage) + return; + + const auto widget = static_cast(scene()->parent()); + + m_bReady.store(true, ::std::memory_order_release); + if (m_workerThread.joinable()) + m_workerThread.join(); + m_bReady.store(false, ::std::memory_order_release); + + if (m_temporaryImage != nullptr) + { + delete m_temporaryImage; + m_temporaryImage = nullptr; + } + + m_workerThread = ::std::move(::std::thread([this](HystRegime _regime, qreal _current_scale, + qreal _minimum, qreal _maximum, qreal _range, qreal _value, qreal _width, bool _bindMode, + float _frame_time, ::profiler::timestamp_t _begin_time) + { + updateImage(_regime, _current_scale, _minimum, _maximum, _range, _value, _width, _bindMode, _frame_time, _begin_time); + m_bReady.store(true, ::std::memory_order_release); + }, m_regime, widget->getWindowScale(), widget->minimum(), widget->maximum(), widget->range(), widget->value(), widget->sliderWidth(), widget->bindMode(), EASY_GLOBALS.frame_time, EASY_GLOBALS.begin_time)); + + m_timeouts = 3; + m_timer.start(500); +} + +void EasyHystogramItem::updateImage(HystRegime _regime, qreal _current_scale, + qreal _minimum, qreal _maximum, qreal _range, + qreal _value, qreal _width, bool _bindMode, + float _frame_time, ::profiler::timestamp_t _begin_time) +{ + const auto bottom = m_boundingRect.height();//m_boundingRect.bottom(); + const auto width = m_boundingRect.width() * _current_scale; + const auto dtime = m_maxDuration - m_minDuration; + const auto coeff = (m_boundingRect.height() - HYST_COLUMN_MIN_HEIGHT) / (dtime > 1e-3 ? dtime : 1.); + const auto heightRevert = 1.0 / m_boundingRect.height(); + + m_temporaryImage = new QImage((_bindMode ? width * 2. : width) + 0.5, m_boundingRect.height(), QImage::Format_ARGB32); + m_temporaryImage->fill(Qt::white); + QPainter p(m_temporaryImage); + p.setPen(Qt::NoPen); + + QRectF rect; + QBrush brush(Qt::SolidPattern); + QRgb previousColor = 0; + + qreal previous_x = -1e30, previous_h = -1e30, offset = 0.; + auto realScale = _current_scale; + auto minimum = _minimum; + auto maximum = _maximum; + + const bool gotFrame = _frame_time > 1e-6f; + qreal frameCoeff = 1; + if (gotFrame) + { + if (_frame_time <= m_minDuration) + frameCoeff = m_boundingRect.height(); + else + frameCoeff = 0.9 * dtime / (_frame_time - m_minDuration); + } + + auto const calculate_color = gotFrame ? calculate_color2 : calculate_color1; + auto const k = gotFrame ? sqr(sqr(heightRevert * frameCoeff)) : heightRevert; + + if (_regime == Hyst_Pointer) + { + const auto& items = *m_pSource; + if (items.empty()) + return; + + //if (_bindMode) + //{ + // minimum = _value; + // maximum = minimum + _width; + // realScale *= _range / _width; + // offset = minimum * realScale; + + // auto first = ::std::lower_bound(items.begin(), items.end(), minimum, [](const ::profiler_gui::EasyBlockItem& _item, qreal _value) + // { + // return _item.left() < _value; + // }); + + // if (first != items.end()) + // { + // if (first != items.begin()) + // --first; + // } + // else + // { + // first = items.begin() + items.size() - 1; + // } + //} + + for (auto it = items.begin(), end = items.end(); it != end; ++it) + { + // Draw rectangle + if (it->left() > maximum) + break; + + 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 auto h = HYST_COLUMN_MIN_HEIGHT + (it->width() - m_minDuration) * coeff; + + if (h < previous_h && item_r < previous_x) + continue; + + const auto col = calculate_color(h, k); + const auto color = 0x00ffffff & QColor::fromHsvF((1.0 - col) * 0.375, 0.85, 0.85).rgb(); + + if (previousColor != color) + { + // Set background color brush for rectangle + previousColor = color; + brush.setColor(QColor::fromRgba(0xc0000000 | color)); + p.setBrush(brush); + } + + rect.setRect(item_x, bottom - h, item_w, h); + p.drawRect(rect); + + previous_x = item_r; + previous_h = h; + } + } + else + { + minimum *= 1e3; + maximum *= 1e3; + + for (auto it = m_selectedBlocks.begin(), end = m_selectedBlocks.end(); it != end; ++it) + { + // Draw rectangle + const auto item = easyBlock(*it).tree.node; + + const auto beginTime = item->begin() - _begin_time; + if (beginTime > maximum) + break; + + const auto endTime = item->end() - _begin_time; + if (endTime < minimum) + continue; + + 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 = HYST_COLUMN_MIN_HEIGHT + (duration - m_minDuration) * coeff; + + if (h < previous_h && item_r < previous_x) + continue; + + const auto col = calculate_color(h, k); + const auto color = 0x00ffffff & QColor::fromHsvF((1.0 - col) * 0.375, 0.85, 0.85).rgb(); + + if (previousColor != color) + { + // Set background color brush for rectangle + previousColor = color; + brush.setColor(QColor::fromRgba(0xc0000000 | color)); + p.setBrush(brush); + } + + rect.setRect(item_x, bottom - h, item_w, h); + p.drawRect(rect); + + previous_x = item_r; + previous_h = h; + } + } +} + ////////////////////////////////////////////////////////////////////////// EasyGraphicsScrollbar::EasyGraphicsScrollbar(QWidget* _parent) @@ -436,9 +1142,11 @@ EasyGraphicsScrollbar::EasyGraphicsScrollbar(QWidget* _parent) , m_mouseButtons(Qt::NoButton) , m_slider(nullptr) , m_chronometerIndicator(nullptr) - , m_minimap(nullptr) + , m_hystogramItem(nullptr) + , m_defaultFontHeight(0) , m_bScrolling(false) , m_bBindMode(false) + , m_bLocked(false) { setCacheMode(QGraphicsView::CacheNone); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); @@ -450,17 +1158,19 @@ EasyGraphicsScrollbar::EasyGraphicsScrollbar(QWidget* _parent) setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setContentsMargins(0, 0, 0, 0); - setFixedHeight(DEFAULT_HEIGHT + 2); auto selfScene = new QGraphicsScene(this); - selfScene->setSceneRect(0, DEFAULT_TOP, 500, DEFAULT_HEIGHT); + m_defaultFontHeight = QFontMetrics(selfScene->font()).height(); + selfScene->setSceneRect(0, DEFAULT_TOP, 500, DEFAULT_HEIGHT + m_defaultFontHeight + 2); + setFixedHeight(DEFAULT_HEIGHT + m_defaultFontHeight + 2); + setScene(selfScene); - m_minimap = new EasyMinimapItem(); - m_minimap->setPos(0, 0); - m_minimap->setBoundingRect(0, DEFAULT_TOP + INDICATOR_SIZE, scene()->width(), DEFAULT_HEIGHT - INDICATOR_SIZE_x2); - selfScene->addItem(m_minimap); - m_minimap->hide(); + m_hystogramItem = new EasyHystogramItem(); + m_hystogramItem->setPos(0, 0); + m_hystogramItem->setBoundingRect(0, DEFAULT_TOP + INDICATOR_SIZE, scene()->width(), DEFAULT_HEIGHT - INDICATOR_SIZE_x2); + selfScene->addItem(m_hystogramItem); + m_hystogramItem->hide(); m_chronometerIndicator = new EasyGraphicsSliderItem(false); m_chronometerIndicator->setPos(0, 0); @@ -474,7 +1184,12 @@ EasyGraphicsScrollbar::EasyGraphicsScrollbar(QWidget* _parent) selfScene->addItem(m_slider); m_slider->hide(); - connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::timelineMarkerChanged, [this](){ scene()->update(); }); + connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::timelineMarkerChanged, [this]() + { + if (m_hystogramItem->isVisible()) + m_hystogramItem->updateImage(); + scene()->update(); + }); centerOn(0, 0); } @@ -488,7 +1203,7 @@ EasyGraphicsScrollbar::~EasyGraphicsScrollbar() void EasyGraphicsScrollbar::clear() { - setMinimapFrom(0, nullptr); + setHystogramFrom(0, nullptr); hideChrono(); setRange(0, 100); setSliderWidth(2); @@ -507,9 +1222,9 @@ qreal EasyGraphicsScrollbar::getWindowScale() const return m_windowScale; } -::profiler::thread_id_t EasyGraphicsScrollbar::minimapThread() const +::profiler::thread_id_t EasyGraphicsScrollbar::hystThread() const { - return m_minimap->threadId(); + return m_hystogramItem->threadId(); } qreal EasyGraphicsScrollbar::minimum() const @@ -542,6 +1257,11 @@ qreal EasyGraphicsScrollbar::sliderHalfWidth() const return m_slider->halfwidth(); } +int EasyGraphicsScrollbar::defaultFontHeight() const +{ + return m_defaultFontHeight; +} + ////////////////////////////////////////////////////////////////////////// void EasyGraphicsScrollbar::setValue(qreal _value) @@ -558,8 +1278,8 @@ void EasyGraphicsScrollbar::setRange(qreal _minValue, qreal _maxValue) m_minimumValue = _minValue; m_maximumValue = _maxValue; - scene()->setSceneRect(_minValue, DEFAULT_TOP, _maxValue - _minValue, DEFAULT_HEIGHT); - m_minimap->setBoundingRect(_minValue, DEFAULT_TOP + INDICATOR_SIZE, _maxValue, DEFAULT_HEIGHT - INDICATOR_SIZE_x2); + scene()->setSceneRect(_minValue, DEFAULT_TOP, _maxValue - _minValue, DEFAULT_HEIGHT + m_defaultFontHeight + 4); + m_hystogramItem->setBoundingRect(_minValue, DEFAULT_TOP + INDICATOR_SIZE, _maxValue, DEFAULT_HEIGHT - INDICATOR_SIZE_x2); emit rangeChanged(); setValue(_minValue + oldValue * range()); @@ -592,10 +1312,23 @@ void EasyGraphicsScrollbar::hideChrono() ////////////////////////////////////////////////////////////////////////// -void EasyGraphicsScrollbar::setMinimapFrom(::profiler::thread_id_t _thread_id, const ::profiler_gui::EasyItems* _items) +void EasyGraphicsScrollbar::setHystogramFrom(::profiler::thread_id_t _thread_id, const ::profiler_gui::EasyItems* _items) { - m_minimap->setSource(_thread_id, _items); - m_slider->setVisible(m_minimap->isVisible()); + if (m_bLocked) + return; + + m_hystogramItem->setSource(_thread_id, _items); + m_slider->setVisible(m_hystogramItem->isVisible()); + scene()->update(); +} + +void EasyGraphicsScrollbar::setHystogramFrom(::profiler::thread_id_t _thread_id, ::profiler::block_id_t _block_id) +{ + if (m_bLocked) + return; + + m_hystogramItem->setSource(_thread_id, _block_id); + m_slider->setVisible(m_hystogramItem->isVisible()); scene()->update(); } diff --git a/profiler_gui/easy_graphics_scrollbar.h b/profiler_gui/easy_graphics_scrollbar.h index cd7801c..f89e3b1 100644 --- a/profiler_gui/easy_graphics_scrollbar.h +++ b/profiler_gui/easy_graphics_scrollbar.h @@ -46,14 +46,55 @@ #define EASY__GRAPHICS_SCROLLBAR__H #include +#include +#include #include #include #include #include +#include +#include "easy_qtimer.h" #include "common_types.h" ////////////////////////////////////////////////////////////////////////// +// TODO: use profiler_core/spin_lock.h + +#define EASY_GUI_USE_CRITICAL_SECTION // Use CRITICAL_SECTION instead of std::atomic_flag +#if defined(_WIN32) && defined(EASY_GUI_USE_CRITICAL_SECTION) +namespace profiler_gui { + // std::atomic_flag on Windows works slower than critical section, so we will use it instead of std::atomic_flag... + // By the way, Windows critical sections are slower than std::atomic_flag on Unix. + class spin_lock { void* m_lock; public: + void lock(); + void unlock(); + spin_lock(); + ~spin_lock(); + }; +#else +namespace profiler_gui { + // std::atomic_flag on Unix works fine and very fast (almost instant!) + class spin_lock { + ::std::atomic_flag m_lock; public: + + void lock() { + while (m_lock.test_and_set(::std::memory_order_acquire)); + } + + void unlock() { + m_lock.clear(::std::memory_order_release); + } + + spin_lock() { + m_lock.clear(); + } + }; +#endif + +} // END of namespace profiler_gui. + +////////////////////////////////////////////////////////////////////////// + class EasyGraphicsSliderItem : public QGraphicsRectItem { typedef QGraphicsRectItem Parent; @@ -85,24 +126,40 @@ public: ////////////////////////////////////////////////////////////////////////// -class EasyMinimapItem : public QGraphicsItem +class EasyHystogramItem : public QGraphicsItem { typedef QGraphicsItem Parent; - typedef EasyMinimapItem This; + typedef EasyHystogramItem This; - QRectF m_boundingRect; - qreal m_maxDuration; - qreal m_minDuration; - QString m_maxDurationStr; - QString m_minDurationStr; - const ::profiler_gui::EasyItems* m_pSource; - ::profiler::thread_id_t m_threadId; - ::profiler_gui::TimeUnits m_timeUnits; + enum HystRegime : uint8_t { Hyst_Pointer, Hyst_Id }; + + QRectF m_boundingRect; + qreal m_maxDuration; + qreal m_minDuration; + QString m_maxDurationStr; + QString m_minDurationStr; + QString m_threadName; + ::profiler::BlocksTree::children_t m_selectedBlocks; + QImage m_mainImage; + EasyQTimer m_timer; + ::std::thread m_workerThread; + ::profiler::timestamp_t m_threadDuration; + ::profiler::timestamp_t m_threadActiveTime; + const ::profiler_gui::EasyItems* m_pSource; + QImage* m_temporaryImage; + ::profiler::thread_id_t m_threadId; + ::profiler::block_index_t m_blockId; + int m_timeouts; + ::profiler_gui::TimeUnits m_timeUnits; + HystRegime m_regime; + bool m_bUpdatingImage; + ::profiler_gui::spin_lock m_spin; + ::std::atomic_bool m_bReady; public: - explicit EasyMinimapItem(); - virtual ~EasyMinimapItem(); + explicit EasyHystogramItem(); + virtual ~EasyHystogramItem(); // Public virtual methods @@ -119,8 +176,20 @@ public: void setBoundingRect(qreal x, qreal y, qreal w, qreal h); void setSource(::profiler::thread_id_t _thread_id, const ::profiler_gui::EasyItems* _items); + void setSource(::profiler::thread_id_t _thread_id, ::profiler::block_id_t _block_id); + void updateImage(); -}; // END of class EasyMinimapItem. +private: + + void paintByPtr(QPainter* _painter); + void paintById(QPainter* _painter); + void onTimeout(); + void updateImage(HystRegime _regime, qreal _current_scale, + qreal _minimum, qreal _maximum, qreal _range, + qreal _value, qreal _width, bool _bindMode, + float _frame_time, ::profiler::timestamp_t _begin_time); + +}; // END of class EasyHystogramItem. ////////////////////////////////////////////////////////////////////////// @@ -141,9 +210,11 @@ private: Qt::MouseButtons m_mouseButtons; EasyGraphicsSliderItem* m_slider; EasyGraphicsSliderItem* m_chronometerIndicator; - EasyMinimapItem* m_minimap; + EasyHystogramItem* m_hystogramItem; + int m_defaultFontHeight; bool m_bScrolling; bool m_bBindMode; + bool m_bLocked; public: @@ -169,7 +240,7 @@ public: bool bindMode() const; qreal getWindowScale() const; - ::profiler::thread_id_t minimapThread() const; + ::profiler::thread_id_t hystThread() const; qreal minimum() const; qreal maximum() const; @@ -177,6 +248,7 @@ public: qreal value() const; qreal sliderWidth() const; qreal sliderHalfWidth() const; + int defaultFontHeight() const; void setValue(qreal _value); void setRange(qreal _minValue, qreal _maxValue); @@ -185,11 +257,22 @@ public: void showChrono(); void hideChrono(); - void setMinimapFrom(::profiler::thread_id_t _thread_id, const::profiler_gui::EasyItems* _items); + void setHystogramFrom(::profiler::thread_id_t _thread_id, const::profiler_gui::EasyItems* _items); + void setHystogramFrom(::profiler::thread_id_t _thread_id, ::profiler::block_id_t _block_id); - inline void setMinimapFrom(::profiler::thread_id_t _thread_id, const ::profiler_gui::EasyItems& _items) + inline void setHystogramFrom(::profiler::thread_id_t _thread_id, const ::profiler_gui::EasyItems& _items) { - setMinimapFrom(_thread_id, &_items); + setHystogramFrom(_thread_id, &_items); + } + + inline void lock() + { + m_bLocked = true; + } + + inline void unlock() + { + m_bLocked = false; } signals: diff --git a/profiler_gui/easy_qtimer.cpp b/profiler_gui/easy_qtimer.cpp new file mode 100644 index 0000000..d567d82 --- /dev/null +++ b/profiler_gui/easy_qtimer.cpp @@ -0,0 +1,74 @@ +/************************************************************************ +* file name : easy_qtimer.h +* ----------------- : +* creation time : 2016/12/05 +* author : Victor Zarubkin +* email : v.s.zarubkin@gmail.com +* ----------------- : +* description : This file contains implementation of EasyQTimer class used to +* : connect QTimer to non-QObject classes. +* ----------------- : +* change log : * 2016/12/05 Victor Zarubkin: Initial commit. +* : +* : * +* ----------------- : +* license : Lightweight profiler library for c++ +* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin +* : +* : +* : Licensed under the Apache License, Version 2.0 (the "License"); +* : you may not use this file except in compliance with the License. +* : You may obtain a copy of the License at +* : +* : http://www.apache.org/licenses/LICENSE-2.0 +* : +* : Unless required by applicable law or agreed to in writing, software +* : distributed under the License is distributed on an "AS IS" BASIS, +* : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* : See the License for the specific language governing permissions and +* : limitations under the License. +* : +* : +* : GNU General Public License Usage +* : Alternatively, this file may be used under the terms of the GNU +* : General Public License as published by the Free Software Foundation, +* : either version 3 of the License, or (at your option) any later version. +* : +* : This program is distributed in the hope that it will be useful, +* : but WITHOUT ANY WARRANTY; without even the implied warranty of +* : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +* : GNU General Public License for more details. +* : +* : You should have received a copy of the GNU General Public License +* : along with this program.If not, see . +************************************************************************/ + +#include "easy_qtimer.h" + +////////////////////////////////////////////////////////////////////////// + +EasyQTimer::EasyQTimer() + : QObject() +{ + connect(&m_timer, &QTimer::timeout, [this](){ m_handler(); }); +} + +EasyQTimer::EasyQTimer(::std::function&& _handler) + : QObject() + , m_handler(::std::forward<::std::function&&>(_handler)) +{ + connect(&m_timer, &QTimer::timeout, [this](){ m_handler(); }); +} + +EasyQTimer::~EasyQTimer() +{ + +} + +void EasyQTimer::setHandler(::std::function&& _handler) +{ + m_handler = _handler; +} + +////////////////////////////////////////////////////////////////////////// + diff --git a/profiler_gui/easy_qtimer.h b/profiler_gui/easy_qtimer.h new file mode 100644 index 0000000..6acb8f1 --- /dev/null +++ b/profiler_gui/easy_qtimer.h @@ -0,0 +1,79 @@ +/************************************************************************ +* file name : easy_qtimer.h +* ----------------- : +* creation time : 2016/12/05 +* author : Victor Zarubkin +* email : v.s.zarubkin@gmail.com +* ----------------- : +* description : This file contains declaration of EasyQTimer class used to +* : connect QTimer to non-QObject classes. +* ----------------- : +* change log : * 2016/12/05 Victor Zarubkin: Initial commit. +* : +* : * +* ----------------- : +* license : Lightweight profiler library for c++ +* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin +* : +* : +* : Licensed under the Apache License, Version 2.0 (the "License"); +* : you may not use this file except in compliance with the License. +* : You may obtain a copy of the License at +* : +* : http://www.apache.org/licenses/LICENSE-2.0 +* : +* : Unless required by applicable law or agreed to in writing, software +* : distributed under the License is distributed on an "AS IS" BASIS, +* : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* : See the License for the specific language governing permissions and +* : limitations under the License. +* : +* : +* : GNU General Public License Usage +* : Alternatively, this file may be used under the terms of the GNU +* : General Public License as published by the Free Software Foundation, +* : either version 3 of the License, or (at your option) any later version. +* : +* : This program is distributed in the hope that it will be useful, +* : but WITHOUT ANY WARRANTY; without even the implied warranty of +* : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +* : GNU General Public License for more details. +* : +* : You should have received a copy of the GNU General Public License +* : along with this program.If not, see . +************************************************************************/ + +#ifndef EASY__QTIMER__H +#define EASY__QTIMER__H + +#include +#include + +////////////////////////////////////////////////////////////////////////// + +class EasyQTimer : public QObject +{ + Q_OBJECT + +private: + + QTimer m_timer; + ::std::function m_handler; + +public: + + EasyQTimer(); + EasyQTimer(::std::function&& _handler); + virtual ~EasyQTimer(); + + void setHandler(::std::function&& _handler); + + inline void start(int msec) { m_timer.start(msec); } + inline void stop() { m_timer.stop(); } + inline bool isActive() const { return m_timer.isActive(); } + +}; // END of class EasyQTimer. + +////////////////////////////////////////////////////////////////////////// + +#endif // EASY__QTIMER__H diff --git a/profiler_gui/globals.cpp b/profiler_gui/globals.cpp index cfab6bf..8e25057 100644 --- a/profiler_gui/globals.cpp +++ b/profiler_gui/globals.cpp @@ -61,6 +61,7 @@ namespace profiler_gui { : selected_thread(0U) , selected_block(::profiler_gui::numeric_max()) , selected_block_id(::profiler_gui::numeric_max()) + , begin_time(0) , frame_time(4e4f) , blocks_spacing(2) , blocks_size_min(3) @@ -79,6 +80,7 @@ namespace profiler_gui { , all_items_expanded_by_default(true) , only_current_thread_hierarchy(false) , highlight_blocks_with_same_id(true) + , selecting_block_changes_thread(true) , bind_scene_and_tree_expand_status(true) { diff --git a/profiler_gui/globals.h b/profiler_gui/globals.h index 1dc1fc0..5d78210 100644 --- a/profiler_gui/globals.h +++ b/profiler_gui/globals.h @@ -108,6 +108,7 @@ namespace profiler_gui { ::profiler::thread_blocks_tree_t profiler_blocks; ///< Profiler blocks tree loaded from file ::profiler::descriptors_list_t descriptors; ///< Profiler block descriptors list EasyBlocks gui_blocks; ///< Profiler graphics blocks builded by GUI + ::profiler::timestamp_t begin_time; ///< ::profiler::thread_id_t selected_thread; ///< Current selected thread id ::profiler::block_index_t selected_block; ///< Current selected profiler block index ::profiler::block_id_t selected_block_id; ///< Current selected profiler block id @@ -129,6 +130,7 @@ namespace profiler_gui { bool all_items_expanded_by_default; ///< Expand all items after file is opened bool only_current_thread_hierarchy; ///< Build hierarchy tree for current thread only bool highlight_blocks_with_same_id; ///< Highlight all blocks with same id on diagram + bool selecting_block_changes_thread; ///< If true then current selected thread will change every time you select block bool bind_scene_and_tree_expand_status; /** \brief If true then items on graphics scene and in the tree (blocks hierarchy) are binded on each other so expanding/collapsing items on scene also expands/collapse items in the tree. */ diff --git a/profiler_gui/globals_qobjects.h b/profiler_gui/globals_qobjects.h index a5b2792..82d3ef4 100644 --- a/profiler_gui/globals_qobjects.h +++ b/profiler_gui/globals_qobjects.h @@ -49,6 +49,7 @@ namespace profiler_gui { void selectedThreadChanged(::profiler::thread_id_t _id); void selectedBlockChanged(uint32_t _block_index); + void selectedBlockIdChanged(::profiler::block_id_t _id); void itemsExpandStateChanged(); void blockStatusChanged(::profiler::block_id_t _id, ::profiler::EasyBlockStatus _status); void connectionChanged(bool _connected); diff --git a/profiler_gui/main_window.cpp b/profiler_gui/main_window.cpp index 31c58a9..befde6d 100644 --- a/profiler_gui/main_window.cpp +++ b/profiler_gui/main_window.cpp @@ -246,22 +246,27 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("127.0.0.1"), m_lastP menu->addSeparator(); auto submenu = menu->addMenu("View"); + submenu->setToolTipsVisible(true); action = submenu->addAction("Draw items' borders"); + action->setToolTip("Draw borders for blocks on diagram.\nThis reduces performance."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.draw_graphics_items_borders); connect(action, &QAction::triggered, this, &This::onDrawBordersChanged); action = submenu->addAction("Hide narrow children"); + action->setToolTip("Children blocks will be hidden by narrow\nparent blocks. See also \"Blocks narrow size\".\nThis improves performance."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.hide_narrow_children); connect(action, &QAction::triggered, this, &This::onHideNarrowChildrenChanged); action = submenu->addAction("Build hierarchy only for current thread"); + action->setToolTip("Hierarchy tree will be built\nfor blocks from current thread only.\nThis improves performance\nand saves a lot of memory."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.only_current_thread_hierarchy); connect(action, &QAction::triggered, this, &This::onHierarchyFlagChange); action = submenu->addAction("Add zero blocks to hierarchy"); + action->setToolTip("Zero duration blocks will be added into hierarchy tree.\nThis reduces performance and increases memory consumption."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.add_zero_blocks_to_hierarchy); connect(action, &QAction::triggered, [this](bool _checked) @@ -270,32 +275,44 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("127.0.0.1"), m_lastP emit EASY_GLOBALS.events.hierarchyFlagChanged(_checked); }); - action = submenu->addAction("Enable zero length blocks"); + action = submenu->addAction("Enable zero duration blocks on diagram"); + action->setToolTip("If checked then allows diagram to paint zero duration blocks\nwith 1px width on each scale. Otherwise, such blocks will be resized\nto 250ns duration."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.enable_zero_length); connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.enable_zero_length = _checked; refreshDiagram(); }); action = submenu->addAction("Highlight similar blocks"); + action->setToolTip("Highlight all visible blocks which are similar\nto the current selected block.\nThis reduces performance."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.highlight_blocks_with_same_id); connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.highlight_blocks_with_same_id = _checked; refreshDiagram(); }); - action = submenu->addAction("Collapse items on tree reset"); + action = submenu->addAction("Collapse blocks on tree reset"); + action->setToolTip("This collapses all blocks on diagram\nafter hierarchy tree reset."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.collapse_items_on_tree_close); connect(action, &QAction::triggered, this, &This::onCollapseItemsAfterCloseChanged); action = submenu->addAction("Expand all on file open"); + action->setToolTip("If checked then all blocks on diagram\nwill be initially expanded."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.all_items_expanded_by_default); connect(action, &QAction::triggered, this, &This::onAllItemsExpandedByDefaultChange); - action = submenu->addAction("Bind scene and tree expand"); + action = submenu->addAction("Bind diagram and tree expand"); + action->setToolTip("Expanding/collapsing blocks at diagram expands/collapses\nblocks at hierarchy tree and wise versa."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.bind_scene_and_tree_expand_status); connect(action, &QAction::triggered, this, &This::onBindExpandStatusChange); + action = submenu->addAction("Selecting block changes current thread"); + action->setToolTip("Automatically select thread while selecting a block.\nIf not checked then you will have to select current thread\nmanually double clicking on thread name on a diagram."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.selecting_block_changes_thread); + connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.selecting_block_changes_thread = _checked; }); + action = submenu->addAction("Draw event indicators"); + action->setToolTip("Display event indicators under the blocks\n(even if event-blocks are not visible).\nThis slightly reduces performance."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.enable_event_indicators); connect(action, &QAction::triggered, this, &This::onEventIndicatorsChange); @@ -305,6 +322,7 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("127.0.0.1"), m_lastP actionGroup->setExclusive(true); action = new QAction("Chrono text at top", actionGroup); + action->setToolTip("Draw duration of selected interval\nat the top of the screen."); action->setCheckable(true); action->setData(static_cast(::profiler_gui::ChronoTextPosition_Top)); if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Top) @@ -313,6 +331,7 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("127.0.0.1"), m_lastP connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged); action = new QAction("Chrono text at center", actionGroup); + action->setToolTip("Draw duration of selected interval\nat the center of the screen."); action->setCheckable(true); action->setData(static_cast(::profiler_gui::ChronoTextPosition_Center)); if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Center) @@ -321,6 +340,7 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("127.0.0.1"), m_lastP connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged); action = new QAction("Chrono text at bottom", actionGroup); + action->setToolTip("Draw duration of selected interval\nat the bottom of the screen."); action->setCheckable(true); action->setData(static_cast(::profiler_gui::ChronoTextPosition_Bottom)); if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Bottom) @@ -332,7 +352,7 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("127.0.0.1"), m_lastP auto w = new QWidget(submenu); auto l = new QHBoxLayout(w); l->setContentsMargins(33, 1, 1, 1); - l->addWidget(new QLabel("Blocks spacing", w), 0, Qt::AlignLeft); + l->addWidget(new QLabel("Min blocks spacing, px", w), 0, Qt::AlignLeft); auto spinbox = new QSpinBox(w); spinbox->setMinimum(0); spinbox->setValue(EASY_GLOBALS.blocks_spacing); @@ -347,7 +367,7 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("127.0.0.1"), m_lastP w = new QWidget(submenu); l = new QHBoxLayout(w); l->setContentsMargins(33, 1, 1, 1); - l->addWidget(new QLabel("Blocks size min", w), 0, Qt::AlignLeft); + l->addWidget(new QLabel("Min blocks size, px", w), 0, Qt::AlignLeft); spinbox = new QSpinBox(w); spinbox->setMinimum(1); spinbox->setValue(EASY_GLOBALS.blocks_size_min); @@ -362,7 +382,7 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("127.0.0.1"), m_lastP w = new QWidget(submenu); l = new QHBoxLayout(w); l->setContentsMargins(33, 1, 1, 1); - l->addWidget(new QLabel("Blocks narrow size", w), 0, Qt::AlignLeft); + l->addWidget(new QLabel("Blocks narrow size, px", w), 0, Qt::AlignLeft); spinbox = new QSpinBox(w); spinbox->setMinimum(1); spinbox->setValue(EASY_GLOBALS.blocks_narrow_size); @@ -950,6 +970,10 @@ void EasyMainWindow::loadSettings() if (!flag.isNull()) EASY_GLOBALS.bind_scene_and_tree_expand_status = flag.toBool(); + flag = settings.value("selecting_block_changes_thread"); + if (!flag.isNull()) + EASY_GLOBALS.selecting_block_changes_thread = flag.toBool(); + flag = settings.value("enable_event_indicators"); if (!flag.isNull()) EASY_GLOBALS.enable_event_indicators = flag.toBool(); @@ -1007,6 +1031,7 @@ void EasyMainWindow::saveSettingsAndGeometry() settings.setValue("add_zero_blocks_to_hierarchy", EASY_GLOBALS.add_zero_blocks_to_hierarchy); settings.setValue("highlight_blocks_with_same_id", EASY_GLOBALS.highlight_blocks_with_same_id); settings.setValue("bind_scene_and_tree_expand_status", EASY_GLOBALS.bind_scene_and_tree_expand_status); + settings.setValue("selecting_block_changes_thread", EASY_GLOBALS.selecting_block_changes_thread); settings.setValue("enable_event_indicators", EASY_GLOBALS.enable_event_indicators); settings.setValue("enable_statistics", EASY_GLOBALS.enable_statistics); settings.setValue("encoding", QTextCodec::codecForLocale()->name()); @@ -1403,7 +1428,8 @@ void EasyMainWindow::onCaptureClicked(bool) m_listener.startCapture(); m_listenerTimer.start(250); - m_listenerDialog = new QMessageBox(QMessageBox::Information, "Capturing frames...", "Close this dialog to stop capturing.", QMessageBox::Close, this); + m_listenerDialog = new QMessageBox(QMessageBox::Information, "Capturing frames...", "Close this dialog to stop capturing.", QMessageBox::NoButton, this); + m_listenerDialog->addButton("Stop", QMessageBox::AcceptRole); m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true); connect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose); m_listenerDialog->show();