From c74744fae48dce4c4993febf358fcf4d99842491 Mon Sep 17 00:00:00 2001 From: Victor Zarubkin Date: Thu, 24 Oct 2019 19:21:31 +0300 Subject: [PATCH] [ui] added workaround for Qt memory leak on Linux when using multiple threads; [ui] added median duration into tree stats; [ui] added max rows count for "Call-stack" tree mode; --- CMakeLists.txt | 7 + .../easy/details/easy_compiler_support.h | 23 ++ easy_profiler_core/reader.cpp | 14 +- profiler_gui/blocks_tree_widget.cpp | 246 +++++++------ profiler_gui/common_functions.cpp | 20 ++ profiler_gui/common_functions.h | 6 + profiler_gui/descriptors_tree_widget.cpp | 9 +- profiler_gui/globals.cpp | 1 + profiler_gui/globals.h | 2 + profiler_gui/main_window.cpp | 31 ++ profiler_gui/main_window.h | 1 + profiler_gui/thread_pool.cpp | 98 ++++-- profiler_gui/thread_pool.h | 28 +- profiler_gui/thread_pool_task.cpp | 12 +- profiler_gui/thread_pool_task.h | 7 +- profiler_gui/tree_widget_item.cpp | 163 ++++----- profiler_gui/tree_widget_item.h | 33 +- profiler_gui/tree_widget_loader.cpp | 327 +++++++++++++++--- profiler_gui/tree_widget_loader.h | 42 ++- 19 files changed, 770 insertions(+), 300 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f1cfb3..1a0a3c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,13 @@ else () set(CMAKE_CXX_STANDARD_REQUIRED ON) endif () +if (MSVC) + if (NOT (MSVC_VERSION LESS 1914)) + # turn on valid __cplusplus macro value for visual studio (available since msvc 2017 update 7) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:__cplusplus") + endif () +endif () + option(EASY_PROFILER_NO_GUI "Build easy_profiler without the GUI application (required Qt)" OFF) set(EASY_PROGRAM_VERSION_MAJOR 2) diff --git a/easy_profiler_core/include/easy/details/easy_compiler_support.h b/easy_profiler_core/include/easy/details/easy_compiler_support.h index 587465b..7531dbe 100644 --- a/easy_profiler_core/include/easy/details/easy_compiler_support.h +++ b/easy_profiler_core/include/easy/details/easy_compiler_support.h @@ -62,6 +62,17 @@ # endif #endif +#ifdef __cplusplus +# if __cplusplus >= 201703L +# define EASY_STD 17 +# elif __cplusplus >= 201402L +# define EASY_STD 14 +# else +# define EASY_STD 11 +# endif +#else +# define EASY_STD 11 +#endif #if defined(_MSC_VER) @@ -92,6 +103,10 @@ # define EASY_NOEXCEPT throw() # endif +# if EASY_STD > 11 && _MSC_VER >= 1900 +# define EASY_LAMBDA_MOVE_CAPTURE +# endif + # define EASY_FORCE_INLINE __forceinline #elif defined(__clang__) @@ -123,6 +138,10 @@ # define EASY_FINAL # endif +# if EASY_STD > 11 && EASY_COMPILER_VERSION >= 34 +# define EASY_LAMBDA_MOVE_CAPTURE +# endif + # define EASY_FORCE_INLINE inline __attribute__((always_inline)) # undef EASY_COMPILER_VERSION @@ -160,6 +179,10 @@ # define EASY_FINAL # endif +# if EASY_STD > 11 && EASY_COMPILER_VERSION >= 49 +# define EASY_LAMBDA_MOVE_CAPTURE +# endif + # define EASY_FORCE_INLINE inline __attribute__((always_inline)) # undef EASY_COMPILER_VERSION diff --git a/easy_profiler_core/reader.cpp b/easy_profiler_core/reader.cpp index ffecdad..a15cfe2 100644 --- a/easy_profiler_core/reader.cpp +++ b/easy_profiler_core/reader.cpp @@ -136,7 +136,7 @@ using async_future = std::future; template struct Counter { - T value = 0; + T count = 0; }; struct Stats @@ -147,7 +147,7 @@ struct Stats Stats(profiler::BlockStatistics* stats_ptr, profiler::timestamp_t duration) EASY_NOEXCEPT : stats(stats_ptr) { - durations[duration].value = 1; + durations[duration].count = 1; } Stats(Stats&& another) EASY_NOEXCEPT @@ -393,7 +393,7 @@ static profiler::BlockStatistics* update_statistics( // write pointer to statistics into output (this is BlocksTree:: per_thread_stats or per_parent_stats or per_frame_stats) auto stats = it->second.stats; auto& durations = it->second.durations; - ++durations[duration].value; + ++durations[duration].count; ++stats->calls_number; // update calls number of this block stats->total_duration += duration; // update summary duration of all block calls @@ -457,7 +457,7 @@ static profiler::BlockStatistics* update_statistics( auto stats = it->second.stats; auto& durations = it->second.durations; - ++durations[duration].value; + ++durations[duration].count; ++stats->calls_number; // update calls number of this block stats->total_duration += duration; // update summary duration of all block calls @@ -515,7 +515,7 @@ static void calculate_medians(TStatsMapIterator begin, TStatsMapIterator end) size_t total_count = 0; for (auto& kv : durations) { - total_count += kv.second.value; + total_count += kv.second.count; } auto stats = it->second.stats; @@ -525,7 +525,7 @@ static void calculate_medians(TStatsMapIterator begin, TStatsMapIterator end) size_t i = 0; for (auto& kv : durations) { - const auto count = kv.second.value; + const auto count = kv.second.count; i += count; if (i < index) @@ -546,7 +546,7 @@ static void calculate_medians(TStatsMapIterator begin, TStatsMapIterator end) bool i1 = false; for (auto& kv : durations) { - const auto count = kv.second.value; + const auto count = kv.second.count; i += count; if (i < index1) diff --git a/profiler_gui/blocks_tree_widget.cpp b/profiler_gui/blocks_tree_widget.cpp index 2ab1203..e2c35ba 100644 --- a/profiler_gui/blocks_tree_widget.cpp +++ b/profiler_gui/blocks_tree_widget.cpp @@ -72,6 +72,7 @@ #include #include #include +#include #include #include #include @@ -99,99 +100,87 @@ const int HIERARCHY_BUILDER_TIMER_INTERVAL = 40; const bool PLAIN_MODE_COLUMNS[COL_COLUMNS_NUMBER] = { - true, //COL_NAME, - true, //COL_BEGIN, - true, //COL_TIME, - true, //COL_SELF_TIME, - - false, //COL_TOTAL_TIME_PER_PARENT, - false, //COL_TOTAL_TIME_PER_FRAME, - true, //COL_TOTAL_TIME_PER_THREAD, - - true, //COL_SELF_TIME_PERCENT, - - false, //COL_PERCENT_PER_PARENT, - true, //COL_PERCENT_PER_FRAME, - - false, //COL_PERCENT_SUM_PER_PARENT, - false, //COL_PERCENT_SUM_PER_FRAME, - true, //COL_PERCENT_SUM_PER_THREAD, - - true, //COL_END, - - true, //COL_MIN_PER_FRAME, - true, //COL_MAX_PER_FRAME, - true, //COL_AVG_PER_FRAME, - true, //COL_NCALLS_PER_FRAME, - - true, //COL_MIN_PER_THREAD, - true, //COL_MAX_PER_THREAD, - true, //COL_AVG_PER_THREAD, - true, //COL_NCALLS_PER_THREAD, - - false, //COL_MIN_PER_PARENT, - false, //COL_MAX_PER_PARENT, - false, //COL_AVG_PER_PARENT, - false, //COL_NCALLS_PER_PARENT, - - true, //COL_ACTIVE_TIME, - true, //COL_ACTIVE_PERCENT, - - true, //COL_PERCENT_PER_AREA, - true, //COL_TOTAL_TIME_PER_AREA, - true, //COL_PERCENT_SUM_PER_AREA, - true, //COL_MIN_PER_AREA, - true, //COL_MAX_PER_AREA, - true, //COL_AVG_PER_AREA, - true //COL_NCALLS_PER_AREA, + true // COL_NAME = 0, + , true // COL_BEGIN, + , true // COL_TIME, + , true // COL_SELF_TIME, + , true // COL_SELF_TIME_PERCENT, + , true // COL_END, + , true // COL_PERCENT_PER_FRAME, + , false // COL_TOTAL_TIME_PER_FRAME, + , false // COL_PERCENT_SUM_PER_FRAME, + , true // COL_MIN_PER_FRAME, + , true // COL_MAX_PER_FRAME, + , true // COL_AVG_PER_FRAME, + , true // COL_MEDIAN_PER_FRAME, + , true // COL_NCALLS_PER_FRAME, + , true // COL_TOTAL_TIME_PER_THREAD, + , true // COL_PERCENT_SUM_PER_THREAD, + , true // COL_MIN_PER_THREAD, + , true // COL_MAX_PER_THREAD, + , true // COL_AVG_PER_THREAD, + , true // COL_MEDIAN_PER_THREAD, + , true // COL_NCALLS_PER_THREAD, + , false // COL_PERCENT_PER_PARENT, + , false // COL_TOTAL_TIME_PER_PARENT, + , false // COL_PERCENT_SUM_PER_PARENT, + , false // COL_MIN_PER_PARENT, + , false // COL_MAX_PER_PARENT, + , false // COL_AVG_PER_PARENT, + , false // COL_MEDIAN_PER_PARENT, + , false // COL_NCALLS_PER_PARENT, + , true // COL_ACTIVE_TIME, + , true // COL_ACTIVE_PERCENT, + , true // COL_PERCENT_PER_AREA, + , true // COL_TOTAL_TIME_PER_AREA, + , true // COL_PERCENT_SUM_PER_AREA, + , true // COL_MIN_PER_AREA, + , true // COL_MAX_PER_AREA, + , true // COL_AVG_PER_AREA, + , true // COL_MEDIAN_PER_AREA, + , true // COL_NCALLS_PER_AREA, }; const bool SELECTION_MODE_COLUMNS[COL_COLUMNS_NUMBER] = { - true, //COL_NAME, - false, //COL_BEGIN, - true, //COL_TIME, - true, //COL_SELF_TIME, - - false, //COL_TOTAL_TIME_PER_PARENT, - false, //COL_TOTAL_TIME_PER_FRAME, - true, //COL_TOTAL_TIME_PER_THREAD, - - true, //COL_SELF_TIME_PERCENT, - - false, //COL_PERCENT_PER_PARENT, - false, //COL_PERCENT_PER_FRAME, - - false, //COL_PERCENT_SUM_PER_PARENT, - false, //COL_PERCENT_SUM_PER_FRAME, - true, //COL_PERCENT_SUM_PER_THREAD, - - false, //COL_END, - - false, //COL_MIN_PER_FRAME, - false, //COL_MAX_PER_FRAME, - false, //COL_AVG_PER_FRAME, - false, //COL_NCALLS_PER_FRAME, - - true, //COL_MIN_PER_THREAD, - true, //COL_MAX_PER_THREAD, - true, //COL_AVG_PER_THREAD, - true, //COL_NCALLS_PER_THREAD, - - false, //COL_MIN_PER_PARENT, - false, //COL_MAX_PER_PARENT, - false, //COL_AVG_PER_PARENT, - false, //COL_NCALLS_PER_PARENT, - - true, //COL_ACTIVE_TIME, - true, //COL_ACTIVE_PERCENT, - - false, //COL_PERCENT_PER_AREA, - true, //COL_TOTAL_TIME_PER_AREA, - true, //COL_PERCENT_SUM_PER_AREA, - true, //COL_MIN_PER_AREA, - true, //COL_MAX_PER_AREA, - true, //COL_AVG_PER_AREA, - true //COL_NCALLS_PER_AREA, + true // COL_NAME = 0, + , false // COL_BEGIN, + , true // COL_TIME, + , true // COL_SELF_TIME, + , true // COL_SELF_TIME_PERCENT, + , false // COL_END, + , false // COL_PERCENT_PER_FRAME, + , false // COL_TOTAL_TIME_PER_FRAME, + , false // COL_PERCENT_SUM_PER_FRAME, + , false // COL_MIN_PER_FRAME, + , false // COL_MAX_PER_FRAME, + , false // COL_AVG_PER_FRAME, + , false // COL_MEDIAN_PER_FRAME, + , false // COL_NCALLS_PER_FRAME, + , true // COL_TOTAL_TIME_PER_THREAD, + , true // COL_PERCENT_SUM_PER_THREAD, + , true // COL_MIN_PER_THREAD, + , true // COL_MAX_PER_THREAD, + , true // COL_AVG_PER_THREAD, + , true // COL_MEDIAN_PER_THREAD, + , true // COL_NCALLS_PER_THREAD, + , false // COL_PERCENT_PER_PARENT, + , false // COL_TOTAL_TIME_PER_PARENT, + , false // COL_PERCENT_SUM_PER_PARENT, + , false // COL_MIN_PER_PARENT, + , false // COL_MAX_PER_PARENT, + , false // COL_AVG_PER_PARENT, + , false // COL_MEDIAN_PER_PARENT, + , false // COL_NCALLS_PER_PARENT, + , true // COL_ACTIVE_TIME, + , true // COL_ACTIVE_PERCENT, + , true // COL_PERCENT_PER_AREA, + , true // COL_TOTAL_TIME_PER_AREA, + , true // COL_PERCENT_SUM_PER_AREA, + , true // COL_MIN_PER_AREA, + , true // COL_MAX_PER_AREA, + , true // COL_AVG_PER_AREA, + , true // COL_MEDIAN_PER_AREA, + , true // COL_NCALLS_PER_AREA, }; ////////////////////////////////////////////////////////////////////////// @@ -249,16 +238,19 @@ BlocksTreeWidget::BlocksTreeWidget(QWidget* _parent) header_item->setText(COL_MIN_PER_FRAME, "Min/frame"); header_item->setText(COL_MAX_PER_FRAME, "Max/frame"); header_item->setText(COL_AVG_PER_FRAME, "Avg/frame"); + header_item->setText(COL_MEDIAN_PER_FRAME, "Mdn/frame"); header_item->setText(COL_NCALLS_PER_FRAME, "N/frame"); header_item->setText(COL_MIN_PER_PARENT, "Min/parent"); header_item->setText(COL_MAX_PER_PARENT, "Max/parent"); header_item->setText(COL_AVG_PER_PARENT, "Avg/parent"); + header_item->setText(COL_MEDIAN_PER_PARENT, "Mdn/parent"); header_item->setText(COL_NCALLS_PER_PARENT, "N/parent"); header_item->setText(COL_MIN_PER_THREAD, "Min/thread"); header_item->setText(COL_MAX_PER_THREAD, "Max/thread"); header_item->setText(COL_AVG_PER_THREAD, "Avg/thread"); + header_item->setText(COL_MEDIAN_PER_THREAD, "Mdn/thread"); header_item->setText(COL_NCALLS_PER_THREAD, "N/thread"); header_item->setText(COL_ACTIVE_TIME, "WorkTime"); @@ -270,12 +262,14 @@ BlocksTreeWidget::BlocksTreeWidget(QWidget* _parent) header_item->setText(COL_MIN_PER_AREA, "Min/area"); header_item->setText(COL_MAX_PER_AREA, "Max/area"); header_item->setText(COL_AVG_PER_AREA, "Avg/area"); + header_item->setText(COL_MEDIAN_PER_AREA, "Mdn/area"); header_item->setText(COL_NCALLS_PER_AREA, "N/area"); auto color = QColor::fromRgb(profiler::colors::DeepOrange900); header_item->setForeground(COL_MIN_PER_THREAD, color); header_item->setForeground(COL_MAX_PER_THREAD, color); header_item->setForeground(COL_AVG_PER_THREAD, color); + header_item->setForeground(COL_MEDIAN_PER_THREAD, color); header_item->setForeground(COL_NCALLS_PER_THREAD, color); header_item->setForeground(COL_PERCENT_SUM_PER_THREAD, color); header_item->setForeground(COL_TOTAL_TIME_PER_THREAD, color); @@ -284,6 +278,7 @@ BlocksTreeWidget::BlocksTreeWidget(QWidget* _parent) header_item->setForeground(COL_MIN_PER_FRAME, color); header_item->setForeground(COL_MAX_PER_FRAME, color); header_item->setForeground(COL_AVG_PER_FRAME, color); + header_item->setForeground(COL_MEDIAN_PER_FRAME, color); header_item->setForeground(COL_NCALLS_PER_FRAME, color); header_item->setForeground(COL_PERCENT_SUM_PER_FRAME, color); header_item->setForeground(COL_TOTAL_TIME_PER_FRAME, color); @@ -293,6 +288,7 @@ BlocksTreeWidget::BlocksTreeWidget(QWidget* _parent) header_item->setForeground(COL_MIN_PER_PARENT, color); header_item->setForeground(COL_MAX_PER_PARENT, color); header_item->setForeground(COL_AVG_PER_PARENT, color); + header_item->setForeground(COL_MEDIAN_PER_PARENT, color); header_item->setForeground(COL_NCALLS_PER_PARENT, color); header_item->setForeground(COL_PERCENT_SUM_PER_PARENT, color); header_item->setForeground(COL_TOTAL_TIME_PER_PARENT, color); @@ -305,6 +301,7 @@ BlocksTreeWidget::BlocksTreeWidget(QWidget* _parent) header_item->setForeground(COL_MIN_PER_AREA, color); header_item->setForeground(COL_MAX_PER_AREA, color); header_item->setForeground(COL_AVG_PER_AREA, color); + header_item->setForeground(COL_MEDIAN_PER_AREA, color); header_item->setForeground(COL_NCALLS_PER_AREA, color); setHeaderItem(header_item); @@ -502,6 +499,7 @@ void BlocksTreeWidget::onFillTimerTimeout() ThreadedItems toplevelitems; m_hierarchyBuilder.takeItems(m_items); m_hierarchyBuilder.takeTopLevelItems(toplevelitems); + auto error = m_hierarchyBuilder.error(); m_hierarchyBuilder.interrupt(); { const QSignalBlocker b(this); @@ -550,6 +548,12 @@ void BlocksTreeWidget::onFillTimerTimeout() connect(this, &Parent::itemDoubleClicked, this, &This::onItemDoubleClicked); onSelectedThreadChange(EASY_GLOBALS.selected_thread); onSelectedBlockChange(EASY_GLOBALS.selected_block); + + if (!error.isEmpty()) + { + QMessageBox::warning(this, "Warning", error, QMessageBox::Close); + clearSilent(); + } } else if (m_progress != nullptr) { @@ -578,7 +582,7 @@ void BlocksTreeWidget::onIdleTimeout() return; const int column = columnAt(pos.x()); - if (item->hasToolTip(column)) + if (!item->data(column, Qt::ToolTipRole).isNull()) return; auto focusWidget = qApp->focusWidget(); @@ -675,9 +679,14 @@ void BlocksTreeWidget::clearSilent(bool _global) for (int i = topLevelItemCount() - 1; i >= 0; --i) topLevelItems.push_back(takeTopLevelItem(i)); - ThreadPool::instance().backgroundJob([=] { +#ifdef EASY_LAMBDA_MOVE_CAPTURE + ThreadPool::instance().backgroundJob([items = std::move(topLevelItems)] { + for (auto item : items) +#else + ThreadPool::instance().backgroundJob([topLevelItems] { for (auto item : topLevelItems) - delete item; +#endif + profiler_gui::deleteTreeItem(item); }); } @@ -947,9 +956,11 @@ void BlocksTreeWidget::contextMenuEvent(QContextMenuEvent* _event) case COL_MIN_PER_THREAD: case COL_MIN_PER_PARENT: case COL_MIN_PER_FRAME: + case COL_MIN_PER_AREA: case COL_MAX_PER_THREAD: case COL_MAX_PER_PARENT: case COL_MAX_PER_FRAME: + case COL_MAX_PER_AREA: { auto& block = item->block(); auto i = profiler_gui::numeric_max(); @@ -961,6 +972,22 @@ void BlocksTreeWidget::contextMenuEvent(QContextMenuEvent* _event) case COL_MAX_PER_THREAD: i = block.per_thread_stats->max_duration_block; break; case COL_MAX_PER_PARENT: i = block.per_parent_stats->max_duration_block; break; case COL_MAX_PER_FRAME: i = block.per_frame_stats->max_duration_block; break; + + case COL_MIN_PER_AREA: + { + auto data = item->data(COL_MIN_PER_AREA, MinMaxBlockIndexRole); + if (!data.isNull()) + i = data.toUInt(); + break; + } + + case COL_MAX_PER_AREA: + { + auto data = item->data(COL_MAX_PER_AREA, MinMaxBlockIndexRole); + if (!data.isNull()) + i = data.toUInt(); + break; + } } if (i != profiler_gui::numeric_max(i)) @@ -1036,6 +1063,7 @@ void BlocksTreeWidget::contextMenuEvent(QContextMenuEvent* _event) ADD_COLUMN_ACTION(COL_MIN_PER_FRAME); ADD_COLUMN_ACTION(COL_MAX_PER_FRAME); ADD_COLUMN_ACTION(COL_AVG_PER_FRAME); + ADD_COLUMN_ACTION(COL_MEDIAN_PER_FRAME); ADD_COLUMN_ACTION(COL_NCALLS_PER_FRAME); hidemenu->addSeparator(); @@ -1045,6 +1073,7 @@ void BlocksTreeWidget::contextMenuEvent(QContextMenuEvent* _event) ADD_COLUMN_ACTION(COL_MIN_PER_THREAD); ADD_COLUMN_ACTION(COL_MAX_PER_THREAD); ADD_COLUMN_ACTION(COL_AVG_PER_THREAD); + ADD_COLUMN_ACTION(COL_MEDIAN_PER_THREAD); ADD_COLUMN_ACTION(COL_NCALLS_PER_THREAD); hidemenu->addSeparator(); @@ -1055,6 +1084,7 @@ void BlocksTreeWidget::contextMenuEvent(QContextMenuEvent* _event) ADD_COLUMN_ACTION(COL_MIN_PER_PARENT); ADD_COLUMN_ACTION(COL_MAX_PER_PARENT); ADD_COLUMN_ACTION(COL_AVG_PER_PARENT); + ADD_COLUMN_ACTION(COL_MEDIAN_PER_PARENT); ADD_COLUMN_ACTION(COL_NCALLS_PER_PARENT); hidemenu->addSeparator(); @@ -1070,6 +1100,7 @@ void BlocksTreeWidget::contextMenuEvent(QContextMenuEvent* _event) ADD_COLUMN_ACTION(COL_MIN_PER_AREA); ADD_COLUMN_ACTION(COL_MAX_PER_AREA); ADD_COLUMN_ACTION(COL_AVG_PER_AREA); + ADD_COLUMN_ACTION(COL_MEDIAN_PER_AREA); ADD_COLUMN_ACTION(COL_NCALLS_PER_AREA); #undef ADD_STATUS_ACTION @@ -1473,19 +1504,29 @@ void BlocksTreeWidget::loadSettings() settings.beginGroup("tree_widget"); auto val = settings.value("regime"); - if (!val.isNull()) - m_mode = static_cast(val.toUInt()); - - val = settings.value("columns"); if (!val.isNull()) { - auto byteArray = val.toByteArray(); - memcpy(m_columnsHiddenStatus, byteArray.constData(), ::std::min(sizeof(m_columnsHiddenStatus), (size_t)byteArray.size())); + m_mode = static_cast(val.toUInt()); } - auto state = settings.value("headerState").toByteArray(); - if (!state.isEmpty()) - header()->restoreState(state); + val = settings.value("columns_version"); + if (!val.isNull() && val.toInt() == COLUMNS_VERSION) + { + val = settings.value("columns"); + if (!val.isNull()) + { + auto byteArray = val.toByteArray(); + memcpy( + m_columnsHiddenStatus, byteArray.constData(), std::min(sizeof(m_columnsHiddenStatus), (size_t)byteArray.size()) + ); + } + + auto state = settings.value("headerState").toByteArray(); + if (!state.isEmpty()) + { + header()->restoreState(state); + } + } settings.endGroup(); } @@ -1495,6 +1536,7 @@ void BlocksTreeWidget::saveSettings() QSettings settings(profiler_gui::ORGANAZATION_NAME, profiler_gui::APPLICATION_NAME); settings.beginGroup("tree_widget"); settings.setValue("regime", static_cast(m_mode)); + settings.setValue("columns_version", COLUMNS_VERSION); settings.setValue("columns", QByteArray(m_columnsHiddenStatus, COL_COLUMNS_NUMBER)); settings.setValue("headerState", header()->saveState()); settings.endGroup(); diff --git a/profiler_gui/common_functions.cpp b/profiler_gui/common_functions.cpp index ad2fa8f..706ff91 100644 --- a/profiler_gui/common_functions.cpp +++ b/profiler_gui/common_functions.cpp @@ -52,6 +52,8 @@ * : limitations under the License. ************************************************************************/ +#include +#include #include "common_functions.h" template @@ -459,4 +461,22 @@ namespace profiler_gui { ////////////////////////////////////////////////////////////////////////// + void deleteTreeItem(QTreeWidgetItem* item) + { + if (item == nullptr) + { + return; + } + + QList stack; + stack.append(item); + + while (!stack.isEmpty()) + { + auto i = stack.takeFirst(); + stack.append(i->takeChildren()); + delete i; + } + } + } // end of namespace profiler_gui. diff --git a/profiler_gui/common_functions.h b/profiler_gui/common_functions.h index 4ed631b..e440684 100644 --- a/profiler_gui/common_functions.h +++ b/profiler_gui/common_functions.h @@ -66,6 +66,8 @@ #include "common_types.h" +class QTreeWidgetItem; + ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -237,6 +239,10 @@ void updateProperty(QWidget* widget, const char* name, T&& property) } } +/////////////////////////////////////////////////////////////////////// + +void deleteTreeItem(QTreeWidgetItem* item); + } // END of namespace profiler_gui. ////////////////////////////////////////////////////////////////////////// diff --git a/profiler_gui/descriptors_tree_widget.cpp b/profiler_gui/descriptors_tree_widget.cpp index 49f4dd5..4343cb1 100644 --- a/profiler_gui/descriptors_tree_widget.cpp +++ b/profiler_gui/descriptors_tree_widget.cpp @@ -459,9 +459,14 @@ void DescriptorsTreeWidget::clearSilent(bool _global) topLevelItems.push_back(item); } - ThreadPool::instance().backgroundJob([=] { +#ifdef EASY_LAMBDA_MOVE_CAPTURE + ThreadPool::instance().backgroundJob([items = std::move(topLevelItems)] { + for (auto item : items) +#else + ThreadPool::instance().backgroundJob([topLevelItems] { for (auto item : topLevelItems) - delete item; +#endif + profiler_gui::deleteTreeItem(item); }); } diff --git a/profiler_gui/globals.cpp b/profiler_gui/globals.cpp index 9aee7e0..e59c738 100644 --- a/profiler_gui/globals.cpp +++ b/profiler_gui/globals.cpp @@ -85,6 +85,7 @@ Globals::Globals() , selected_block(::profiler_gui::numeric_max()) , selected_block_id(::profiler_gui::numeric_max()) , version(0) + , max_rows_count(500 * 1000) , frame_time(16700) , blocks_spacing(0) , blocks_size_min(2) diff --git a/profiler_gui/globals.h b/profiler_gui/globals.h index d5099a1..aeca179 100644 --- a/profiler_gui/globals.h +++ b/profiler_gui/globals.h @@ -213,6 +213,8 @@ namespace profiler_gui { ::profiler::block_id_t selected_block_id; ///< Current selected profiler block id uint32_t version; ///< Opened file version (files may have different format) + uint32_t max_rows_count; ///< Maximum blocks count for the Hierarchy widget for the full call-stack mode + float frame_time; ///< Expected frame time value in microseconds to be displayed at minimap on graphics scrollbar int blocks_spacing; ///< Minimum blocks spacing on diagram int blocks_size_min; ///< Minimum blocks size on diagram diff --git a/profiler_gui/main_window.cpp b/profiler_gui/main_window.cpp index 46582ff..e659cf2 100644 --- a/profiler_gui/main_window.cpp +++ b/profiler_gui/main_window.cpp @@ -722,6 +722,22 @@ MainWindow::MainWindow() : Parent(), m_theme("default"), m_lastAddress("localhos submenu->addAction(waction); + submenu->addSeparator(); + w = new QWidget(submenu); + l = new QHBoxLayout(w); + l->setContentsMargins(26, 1, 16, 1); + l->addWidget(new QLabel("Max rows in tree", w), 0, Qt::AlignLeft); + spinbox = new QSpinBox(w); + spinbox->setRange(0, std::numeric_limits::max()); + spinbox->setValue(static_cast(EASY_GLOBALS.max_rows_count)); + spinbox->setFixedWidth(px(100)); + connect(spinbox, Overload::of(&QSpinBox::valueChanged), this, &This::onMaxBlocksCountChange); + l->addWidget(spinbox); + w->setLayout(l); + waction = new QWidgetAction(submenu); + waction->setDefaultWidget(w); + submenu->addAction(waction); + submenu = menu->addMenu("FPS Monitor"); @@ -1470,6 +1486,16 @@ void MainWindow::onViewportInfoClicked(bool) ////////////////////////////////////////////////////////////////////////// +void MainWindow::onMaxBlocksCountChange(int _value) +{ + if (_value >= 0) + { + EASY_GLOBALS.max_rows_count = static_cast(_value); + } +} + +////////////////////////////////////////////////////////////////////////// + void MainWindow::onSpacingChange(int _value) { EASY_GLOBALS.blocks_spacing = _value; @@ -1676,6 +1702,10 @@ void MainWindow::loadSettings() if (!val.isNull()) EASY_GLOBALS.frame_time = val.toFloat(); + val = settings.value("max_rows_count"); + if (!val.isNull()) + EASY_GLOBALS.max_rows_count = val.toUInt(); + val = settings.value("blocks_spacing"); if (!val.isNull()) EASY_GLOBALS.blocks_spacing = val.toInt(); @@ -1853,6 +1883,7 @@ void MainWindow::saveSettingsAndGeometry() settings.setValue("chrono_text_position", static_cast(EASY_GLOBALS.chrono_text_position)); settings.setValue("time_units", static_cast(EASY_GLOBALS.time_units)); settings.setValue("frame_time", EASY_GLOBALS.frame_time); + settings.setValue("max_rows_count", EASY_GLOBALS.max_rows_count); settings.setValue("blocks_spacing", EASY_GLOBALS.blocks_spacing); settings.setValue("blocks_size_min", EASY_GLOBALS.blocks_size_min); settings.setValue("blocks_narrow_size", EASY_GLOBALS.blocks_narrow_size); diff --git a/profiler_gui/main_window.h b/profiler_gui/main_window.h index ccfc626..bcdf374 100644 --- a/profiler_gui/main_window.h +++ b/profiler_gui/main_window.h @@ -344,6 +344,7 @@ protected slots: void onExpandAllClicked(bool); void onCollapseAllClicked(bool); void onViewportInfoClicked(bool); + void onMaxBlocksCountChange(int _value); void onSpacingChange(int _value); void onMinSizeChange(int _value); void onNarrowSizeChange(int _value); diff --git a/profiler_gui/thread_pool.cpp b/profiler_gui/thread_pool.cpp index 50958e7..6191e36 100644 --- a/profiler_gui/thread_pool.cpp +++ b/profiler_gui/thread_pool.cpp @@ -69,7 +69,7 @@ # pragma message "TODO: include proper headers to be able to use pthread_setschedprio() on OSX and iOS (pthread.h is not enough...)" #endif -void setLowestThreadPriority() +static void setLowestThreadPriority() { #ifdef _WIN32 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); @@ -95,13 +95,32 @@ ThreadPool& ThreadPool::instance() ThreadPool::ThreadPool() { - m_threads.reserve(std::thread::hardware_concurrency() + 1); + auto count = std::thread::hardware_concurrency(); +#ifdef EASY_THREADPOOL_SEPARATE_QT_THREAD + if (count > 1) + count -= 1; + m_threads.reserve(count + 2); +#else + m_threads.reserve(count + 1); +#endif // N threads for main tasks - std::generate_n(std::back_inserter(m_threads), std::thread::hardware_concurrency(), [this] { - return std::thread(&ThreadPool::tasksWorker, this); + std::generate_n(std::back_inserter(m_threads), count, [this] { + return std::thread(&ThreadPool::tasksWorker, this, std::ref(m_tasks)); }); +#ifdef EASY_THREADPOOL_SEPARATE_QT_THREAD + // FIXME: Workaround for Qt on Linux + // + // There is a memory leak in Qt when creating Qt objects in several separate threads + // and removing them in one another (different) thread. + // + // But there is no memory leak when there is only one separate thread (apart from main thread) + // for creating Qt objects and one separate (different!) thread for removing them. + // + m_threads.emplace_back(&ThreadPool::tasksWorker, this, std::ref(m_qtasks)); +#endif + // One thread for background jobs m_threads.emplace_back(&ThreadPool::jobsWorker, this); } @@ -110,6 +129,9 @@ ThreadPool::~ThreadPool() { m_interrupt.store(true, std::memory_order_release); m_tasks.cv.notify_all(); +#ifdef EASY_THREADPOOL_SEPARATE_QT_THREAD + m_qtasks.cv.notify_all(); +#endif m_backgroundJobs.cv.notify_all(); for (auto& thread : m_threads) thread.join(); @@ -125,53 +147,69 @@ void ThreadPool::backgroundJob(std::function&& func) void ThreadPool::enqueue(ThreadPoolTask& task) { - m_tasks.mutex.lock(); - m_tasks.queue.emplace_back(task); - m_tasks.mutex.unlock(); - m_tasks.cv.notify_one(); +#ifdef EASY_THREADPOOL_SEPARATE_QT_THREAD + if (task.creatingQtObjects()) + enqueue(task, m_qtasks); + else +#endif + enqueue(task, m_tasks); } void ThreadPool::dequeue(ThreadPoolTask& task) { - const std::lock_guard lock(m_tasks.mutex); +#ifdef EASY_THREADPOOL_SEPARATE_QT_THREAD + if (task.creatingQtObjects()) + dequeue(task, m_qtasks); + else +#endif + dequeue(task, m_tasks); +} + +void ThreadPool::enqueue(ThreadPoolTask& task, TaskJobs& tasks) +{ + tasks.mutex.lock(); + tasks.queue.emplace_back(task); + tasks.mutex.unlock(); + tasks.cv.notify_one(); +} + +void ThreadPool::dequeue(ThreadPoolTask& task, TaskJobs& tasks) +{ + const std::lock_guard lock(tasks.mutex); if (task.status() != TaskStatus::Enqueued) return; - for (auto it = m_tasks.queue.begin(); it != m_tasks.queue.end(); ++it) + for (auto it = tasks.queue.begin(); it != tasks.queue.end(); ++it) { if (&it->get() == &task) { - m_tasks.queue.erase(it); + tasks.queue.erase(it); break; } } } -void ThreadPool::tasksWorker() +void ThreadPool::tasksWorker(Jobs >& tasks) { - while (true) + while (!m_interrupt.load(std::memory_order_acquire)) { - std::unique_lock lock(m_tasks.mutex); - m_tasks.cv.wait(lock, [this] { return !m_tasks.queue.empty() || m_interrupt.load(std::memory_order_acquire); }); + std::unique_lock lock(tasks.mutex); + tasks.cv.wait(lock, [this, &tasks] { + return !tasks.queue.empty() || m_interrupt.load(std::memory_order_acquire); + }); - while (true) // execute all available tasks + while (!tasks.queue.empty() && !m_interrupt.load(std::memory_order_acquire)) // execute all available tasks { - if (m_interrupt.load(std::memory_order_acquire)) - return; - - if (m_tasks.queue.empty()) - break; // the lock will be released on the outer loop new iteration - - auto& task = m_tasks.queue.front().get(); + auto& task = tasks.queue.front().get(); task.setStatus(TaskStatus::Processing); - m_tasks.queue.pop_front(); + tasks.queue.pop_front(); // unlock to permit tasks execution for other worker threads and for adding new tasks lock.unlock(); // execute task - task.execute(); + task(); // lock again to check if there are new tasks in the queue lock.lock(); @@ -183,21 +221,15 @@ void ThreadPool::jobsWorker() { setLowestThreadPriority(); // Background thread has lowest priority - while (true) + while (!m_interrupt.load(std::memory_order_acquire)) { std::unique_lock lock(m_backgroundJobs.mutex); m_backgroundJobs.cv.wait(lock, [this] { return !m_backgroundJobs.queue.empty() || m_interrupt.load(std::memory_order_acquire); }); - while (true) // execute all available jobs + while (!m_backgroundJobs.queue.empty()) // execute all available jobs { - if (m_interrupt.load(std::memory_order_acquire)) - return; - - if (m_backgroundJobs.queue.empty()) - break; // the lock will be released on the outer loop new iteration - auto job = std::move(m_backgroundJobs.queue.front()); m_backgroundJobs.queue.pop_front(); diff --git a/profiler_gui/thread_pool.h b/profiler_gui/thread_pool.h index 636e39f..8ea4e2e 100644 --- a/profiler_gui/thread_pool.h +++ b/profiler_gui/thread_pool.h @@ -58,6 +58,18 @@ #include #include +#if !defined(_WIN32) && !defined(__APPLE__) +// FIXME: Workaround for Qt on Linux +// +// There is a memory leak in Qt when creating Qt objects in several separate threads +// and removing them in one another (different) thread. +// +// But there is no memory leak when there is only one separate thread (apart from main thread) +// for creating Qt objects and one separate (different!) thread for removing them. +// +#define EASY_THREADPOOL_SEPARATE_QT_THREAD +#endif + class ThreadPool EASY_FINAL { friend ThreadPoolTask; @@ -70,8 +82,14 @@ class ThreadPool EASY_FINAL std::condition_variable cv; }; - Jobs > m_tasks; - Jobs > m_backgroundJobs; + using TaskJobs = Jobs >; + using BackgroundJobs = Jobs >; + + TaskJobs m_tasks; +#ifdef EASY_THREADPOOL_SEPARATE_QT_THREAD + TaskJobs m_qtasks; +#endif + BackgroundJobs m_backgroundJobs; std::vector m_threads; std::atomic_bool m_interrupt; @@ -89,7 +107,11 @@ private: void enqueue(ThreadPoolTask& task); void dequeue(ThreadPoolTask& task); - void tasksWorker(); + + void enqueue(ThreadPoolTask& task, TaskJobs& tasks); + void dequeue(ThreadPoolTask& task, TaskJobs& tasks); + + void tasksWorker(TaskJobs& tasks); void jobsWorker(); }; // end of class ThreadPool. diff --git a/profiler_gui/thread_pool_task.cpp b/profiler_gui/thread_pool_task.cpp index 031ec61..5839795 100644 --- a/profiler_gui/thread_pool_task.cpp +++ b/profiler_gui/thread_pool_task.cpp @@ -53,7 +53,10 @@ static std::atomic_bool s_dummy_flag {false}; -ThreadPoolTask::ThreadPoolTask() : m_func([] {}), m_interrupt(&s_dummy_flag) +ThreadPoolTask::ThreadPoolTask(bool creatingQtObjects) + : m_func([] {}) + , m_interrupt(&s_dummy_flag) + , m_creatingQtObjects(creatingQtObjects) { m_status = static_cast(TaskStatus::Finished); } @@ -64,6 +67,11 @@ ThreadPoolTask::~ThreadPoolTask() dequeue(); } +bool ThreadPoolTask::creatingQtObjects() const +{ + return m_creatingQtObjects; +} + void ThreadPoolTask::enqueue(Func&& func, std::atomic_bool& interruptFlag) { dequeue(); @@ -101,7 +109,7 @@ TaskStatus ThreadPoolTask::status() const return static_cast(m_status.load(std::memory_order_acquire)); } -void ThreadPoolTask::execute() +void ThreadPoolTask::operator()() { // execute if not cancelled { diff --git a/profiler_gui/thread_pool_task.h b/profiler_gui/thread_pool_task.h index fb26238..f309fe6 100644 --- a/profiler_gui/thread_pool_task.h +++ b/profiler_gui/thread_pool_task.h @@ -93,13 +93,14 @@ private: std::atomic_bool* m_interrupt; std::mutex m_mutex; std::atomic m_status; + const bool m_creatingQtObjects; public: ThreadPoolTask(const ThreadPoolTask&) = delete; ThreadPoolTask(ThreadPoolTask&&) = delete; - ThreadPoolTask(); + explicit ThreadPoolTask(bool creatingQtObjects = false); ~ThreadPoolTask(); void enqueue(Func&& func, std::atomic_bool& interruptFlag); @@ -109,7 +110,9 @@ public: private: - void execute(); + bool creatingQtObjects() const; + + void operator() (); TaskStatus status() const; void setStatus(TaskStatus status); diff --git a/profiler_gui/tree_widget_item.cpp b/profiler_gui/tree_widget_item.cpp index 2d1caf7..187c645 100644 --- a/profiler_gui/tree_widget_item.cpp +++ b/profiler_gui/tree_widget_item.cpp @@ -71,55 +71,48 @@ ////////////////////////////////////////////////////////////////////////// -EASY_CONSTEXPR int BlockColorRole = Qt::UserRole + 1; - namespace { -EASY_CONSTEXPR int ColumnBit[COL_COLUMNS_NUMBER] = { - -1 // COL_NAME = 0, - - , 0 // COL_BEGIN, - - , 1 // COL_TIME, - , 2 // COL_SELF_TIME, - , 3 // COL_TOTAL_TIME_PER_PARENT, - , 4 // COL_TOTAL_TIME_PER_FRAME, - , 5 // COL_TOTAL_TIME_PER_THREAD, - - , -1 // COL_SELF_TIME_PERCENT, - , -1 // COL_PERCENT_PER_PARENT, - , -1 // COL_PERCENT_PER_FRAME, - , -1 // COL_PERCENT_SUM_PER_PARENT, - , -1 // COL_PERCENT_SUM_PER_FRAME, - , -1 // COL_PERCENT_SUM_PER_THREAD, - - , 6 // COL_END, - - , 7 // COL_MIN_PER_FRAME, - , 8 // COL_MAX_PER_FRAME, - , 9 // COL_AVG_PER_FRAME, - , -1 // COL_NCALLS_PER_FRAME, - - , 10 // COL_MIN_PER_THREAD, - , 11 // COL_MAX_PER_THREAD, - , 12 // COL_AVG_PER_THREAD, - , -1 // COL_NCALLS_PER_THREAD, - - , 13 // COL_MIN_PER_PARENT, - , 14 // COL_MAX_PER_PARENT, - , 15 // COL_AVG_PER_PARENT, - , -1 // COL_NCALLS_PER_PARENT, - - , 16 // COL_ACTIVE_TIME, - , -1 // COL_ACTIVE_PERCENT, - - , -1 // COL_PERCENT_PER_AREA, - , 17 // COL_TOTAL_TIME_PER_AREA, - , -1 // COL_PERCENT_SUM_PER_AREA, - , 18 // COL_MIN_PER_AREA, - , 19 // COL_MAX_PER_AREA, - , 20 // COL_AVG_PER_AREA, - , -1 // COL_NCALLS_PER_AREA, +EASY_CONSTEXPR bool HasToolTip[COL_COLUMNS_NUMBER] = { + false // COL_NAME = 0, + , true // COL_BEGIN, + , true // COL_TIME, + , true // COL_SELF_TIME, + , false // COL_SELF_TIME_PERCENT, + , true // COL_END, + , false // COL_PERCENT_PER_FRAME, + , true // COL_TOTAL_TIME_PER_FRAME, + , false // COL_PERCENT_SUM_PER_FRAME, + , true // COL_MIN_PER_FRAME, + , true // COL_MAX_PER_FRAME, + , true // COL_AVG_PER_FRAME, + , true // COL_MEDIAN_PER_FRAME, + , false // COL_NCALLS_PER_FRAME, + , true // COL_TOTAL_TIME_PER_THREAD, + , false // COL_PERCENT_SUM_PER_THREAD, + , true // COL_MIN_PER_THREAD, + , true // COL_MAX_PER_THREAD, + , true // COL_AVG_PER_THREAD, + , true // COL_MEDIAN_PER_THREAD, + , false // COL_NCALLS_PER_THREAD, + , false // COL_PERCENT_PER_PARENT, + , true // COL_TOTAL_TIME_PER_PARENT, + , false // COL_PERCENT_SUM_PER_PARENT, + , true // COL_MIN_PER_PARENT, + , true // COL_MAX_PER_PARENT, + , true // COL_AVG_PER_PARENT, + , true // COL_MEDIAN_PER_PARENT, + , false // COL_NCALLS_PER_PARENT, + , true // COL_ACTIVE_TIME, + , false // COL_ACTIVE_PERCENT, + , false // COL_PERCENT_PER_AREA, + , true // COL_TOTAL_TIME_PER_AREA, + , false // COL_PERCENT_SUM_PER_AREA, + , true // COL_MIN_PER_AREA, + , true // COL_MAX_PER_AREA, + , true // COL_AVG_PER_AREA, + , true // COL_MEDIAN_PER_AREA, + , false // COL_NCALLS_PER_AREA, }; } // end of namespace . @@ -192,19 +185,6 @@ bool TreeWidgetItem::isPartial() const return m_partial; } -bool TreeWidgetItem::hasToolTip(int _column) const -{ - const int bit = ColumnBit[_column]; - return bit < 0 ? false : m_bHasToolTip.test(static_cast(bit)); -} - -void TreeWidgetItem::setHasToolTip(int _column) -{ - const int bit = ColumnBit[_column]; - if (bit >= 0) - m_bHasToolTip.set(static_cast(bit), true); -} - QVariant TreeWidgetItem::data(int _column, int _role) const { if (_column == COL_NAME) @@ -233,15 +213,29 @@ QVariant TreeWidgetItem::data(int _column, int _role) const case Qt::ToolTipRole: { - if (hasToolTip(_column)) - return QVariant::fromValue(QString("%1 ns").arg(data(_column, Qt::UserRole).toULongLong())); + const bool hasToolTip = HasToolTip[_column]; + if (hasToolTip) + { + auto v = data(_column, Qt::UserRole); + if (!v.isNull()) + return QString("%1 ns").arg(v.toULongLong()); + } break; } + case MinMaxBlockIndexRole: + { + auto v = Parent::data(_column, _role); + if (!v.isNull() || parent() == nullptr) + return v; + return QVariant::fromValue(m_block); + } + default: { - if (_role != Qt::UserRole && _role != Qt::DisplayRole) - return QTreeWidgetItem::data(_column, _role); + auto v = Parent::data(_column, _role); + if (!v.isNull() || parent() == nullptr || (_role != Qt::UserRole && _role != Qt::DisplayRole)) + return v; return relevantData(_column, _role); } } @@ -269,7 +263,7 @@ QVariant TreeWidgetItem::relevantData(int _column, int _role) const case COL_PERCENT_PER_AREA: case COL_PERCENT_SUM_PER_THREAD: { - return Parent::data(_column, _role); + return QVariant(); } default: @@ -278,10 +272,9 @@ QVariant TreeWidgetItem::relevantData(int _column, int _role) const } } - auto var = Parent::data(_column, _role); - if (!var.isNull() || (EASY_GLOBALS.display_only_relevant_stats && _role == Qt::DisplayRole)) + if (EASY_GLOBALS.display_only_relevant_stats && _role == Qt::DisplayRole) { - return var; + return QVariant(); } switch (_column) @@ -302,23 +295,27 @@ QVariant TreeWidgetItem::relevantData(int _column, int _role) const case COL_AVG_PER_FRAME: case COL_AVG_PER_THREAD: case COL_AVG_PER_AREA: + case COL_MEDIAN_PER_PARENT: + case COL_MEDIAN_PER_FRAME: + case COL_MEDIAN_PER_THREAD: + case COL_MEDIAN_PER_AREA: { - return Parent::data(COL_TIME, _role); + return data(COL_TIME, _role); } case COL_PERCENT_SUM_PER_PARENT: { - return Parent::data(COL_PERCENT_PER_PARENT, _role); + return data(COL_PERCENT_PER_PARENT, _role); } case COL_PERCENT_SUM_PER_FRAME: { - return Parent::data(COL_PERCENT_PER_FRAME, _role); + return data(COL_PERCENT_PER_FRAME, _role); } case COL_PERCENT_SUM_PER_AREA: { - return Parent::data(COL_PERCENT_PER_AREA, _role); + return data(COL_PERCENT_PER_AREA, _role); } default: @@ -327,7 +324,7 @@ QVariant TreeWidgetItem::relevantData(int _column, int _role) const } } - return var; + return QVariant(); } QVariant TreeWidgetItem::partialForeground() const @@ -384,24 +381,12 @@ profiler::thread_id_t TreeWidgetItem::threadId() const return static_cast(parentItem->data(COL_NAME, Qt::UserRole).toULongLong()); } -profiler::timestamp_t TreeWidgetItem::duration() const -{ - if (parent() != nullptr) - return block().node->duration(); - return data(COL_TIME, Qt::UserRole).toULongLong(); -} - -profiler::timestamp_t TreeWidgetItem::selfDuration() const -{ - return data(COL_SELF_TIME, Qt::UserRole).toULongLong(); -} - void TreeWidgetItem::setTimeSmart(int _column, profiler_gui::TimeUnits _units, const profiler::timestamp_t& _time, const QString& _prefix) { const profiler::timestamp_t nanosecondsTime = PROF_NANOSECONDS(_time); setData(_column, Qt::UserRole, (quint64)nanosecondsTime); - setHasToolTip(_column); + //setHasToolTip(_column); setText(_column, QString("%1%2").arg(_prefix).arg(profiler_gui::timeStringRealNs(_units, nanosecondsTime, 3))); } @@ -410,7 +395,7 @@ void TreeWidgetItem::setTimeSmart(int _column, profiler_gui::TimeUnits _units, c const profiler::timestamp_t nanosecondsTime = PROF_NANOSECONDS(_time); setData(_column, Qt::UserRole, (quint64)nanosecondsTime); - setHasToolTip(_column); + //setHasToolTip(_column); setText(_column, profiler_gui::timeStringRealNs(_units, nanosecondsTime, 3)); } @@ -418,7 +403,7 @@ void TreeWidgetItem::setTimeMs(int _column, const profiler::timestamp_t& _time) { const profiler::timestamp_t nanosecondsTime = PROF_NANOSECONDS(_time); setData(_column, Qt::UserRole, (quint64)nanosecondsTime); - setHasToolTip(_column); + //setHasToolTip(_column); setText(_column, QString::number(double(nanosecondsTime) * 1e-6, 'g', 9)); } @@ -426,7 +411,7 @@ void TreeWidgetItem::setTimeMs(int _column, const profiler::timestamp_t& _time, { const profiler::timestamp_t nanosecondsTime = PROF_NANOSECONDS(_time); setData(_column, Qt::UserRole, (quint64)nanosecondsTime); - setHasToolTip(_column); + //setHasToolTip(_column); setText(_column, QString("%1%2").arg(_prefix).arg(double(nanosecondsTime) * 1e-6, 0, 'g', 9)); } diff --git a/profiler_gui/tree_widget_item.h b/profiler_gui/tree_widget_item.h index 9be3aa0..f5cb01b 100644 --- a/profiler_gui/tree_widget_item.h +++ b/profiler_gui/tree_widget_item.h @@ -61,7 +61,6 @@ #include #include #include -#include #include "common_functions.h" @@ -69,6 +68,10 @@ class BlocksTreeWidget; ////////////////////////////////////////////////////////////////////////// +EASY_CONSTEXPR int COLUMNS_VERSION = 3; +EASY_CONSTEXPR int BlockColorRole = Qt::UserRole + 1; +EASY_CONSTEXPR int MinMaxBlockIndexRole = Qt::UserRole + 2; + enum EasyColumnsIndexes { COL_UNKNOWN = -1, @@ -79,32 +82,34 @@ enum EasyColumnsIndexes COL_TIME, COL_SELF_TIME, - COL_TOTAL_TIME_PER_PARENT, - COL_TOTAL_TIME_PER_FRAME, - COL_TOTAL_TIME_PER_THREAD, - COL_SELF_TIME_PERCENT, - COL_PERCENT_PER_PARENT, - COL_PERCENT_PER_FRAME, - COL_PERCENT_SUM_PER_PARENT, - COL_PERCENT_SUM_PER_FRAME, - COL_PERCENT_SUM_PER_THREAD, COL_END, + COL_PERCENT_PER_FRAME, + COL_TOTAL_TIME_PER_FRAME, + COL_PERCENT_SUM_PER_FRAME, COL_MIN_PER_FRAME, COL_MAX_PER_FRAME, COL_AVG_PER_FRAME, + COL_MEDIAN_PER_FRAME, COL_NCALLS_PER_FRAME, + COL_TOTAL_TIME_PER_THREAD, + COL_PERCENT_SUM_PER_THREAD, COL_MIN_PER_THREAD, COL_MAX_PER_THREAD, COL_AVG_PER_THREAD, + COL_MEDIAN_PER_THREAD, COL_NCALLS_PER_THREAD, + COL_PERCENT_PER_PARENT, + COL_TOTAL_TIME_PER_PARENT, + COL_PERCENT_SUM_PER_PARENT, COL_MIN_PER_PARENT, COL_MAX_PER_PARENT, COL_AVG_PER_PARENT, + COL_MEDIAN_PER_PARENT, COL_NCALLS_PER_PARENT, COL_ACTIVE_TIME, @@ -116,6 +121,7 @@ enum EasyColumnsIndexes COL_MIN_PER_AREA, COL_MAX_PER_AREA, COL_AVG_PER_AREA, + COL_MEDIAN_PER_AREA, COL_NCALLS_PER_AREA, COL_COLUMNS_NUMBER @@ -130,7 +136,6 @@ class TreeWidgetItem : public QTreeWidgetItem const profiler::block_index_t m_block; QRgb m_customBGColor; - std::bitset<21> m_bHasToolTip; bool m_bMain; bool m_partial; @@ -147,14 +152,10 @@ public: public: bool isPartial() const; - bool hasToolTip(int _column) const; profiler::block_index_t block_index() const; profiler_gui::EasyBlock& guiBlock(); const profiler::BlocksTree& block() const; - profiler::timestamp_t duration() const; - profiler::timestamp_t selfDuration() const; - profiler::thread_id_t threadId() const; void setTimeSmart(int _column, profiler_gui::TimeUnits _units, const profiler::timestamp_t& _time, const QString& _prefix); @@ -174,7 +175,7 @@ public: private: - void setHasToolTip(int _column); + //void setHasToolTip(int _column); QVariant relevantData(int _column, int _role) const; QVariant partialForeground() const; diff --git a/profiler_gui/tree_widget_loader.cpp b/profiler_gui/tree_widget_loader.cpp index f50a630..d6eaf19 100644 --- a/profiler_gui/tree_widget_loader.cpp +++ b/profiler_gui/tree_widget_loader.cpp @@ -54,10 +54,13 @@ * : limitations under the License. ************************************************************************/ -#include "tree_widget_loader.h" -#include "tree_widget_item.h" +#include + #include "globals.h" #include "thread_pool.h" +#include "tree_widget_item.h" + +#include "tree_widget_loader.h" #ifdef max #undef max @@ -75,15 +78,94 @@ #define EASY_INIT_ATOMIC(v) {v} #endif +namespace { + struct ThreadData { - StatsMap stats; + StatsMap stats; IdItems iditems; TreeWidgetItem* item = nullptr; }; using ThreadDataMap = std::unordered_map >; +void calculate_medians(StatsMap::iterator begin, StatsMap::iterator end) +{ + for (auto it = begin; it != end; ++it) + { + auto& durations = it->second.durations; + if (durations.empty()) + { + 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; + } + } + + decltype(it->second.durations) dummy; + dummy.swap(durations); + } +} + +} // end of namespace . + static void fillStatsColumns( TreeWidgetItem* item, const profiler::BlockStatistics* stats, @@ -91,12 +173,19 @@ static void fillStatsColumns( int min_column, int max_column, int avg_column, + int median_column, int total_column, int n_calls_column ) { item->setData(n_calls_column, Qt::UserRole, stats->calls_number); item->setText(n_calls_column, QString::number(stats->calls_number)); + if (min_column == COL_MIN_PER_AREA) + { + item->setData(min_column, MinMaxBlockIndexRole, stats->min_duration_block); + item->setData(max_column, MinMaxBlockIndexRole, stats->max_duration_block); + } + if (stats->calls_number < 2)// && EASY_GLOBALS.display_only_relevant_stats) { return; @@ -106,21 +195,23 @@ static void fillStatsColumns( const auto max_duration = easyBlock(stats->max_duration_block).tree.node->duration(); const auto avg_duration = stats->average_duration(); const auto tot_duration = stats->total_duration; + const auto median_duration = stats->median_duration; item->setTimeSmart(min_column, units, min_duration); item->setTimeSmart(max_column, units, max_duration); item->setTimeSmart(avg_column, units, avg_duration); + item->setTimeSmart(median_column, units, median_duration); item->setTimeSmart(total_column, units, tot_duration); if (stats->calls_number > 1 && tot_duration != 0) { if (max_duration >= (tot_duration >> 1)) { - item->setForeground(max_column, QColor::fromRgb(profiler::colors::Red500)); + item->setForeground(max_column, QColor::fromRgb(profiler::colors::RedA700)); } else if (max_duration >= (tot_duration >> 2)) { - item->setForeground(max_column, QColor::fromRgb(profiler::colors::Red700)); + item->setForeground(max_column, QColor::fromRgb(profiler::colors::OrangeA400)); } } } @@ -134,6 +225,7 @@ inline void fillStatsColumnsThread(TreeWidgetItem* item, const profiler::BlockSt COL_MIN_PER_THREAD, COL_MAX_PER_THREAD, COL_AVG_PER_THREAD, + COL_MEDIAN_PER_THREAD, COL_TOTAL_TIME_PER_THREAD, COL_NCALLS_PER_THREAD ); @@ -148,6 +240,7 @@ inline void fillStatsColumnsFrame(TreeWidgetItem* item, const profiler::BlockSta COL_MIN_PER_FRAME, COL_MAX_PER_FRAME, COL_AVG_PER_FRAME, + COL_MEDIAN_PER_FRAME, COL_TOTAL_TIME_PER_FRAME, COL_NCALLS_PER_FRAME ); @@ -162,6 +255,7 @@ inline void fillStatsColumnsParent(TreeWidgetItem* item, const profiler::BlockSt COL_MIN_PER_PARENT, COL_MAX_PER_PARENT, COL_AVG_PER_PARENT, + COL_MEDIAN_PER_PARENT, COL_TOTAL_TIME_PER_PARENT, COL_NCALLS_PER_PARENT ); @@ -176,13 +270,15 @@ inline void fillStatsColumnsSelection(TreeWidgetItem* item, const profiler::Bloc COL_MIN_PER_AREA, COL_MAX_PER_AREA, COL_AVG_PER_AREA, + COL_MEDIAN_PER_AREA, COL_TOTAL_TIME_PER_AREA, COL_NCALLS_PER_AREA ); } TreeWidgetLoader::TreeWidgetLoader() - : m_bDone(EASY_INIT_ATOMIC(false)) + : m_worker(true) + , m_bDone(EASY_INIT_ATOMIC(false)) , m_bInterrupt(EASY_INIT_ATOMIC(false)) , m_progress(EASY_INIT_ATOMIC(0)) , m_mode(TreeMode::Full) @@ -238,6 +334,11 @@ void TreeWidgetLoader::takeItems(Items& _output) } } +QString TreeWidgetLoader::error() const +{ + return done() ? m_error : QString(); +} + void TreeWidgetLoader::interrupt(bool _wait) { m_worker.dequeue(); @@ -248,21 +349,28 @@ void TreeWidgetLoader::interrupt(bool _wait) { if (!_wait) { - auto items = std::move(m_topLevelItems); - ThreadPool::instance().backgroundJob([=] { + auto topLevelItems = std::move(m_topLevelItems); + +#ifdef EASY_LAMBDA_MOVE_CAPTURE + ThreadPool::instance().backgroundJob([items = std::move(topLevelItems)] { for (auto item : items) - delete item.second; +#else + ThreadPool::instance().backgroundJob([=] { + for (auto item : topLevelItems) +#endif + profiler_gui::deleteTreeItem(item.second); }); } else { for (auto item : m_topLevelItems) - delete item.second; + profiler_gui::deleteTreeItem(item.second); } } m_items.clear(); m_topLevelItems.clear(); + m_error.clear(); } void TreeWidgetLoader::fillTreeBlocks( @@ -280,6 +388,7 @@ void TreeWidgetLoader::fillTreeBlocks( const auto decoratedNames = EASY_GLOBALS.use_decorated_thread_name; const auto hexThreadIds = EASY_GLOBALS.hex_thread_id; const auto timeUnits = EASY_GLOBALS.time_units; + const auto maxCount = EASY_GLOBALS.max_rows_count; const auto blocks = std::ref(_blocks); const auto mode = m_mode; m_worker.enqueue([=] { @@ -287,17 +396,48 @@ void TreeWidgetLoader::fillTreeBlocks( { case TreeMode::Full: { - setTreeInternalTop(_beginTime, blocks, _left, _right, _strict, zeroBlocks, decoratedNames, hexThreadIds, timeUnits); + setTreeInternalTop( + _beginTime, + blocks, + _left, + _right, + _strict, + zeroBlocks, + decoratedNames, + hexThreadIds, + timeUnits, + maxCount + ); break; } case TreeMode::Plain: { - setTreeInternalPlainTop(_beginTime, blocks, _left, _right, _strict, zeroBlocks, decoratedNames, hexThreadIds, timeUnits); + setTreeInternalPlainTop( + _beginTime, + blocks, + _left, + _right, + _strict, + zeroBlocks, + decoratedNames, + hexThreadIds, + timeUnits + ); break; } case TreeMode::SelectedArea: { - setTreeInternalAggregateTop(_beginTime, blocks, _left, _right, _strict, zeroBlocks, decoratedNames, hexThreadIds, timeUnits); + setTreeInternalAggregateTop( + _beginTime, + blocks, + _left, + _right, + _strict, + zeroBlocks, + decoratedNames, + hexThreadIds, + timeUnits + ); break; } } @@ -306,14 +446,6 @@ void TreeWidgetLoader::fillTreeBlocks( ////////////////////////////////////////////////////////////////////////// -// auto calculateTotalChildrenNumber(const profiler::BlocksTree& _tree) -> decltype(_tree.children.size()) -// { -// auto children_number = _tree.children.size(); -// for (auto i : _tree.children) -// children_number += calculateTotalChildrenNumber(easyBlocksTree(i)); -// return children_number; -// } - using BeginEndIndicesMap = std::unordered_map >; @@ -326,13 +458,79 @@ void TreeWidgetLoader::setTreeInternalTop( bool _addZeroBlocks, bool _decoratedThreadNames, bool _hexThreadId, - profiler_gui::TimeUnits _units + profiler_gui::TimeUnits _units, + size_t _maxCount ) { BeginEndIndicesMap beginEndMap; ThreadDataMap threadsMap; + auto total = static_cast(_blocks.size()); + + uint32_t total_count = 0; + int i = 0; + for (const auto& block : _blocks) + { + if (interrupted()) + { + setDone(); + return; + } + + const auto& gui_block = easyBlock(block.tree); + const auto& tree = gui_block.tree; + const auto startTime = tree.node->begin(); + const auto endTime = tree.node->end(); + + if (startTime > _right || endTime < _left) + { + setProgress((3 * ++i) / total); + continue; + } + + const profiler::timestamp_t duration = endTime - startTime; + const bool partial = _strict && (startTime < _left || endTime > _right); + if (partial && duration != 0 && (startTime == _right || endTime == _left)) + { + setProgress((3 * ++i) / total); + continue; + } + + total_count += calculateChildrenCountRecursive(tree.children, startTime, endTime, _strict, partial, _addZeroBlocks) + 1; + + setProgress((3 * ++i) / total); + } + + 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)); + } + + setDone(); + + return; + } + const auto u_thread = profiler_gui::toUnicode("thread"); - int i = 0, total = static_cast(_blocks.size()); + + i = 0; for (const auto& block : _blocks) { if (interrupted()) @@ -345,7 +543,7 @@ void TreeWidgetLoader::setTreeInternalTop( if (startTime > _right || endTime < _left) { - setProgress((95 * ++i) / total); + setProgress(3 + (92 * ++i) / total); continue; } @@ -354,7 +552,7 @@ void TreeWidgetLoader::setTreeInternalTop( const bool partial = _strict && (startTime < _left || endTime > _right); if (partial && duration != 0 && (startTime == _right || endTime == _left)) { - setProgress((95 * ++i) / total); + setProgress(3 + (92 * ++i) / total); continue; } @@ -465,7 +663,7 @@ void TreeWidgetLoader::setTreeInternalTop( if (partial && children_items_number == 0) { delete item; - setProgress((95 * ++i) / total); + setProgress(3 + (92 * ++i) / total); continue; } } @@ -481,14 +679,14 @@ void TreeWidgetLoader::setTreeInternalTop( item->setData(COL_SELF_TIME_PERCENT, Qt::UserRole, percentage); item->setText(COL_SELF_TIME_PERCENT, QString::number(percentage)); - //total_items += children_items_number + 1; + updateStats(stats, tree.node->id(), block.tree, duration, children_duration); if (gui_block.expanded) item->setExpanded(true); m_items.insert(std::make_pair(block.tree, item)); - setProgress((95 * ++i) / total); + setProgress(3 + (92 * ++i) / total); } i = 0; @@ -1088,7 +1286,7 @@ size_t TreeWidgetLoader::setTreeInternal( const auto per_parent_stats = child.per_parent_stats; const auto per_frame_stats = child.per_frame_stats; - auto parent_duration = _parent->duration(); + auto parent_duration = _parent->data(COL_TIME, Qt::UserRole).toULongLong(); auto percentage = duration == 0 ? 0 : profiler_gui::percent(duration, parent_duration); auto percentage_sum = profiler_gui::percent(per_parent_stats->total_duration, parent_duration); item->setData(COL_PERCENT_PER_PARENT, Qt::UserRole, percentage); @@ -1100,7 +1298,7 @@ size_t TreeWidgetLoader::setTreeInternal( { if (_parent != _frame) { - parent_duration = _frame->duration(); + parent_duration = _frame->data(COL_TIME, Qt::UserRole).toULongLong(); percentage = duration == 0 ? 0 : profiler_gui::percent(duration, parent_duration); percentage_sum = profiler_gui::percent(per_frame_stats->total_duration, parent_duration); } @@ -1194,8 +1392,10 @@ size_t TreeWidgetLoader::setTreeInternal( ////////////////////////////////////////////////////////////////////////// -profiler::timestamp_t TreeWidgetLoader::calculateChildrenDurationRecursive(const profiler::BlocksTree::children_t& _children, profiler::block_id_t _id) -{ +profiler::timestamp_t TreeWidgetLoader::calculateChildrenDurationRecursive( + const profiler::BlocksTree::children_t& _children, + profiler::block_id_t _id +) const { profiler::timestamp_t total_duration = 0; for (auto child_index : _children) @@ -1212,6 +1412,45 @@ profiler::timestamp_t TreeWidgetLoader::calculateChildrenDurationRecursive(const return total_duration; } +uint32_t TreeWidgetLoader::calculateChildrenCountRecursive( + const profiler::BlocksTree::children_t& children, + profiler::timestamp_t left, + profiler::timestamp_t right, + bool strict, + bool partial_parent, + bool addZeroBlocks +) const { + uint32_t count = 0; + + for (auto child_index : children) + { + if (interrupted()) + break; + + const auto& gui_block = easyBlock(child_index); + const auto& child = gui_block.tree; + const auto startTime = child.node->begin(); + const auto endTime = child.node->end(); + const auto duration = endTime - startTime; + + if (startTime > right || endTime < left) + continue; + + const bool partial = strict && (startTime < left || endTime > right); + if (partial && partial_parent && duration != 0 && (startTime == right || endTime == left)) + continue; + + const auto& desc = easyDescriptor(child.node->id()); + + if (duration == 0 && !addZeroBlocks && desc.type() == profiler::BlockType::Block) + continue; + + count += calculateChildrenCountRecursive(gui_block.tree.children, left, right, strict, partial, addZeroBlocks) + 1; + } + + return count; +} + size_t TreeWidgetLoader::setTreeInternalPlain( const profiler::BlocksTreeRoot& threadRoot, IdItems& iditems, @@ -1322,7 +1561,8 @@ size_t TreeWidgetLoader::setTreeInternalPlain( item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, percentage_per_thread); item->setText(COL_PERCENT_SUM_PER_THREAD, QString::number(percentage_per_thread)); - const auto percentage_sum = profiler_gui::percent(per_frame_stats->total_duration, frame->duration()); + const auto frame_duration = frame->data(COL_TIME, Qt::UserRole).toULongLong(); + const auto percentage_sum = profiler_gui::percent(per_frame_stats->total_duration, frame_duration); item->setData(COL_PERCENT_PER_FRAME, Qt::UserRole, percentage_sum); item->setText(COL_PERCENT_PER_FRAME, QString::number(percentage_sum)); item->setTimeSmart(COL_TIME, units, per_frame_stats->total_duration); @@ -1603,12 +1843,15 @@ void TreeWidgetLoader::updateStats( auto stats_it = statsMap.find(id); if (stats_it == statsMap.end()) { - stats_it = statsMap.emplace(id, profiler::BlockStatistics(duration, index, 0)).first; - stats_it->second.total_children_duration = children_duration; + stats_it = statsMap.emplace(id, loader::Stats(duration, index, 0)).first; + auto& stat = stats_it->second.stats; + stat.total_children_duration = children_duration; } else { - auto& stat = stats_it->second; + auto& stat = stats_it->second.stats; + auto& durations = stats_it->second.durations; + ++stat.calls_number; stat.total_duration += duration; stat.total_children_duration += children_duration; @@ -1622,18 +1865,22 @@ void TreeWidgetLoader::updateStats( { stat.min_duration_block = index; } + + ++durations[duration].count; } } ////////////////////////////////////////////////////////////////////////// -void TreeWidgetLoader::fillStatsForTree(TreeWidgetItem* root, const StatsMap& stats, profiler_gui::TimeUnits _units, profiler::timestamp_t selectionDuration) const +void TreeWidgetLoader::fillStatsForTree(TreeWidgetItem* root, StatsMap& stats, profiler_gui::TimeUnits _units, profiler::timestamp_t selectionDuration) const { if (stats.empty()) { return; } + calculate_medians(stats.begin(), stats.end()); + std::deque queue; if (root->parent() != nullptr) @@ -1660,11 +1907,11 @@ void TreeWidgetLoader::fillStatsForTree(TreeWidgetItem* root, const StatsMap& st auto stat_it = stats.find(item->block().node->id()); if (stat_it != stats.end()) { - const auto& stat = stat_it->second; + auto& stat = stat_it->second; - fillStatsColumnsSelection(item, &stat, _units); + fillStatsColumnsSelection(item, &stat.stats, _units); - auto percent_per_selection = std::min(100, profiler_gui::percent(stat.total_duration, selectionDuration)); + auto percent_per_selection = std::min(100, profiler_gui::percent(stat.stats.total_duration, selectionDuration)); item->setData(COL_PERCENT_SUM_PER_AREA, Qt::UserRole, percent_per_selection); item->setText(COL_PERCENT_SUM_PER_AREA, QString::number(percent_per_selection)); diff --git a/profiler_gui/tree_widget_loader.h b/profiler_gui/tree_widget_loader.h index fc56fd5..3e5e098 100644 --- a/profiler_gui/tree_widget_loader.h +++ b/profiler_gui/tree_widget_loader.h @@ -68,11 +68,29 @@ class TreeWidgetItem; +namespace loader { + +struct Stats +{ + struct Counter { uint32_t count = 0; }; + + profiler::BlockStatistics stats; + std::map durations; + + Stats(profiler::timestamp_t duration, profiler::block_index_t block_index, profiler::block_index_t parent_index) + : stats(duration, block_index, parent_index) + { + durations[duration].count = 1; + } +}; + +} // end of namespace loader. + using Items = ::std::unordered_map >; using ThreadedItems = std::vector >; using RootsMap = std::unordered_map >; using IdItems = std::unordered_map, estd::hash >; -using StatsMap = std::unordered_map >; +using StatsMap = std::unordered_map >; ////////////////////////////////////////////////////////////////////////// @@ -90,6 +108,7 @@ class TreeWidgetLoader Q_DECL_FINAL ThreadedItems m_topLevelItems; ///< Items m_items; ///< ThreadPoolTask m_worker; ///< + QString m_error; ///< std::atomic_bool m_bDone; ///< std::atomic_bool m_bInterrupt; ///< std::atomic m_progress; ///< @@ -106,6 +125,8 @@ public: void takeTopLevelItems(ThreadedItems& _output); void takeItems(Items& _output); + QString error() const; + void interrupt(bool _wait = false); void fillTreeBlocks( const::profiler_gui::TreeBlocks& _blocks, @@ -130,7 +151,8 @@ private: bool _addZeroBlocks, bool _decoratedThreadNames, bool _hexThreadId, - ::profiler_gui::TimeUnits _units + ::profiler_gui::TimeUnits _units, + size_t _maxCount ); size_t setTreeInternal( @@ -213,11 +235,23 @@ private: int depth ); - profiler::timestamp_t calculateChildrenDurationRecursive(const profiler::BlocksTree::children_t& _children, profiler::block_id_t _id); + profiler::timestamp_t calculateChildrenDurationRecursive( + const profiler::BlocksTree::children_t& _children, + profiler::block_id_t _id + ) const; + + uint32_t calculateChildrenCountRecursive( + const profiler::BlocksTree::children_t& children, + profiler::timestamp_t left, + profiler::timestamp_t right, + bool strict, + bool partial_parent, + bool addZeroBlocks + ) const; profiler::timestamp_t calculateIdleTime(const profiler::BlocksTreeRoot& _threadRoot, profiler::block_index_t& _firstCSwitch, profiler::timestamp_t _begin, profiler::timestamp_t _end) const; void updateStats(StatsMap& stats, profiler::block_id_t id, profiler::block_index_t index, profiler::timestamp_t duration, profiler::timestamp_t children_duration) const; - void fillStatsForTree(TreeWidgetItem* root, const StatsMap& stats, profiler_gui::TimeUnits _units, profiler::timestamp_t selectionDuration) const; + void fillStatsForTree(TreeWidgetItem* root, StatsMap& stats, profiler_gui::TimeUnits _units, profiler::timestamp_t selectionDuration) const; }; // END of class TreeWidgetLoader.