0
0
mirror of https://github.com/yse/easy_profiler.git synced 2024-12-26 08:01:51 +08:00

[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;
This commit is contained in:
Victor Zarubkin 2019-10-24 19:21:31 +03:00
parent b4494bcef9
commit c74744fae4
19 changed files with 770 additions and 300 deletions

View File

@ -12,6 +12,13 @@ else ()
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif () 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) option(EASY_PROFILER_NO_GUI "Build easy_profiler without the GUI application (required Qt)" OFF)
set(EASY_PROGRAM_VERSION_MAJOR 2) set(EASY_PROGRAM_VERSION_MAJOR 2)

View File

@ -62,6 +62,17 @@
# endif # endif
#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) #if defined(_MSC_VER)
@ -92,6 +103,10 @@
# define EASY_NOEXCEPT throw() # define EASY_NOEXCEPT throw()
# endif # endif
# if EASY_STD > 11 && _MSC_VER >= 1900
# define EASY_LAMBDA_MOVE_CAPTURE
# endif
# define EASY_FORCE_INLINE __forceinline # define EASY_FORCE_INLINE __forceinline
#elif defined(__clang__) #elif defined(__clang__)
@ -123,6 +138,10 @@
# define EASY_FINAL # define EASY_FINAL
# endif # endif
# if EASY_STD > 11 && EASY_COMPILER_VERSION >= 34
# define EASY_LAMBDA_MOVE_CAPTURE
# endif
# define EASY_FORCE_INLINE inline __attribute__((always_inline)) # define EASY_FORCE_INLINE inline __attribute__((always_inline))
# undef EASY_COMPILER_VERSION # undef EASY_COMPILER_VERSION
@ -160,6 +179,10 @@
# define EASY_FINAL # define EASY_FINAL
# endif # endif
# if EASY_STD > 11 && EASY_COMPILER_VERSION >= 49
# define EASY_LAMBDA_MOVE_CAPTURE
# endif
# define EASY_FORCE_INLINE inline __attribute__((always_inline)) # define EASY_FORCE_INLINE inline __attribute__((always_inline))
# undef EASY_COMPILER_VERSION # undef EASY_COMPILER_VERSION

View File

@ -136,7 +136,7 @@ using async_future = std::future<async_result_t>;
template <class T> template <class T>
struct Counter struct Counter
{ {
T value = 0; T count = 0;
}; };
struct Stats struct Stats
@ -147,7 +147,7 @@ struct Stats
Stats(profiler::BlockStatistics* stats_ptr, profiler::timestamp_t duration) EASY_NOEXCEPT Stats(profiler::BlockStatistics* stats_ptr, profiler::timestamp_t duration) EASY_NOEXCEPT
: stats(stats_ptr) : stats(stats_ptr)
{ {
durations[duration].value = 1; durations[duration].count = 1;
} }
Stats(Stats&& another) EASY_NOEXCEPT 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) // 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 stats = it->second.stats;
auto& durations = it->second.durations; auto& durations = it->second.durations;
++durations[duration].value; ++durations[duration].count;
++stats->calls_number; // update calls number of this block ++stats->calls_number; // update calls number of this block
stats->total_duration += duration; // update summary duration of all block calls 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 stats = it->second.stats;
auto& durations = it->second.durations; auto& durations = it->second.durations;
++durations[duration].value; ++durations[duration].count;
++stats->calls_number; // update calls number of this block ++stats->calls_number; // update calls number of this block
stats->total_duration += duration; // update summary duration of all block calls 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; size_t total_count = 0;
for (auto& kv : durations) for (auto& kv : durations)
{ {
total_count += kv.second.value; total_count += kv.second.count;
} }
auto stats = it->second.stats; auto stats = it->second.stats;
@ -525,7 +525,7 @@ static void calculate_medians(TStatsMapIterator begin, TStatsMapIterator end)
size_t i = 0; size_t i = 0;
for (auto& kv : durations) for (auto& kv : durations)
{ {
const auto count = kv.second.value; const auto count = kv.second.count;
i += count; i += count;
if (i < index) if (i < index)
@ -546,7 +546,7 @@ static void calculate_medians(TStatsMapIterator begin, TStatsMapIterator end)
bool i1 = false; bool i1 = false;
for (auto& kv : durations) for (auto& kv : durations)
{ {
const auto count = kv.second.value; const auto count = kv.second.count;
i += count; i += count;
if (i < index1) if (i < index1)

View File

@ -72,6 +72,7 @@
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QMenu> #include <QMenu>
#include <QMessageBox>
#include <QMoveEvent> #include <QMoveEvent>
#include <QResizeEvent> #include <QResizeEvent>
#include <QScrollBar> #include <QScrollBar>
@ -99,99 +100,87 @@
const int HIERARCHY_BUILDER_TIMER_INTERVAL = 40; const int HIERARCHY_BUILDER_TIMER_INTERVAL = 40;
const bool PLAIN_MODE_COLUMNS[COL_COLUMNS_NUMBER] = { const bool PLAIN_MODE_COLUMNS[COL_COLUMNS_NUMBER] = {
true, //COL_NAME, true // COL_NAME = 0,
true, //COL_BEGIN, , true // COL_BEGIN,
true, //COL_TIME, , true // COL_TIME,
true, //COL_SELF_TIME, , true // COL_SELF_TIME,
, true // COL_SELF_TIME_PERCENT,
false, //COL_TOTAL_TIME_PER_PARENT, , true // COL_END,
false, //COL_TOTAL_TIME_PER_FRAME, , true // COL_PERCENT_PER_FRAME,
true, //COL_TOTAL_TIME_PER_THREAD, , false // COL_TOTAL_TIME_PER_FRAME,
, false // COL_PERCENT_SUM_PER_FRAME,
true, //COL_SELF_TIME_PERCENT, , true // COL_MIN_PER_FRAME,
, true // COL_MAX_PER_FRAME,
false, //COL_PERCENT_PER_PARENT, , true // COL_AVG_PER_FRAME,
true, //COL_PERCENT_PER_FRAME, , true // COL_MEDIAN_PER_FRAME,
, true // COL_NCALLS_PER_FRAME,
false, //COL_PERCENT_SUM_PER_PARENT, , true // COL_TOTAL_TIME_PER_THREAD,
false, //COL_PERCENT_SUM_PER_FRAME, , true // COL_PERCENT_SUM_PER_THREAD,
true, //COL_PERCENT_SUM_PER_THREAD, , true // COL_MIN_PER_THREAD,
, true // COL_MAX_PER_THREAD,
true, //COL_END, , true // COL_AVG_PER_THREAD,
, true // COL_MEDIAN_PER_THREAD,
true, //COL_MIN_PER_FRAME, , true // COL_NCALLS_PER_THREAD,
true, //COL_MAX_PER_FRAME, , false // COL_PERCENT_PER_PARENT,
true, //COL_AVG_PER_FRAME, , false // COL_TOTAL_TIME_PER_PARENT,
true, //COL_NCALLS_PER_FRAME, , false // COL_PERCENT_SUM_PER_PARENT,
, false // COL_MIN_PER_PARENT,
true, //COL_MIN_PER_THREAD, , false // COL_MAX_PER_PARENT,
true, //COL_MAX_PER_THREAD, , false // COL_AVG_PER_PARENT,
true, //COL_AVG_PER_THREAD, , false // COL_MEDIAN_PER_PARENT,
true, //COL_NCALLS_PER_THREAD, , false // COL_NCALLS_PER_PARENT,
, true // COL_ACTIVE_TIME,
false, //COL_MIN_PER_PARENT, , true // COL_ACTIVE_PERCENT,
false, //COL_MAX_PER_PARENT, , true // COL_PERCENT_PER_AREA,
false, //COL_AVG_PER_PARENT, , true // COL_TOTAL_TIME_PER_AREA,
false, //COL_NCALLS_PER_PARENT, , true // COL_PERCENT_SUM_PER_AREA,
, true // COL_MIN_PER_AREA,
true, //COL_ACTIVE_TIME, , true // COL_MAX_PER_AREA,
true, //COL_ACTIVE_PERCENT, , true // COL_AVG_PER_AREA,
, true // COL_MEDIAN_PER_AREA,
true, //COL_PERCENT_PER_AREA, , true // COL_NCALLS_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,
}; };
const bool SELECTION_MODE_COLUMNS[COL_COLUMNS_NUMBER] = { const bool SELECTION_MODE_COLUMNS[COL_COLUMNS_NUMBER] = {
true, //COL_NAME, true // COL_NAME = 0,
false, //COL_BEGIN, , false // COL_BEGIN,
true, //COL_TIME, , true // COL_TIME,
true, //COL_SELF_TIME, , true // COL_SELF_TIME,
, true // COL_SELF_TIME_PERCENT,
false, //COL_TOTAL_TIME_PER_PARENT, , false // COL_END,
false, //COL_TOTAL_TIME_PER_FRAME, , false // COL_PERCENT_PER_FRAME,
true, //COL_TOTAL_TIME_PER_THREAD, , false // COL_TOTAL_TIME_PER_FRAME,
, false // COL_PERCENT_SUM_PER_FRAME,
true, //COL_SELF_TIME_PERCENT, , false // COL_MIN_PER_FRAME,
, false // COL_MAX_PER_FRAME,
false, //COL_PERCENT_PER_PARENT, , false // COL_AVG_PER_FRAME,
false, //COL_PERCENT_PER_FRAME, , false // COL_MEDIAN_PER_FRAME,
, false // COL_NCALLS_PER_FRAME,
false, //COL_PERCENT_SUM_PER_PARENT, , true // COL_TOTAL_TIME_PER_THREAD,
false, //COL_PERCENT_SUM_PER_FRAME, , true // COL_PERCENT_SUM_PER_THREAD,
true, //COL_PERCENT_SUM_PER_THREAD, , true // COL_MIN_PER_THREAD,
, true // COL_MAX_PER_THREAD,
false, //COL_END, , true // COL_AVG_PER_THREAD,
, true // COL_MEDIAN_PER_THREAD,
false, //COL_MIN_PER_FRAME, , true // COL_NCALLS_PER_THREAD,
false, //COL_MAX_PER_FRAME, , false // COL_PERCENT_PER_PARENT,
false, //COL_AVG_PER_FRAME, , false // COL_TOTAL_TIME_PER_PARENT,
false, //COL_NCALLS_PER_FRAME, , false // COL_PERCENT_SUM_PER_PARENT,
, false // COL_MIN_PER_PARENT,
true, //COL_MIN_PER_THREAD, , false // COL_MAX_PER_PARENT,
true, //COL_MAX_PER_THREAD, , false // COL_AVG_PER_PARENT,
true, //COL_AVG_PER_THREAD, , false // COL_MEDIAN_PER_PARENT,
true, //COL_NCALLS_PER_THREAD, , false // COL_NCALLS_PER_PARENT,
, true // COL_ACTIVE_TIME,
false, //COL_MIN_PER_PARENT, , true // COL_ACTIVE_PERCENT,
false, //COL_MAX_PER_PARENT, , true // COL_PERCENT_PER_AREA,
false, //COL_AVG_PER_PARENT, , true // COL_TOTAL_TIME_PER_AREA,
false, //COL_NCALLS_PER_PARENT, , true // COL_PERCENT_SUM_PER_AREA,
, true // COL_MIN_PER_AREA,
true, //COL_ACTIVE_TIME, , true // COL_MAX_PER_AREA,
true, //COL_ACTIVE_PERCENT, , true // COL_AVG_PER_AREA,
, true // COL_MEDIAN_PER_AREA,
false, //COL_PERCENT_PER_AREA, , true // COL_NCALLS_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,
}; };
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -249,16 +238,19 @@ BlocksTreeWidget::BlocksTreeWidget(QWidget* _parent)
header_item->setText(COL_MIN_PER_FRAME, "Min/frame"); header_item->setText(COL_MIN_PER_FRAME, "Min/frame");
header_item->setText(COL_MAX_PER_FRAME, "Max/frame"); header_item->setText(COL_MAX_PER_FRAME, "Max/frame");
header_item->setText(COL_AVG_PER_FRAME, "Avg/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_NCALLS_PER_FRAME, "N/frame");
header_item->setText(COL_MIN_PER_PARENT, "Min/parent"); header_item->setText(COL_MIN_PER_PARENT, "Min/parent");
header_item->setText(COL_MAX_PER_PARENT, "Max/parent"); header_item->setText(COL_MAX_PER_PARENT, "Max/parent");
header_item->setText(COL_AVG_PER_PARENT, "Avg/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_NCALLS_PER_PARENT, "N/parent");
header_item->setText(COL_MIN_PER_THREAD, "Min/thread"); header_item->setText(COL_MIN_PER_THREAD, "Min/thread");
header_item->setText(COL_MAX_PER_THREAD, "Max/thread"); header_item->setText(COL_MAX_PER_THREAD, "Max/thread");
header_item->setText(COL_AVG_PER_THREAD, "Avg/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_NCALLS_PER_THREAD, "N/thread");
header_item->setText(COL_ACTIVE_TIME, "WorkTime"); 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_MIN_PER_AREA, "Min/area");
header_item->setText(COL_MAX_PER_AREA, "Max/area"); header_item->setText(COL_MAX_PER_AREA, "Max/area");
header_item->setText(COL_AVG_PER_AREA, "Avg/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"); header_item->setText(COL_NCALLS_PER_AREA, "N/area");
auto color = QColor::fromRgb(profiler::colors::DeepOrange900); auto color = QColor::fromRgb(profiler::colors::DeepOrange900);
header_item->setForeground(COL_MIN_PER_THREAD, color); header_item->setForeground(COL_MIN_PER_THREAD, color);
header_item->setForeground(COL_MAX_PER_THREAD, color); header_item->setForeground(COL_MAX_PER_THREAD, color);
header_item->setForeground(COL_AVG_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_NCALLS_PER_THREAD, color);
header_item->setForeground(COL_PERCENT_SUM_PER_THREAD, color); header_item->setForeground(COL_PERCENT_SUM_PER_THREAD, color);
header_item->setForeground(COL_TOTAL_TIME_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_MIN_PER_FRAME, color);
header_item->setForeground(COL_MAX_PER_FRAME, color); header_item->setForeground(COL_MAX_PER_FRAME, color);
header_item->setForeground(COL_AVG_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_NCALLS_PER_FRAME, color);
header_item->setForeground(COL_PERCENT_SUM_PER_FRAME, color); header_item->setForeground(COL_PERCENT_SUM_PER_FRAME, color);
header_item->setForeground(COL_TOTAL_TIME_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_MIN_PER_PARENT, color);
header_item->setForeground(COL_MAX_PER_PARENT, color); header_item->setForeground(COL_MAX_PER_PARENT, color);
header_item->setForeground(COL_AVG_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_NCALLS_PER_PARENT, color);
header_item->setForeground(COL_PERCENT_SUM_PER_PARENT, color); header_item->setForeground(COL_PERCENT_SUM_PER_PARENT, color);
header_item->setForeground(COL_TOTAL_TIME_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_MIN_PER_AREA, color);
header_item->setForeground(COL_MAX_PER_AREA, color); header_item->setForeground(COL_MAX_PER_AREA, color);
header_item->setForeground(COL_AVG_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); header_item->setForeground(COL_NCALLS_PER_AREA, color);
setHeaderItem(header_item); setHeaderItem(header_item);
@ -502,6 +499,7 @@ void BlocksTreeWidget::onFillTimerTimeout()
ThreadedItems toplevelitems; ThreadedItems toplevelitems;
m_hierarchyBuilder.takeItems(m_items); m_hierarchyBuilder.takeItems(m_items);
m_hierarchyBuilder.takeTopLevelItems(toplevelitems); m_hierarchyBuilder.takeTopLevelItems(toplevelitems);
auto error = m_hierarchyBuilder.error();
m_hierarchyBuilder.interrupt(); m_hierarchyBuilder.interrupt();
{ {
const QSignalBlocker b(this); const QSignalBlocker b(this);
@ -550,6 +548,12 @@ void BlocksTreeWidget::onFillTimerTimeout()
connect(this, &Parent::itemDoubleClicked, this, &This::onItemDoubleClicked); connect(this, &Parent::itemDoubleClicked, this, &This::onItemDoubleClicked);
onSelectedThreadChange(EASY_GLOBALS.selected_thread); onSelectedThreadChange(EASY_GLOBALS.selected_thread);
onSelectedBlockChange(EASY_GLOBALS.selected_block); onSelectedBlockChange(EASY_GLOBALS.selected_block);
if (!error.isEmpty())
{
QMessageBox::warning(this, "Warning", error, QMessageBox::Close);
clearSilent();
}
} }
else if (m_progress != nullptr) else if (m_progress != nullptr)
{ {
@ -578,7 +582,7 @@ void BlocksTreeWidget::onIdleTimeout()
return; return;
const int column = columnAt(pos.x()); const int column = columnAt(pos.x());
if (item->hasToolTip(column)) if (!item->data(column, Qt::ToolTipRole).isNull())
return; return;
auto focusWidget = qApp->focusWidget(); auto focusWidget = qApp->focusWidget();
@ -675,9 +679,14 @@ void BlocksTreeWidget::clearSilent(bool _global)
for (int i = topLevelItemCount() - 1; i >= 0; --i) for (int i = topLevelItemCount() - 1; i >= 0; --i)
topLevelItems.push_back(takeTopLevelItem(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) 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_THREAD:
case COL_MIN_PER_PARENT: case COL_MIN_PER_PARENT:
case COL_MIN_PER_FRAME: case COL_MIN_PER_FRAME:
case COL_MIN_PER_AREA:
case COL_MAX_PER_THREAD: case COL_MAX_PER_THREAD:
case COL_MAX_PER_PARENT: case COL_MAX_PER_PARENT:
case COL_MAX_PER_FRAME: case COL_MAX_PER_FRAME:
case COL_MAX_PER_AREA:
{ {
auto& block = item->block(); auto& block = item->block();
auto i = profiler_gui::numeric_max<uint32_t>(); auto i = profiler_gui::numeric_max<uint32_t>();
@ -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_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_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_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)) 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_MIN_PER_FRAME);
ADD_COLUMN_ACTION(COL_MAX_PER_FRAME); ADD_COLUMN_ACTION(COL_MAX_PER_FRAME);
ADD_COLUMN_ACTION(COL_AVG_PER_FRAME); ADD_COLUMN_ACTION(COL_AVG_PER_FRAME);
ADD_COLUMN_ACTION(COL_MEDIAN_PER_FRAME);
ADD_COLUMN_ACTION(COL_NCALLS_PER_FRAME); ADD_COLUMN_ACTION(COL_NCALLS_PER_FRAME);
hidemenu->addSeparator(); hidemenu->addSeparator();
@ -1045,6 +1073,7 @@ void BlocksTreeWidget::contextMenuEvent(QContextMenuEvent* _event)
ADD_COLUMN_ACTION(COL_MIN_PER_THREAD); ADD_COLUMN_ACTION(COL_MIN_PER_THREAD);
ADD_COLUMN_ACTION(COL_MAX_PER_THREAD); ADD_COLUMN_ACTION(COL_MAX_PER_THREAD);
ADD_COLUMN_ACTION(COL_AVG_PER_THREAD); ADD_COLUMN_ACTION(COL_AVG_PER_THREAD);
ADD_COLUMN_ACTION(COL_MEDIAN_PER_THREAD);
ADD_COLUMN_ACTION(COL_NCALLS_PER_THREAD); ADD_COLUMN_ACTION(COL_NCALLS_PER_THREAD);
hidemenu->addSeparator(); hidemenu->addSeparator();
@ -1055,6 +1084,7 @@ void BlocksTreeWidget::contextMenuEvent(QContextMenuEvent* _event)
ADD_COLUMN_ACTION(COL_MIN_PER_PARENT); ADD_COLUMN_ACTION(COL_MIN_PER_PARENT);
ADD_COLUMN_ACTION(COL_MAX_PER_PARENT); ADD_COLUMN_ACTION(COL_MAX_PER_PARENT);
ADD_COLUMN_ACTION(COL_AVG_PER_PARENT); ADD_COLUMN_ACTION(COL_AVG_PER_PARENT);
ADD_COLUMN_ACTION(COL_MEDIAN_PER_PARENT);
ADD_COLUMN_ACTION(COL_NCALLS_PER_PARENT); ADD_COLUMN_ACTION(COL_NCALLS_PER_PARENT);
hidemenu->addSeparator(); hidemenu->addSeparator();
@ -1070,6 +1100,7 @@ void BlocksTreeWidget::contextMenuEvent(QContextMenuEvent* _event)
ADD_COLUMN_ACTION(COL_MIN_PER_AREA); ADD_COLUMN_ACTION(COL_MIN_PER_AREA);
ADD_COLUMN_ACTION(COL_MAX_PER_AREA); ADD_COLUMN_ACTION(COL_MAX_PER_AREA);
ADD_COLUMN_ACTION(COL_AVG_PER_AREA); ADD_COLUMN_ACTION(COL_AVG_PER_AREA);
ADD_COLUMN_ACTION(COL_MEDIAN_PER_AREA);
ADD_COLUMN_ACTION(COL_NCALLS_PER_AREA); ADD_COLUMN_ACTION(COL_NCALLS_PER_AREA);
#undef ADD_STATUS_ACTION #undef ADD_STATUS_ACTION
@ -1473,19 +1504,29 @@ void BlocksTreeWidget::loadSettings()
settings.beginGroup("tree_widget"); settings.beginGroup("tree_widget");
auto val = settings.value("regime"); auto val = settings.value("regime");
if (!val.isNull())
m_mode = static_cast<TreeMode>(val.toUInt());
val = settings.value("columns");
if (!val.isNull()) if (!val.isNull())
{ {
auto byteArray = val.toByteArray(); m_mode = static_cast<TreeMode>(val.toUInt());
memcpy(m_columnsHiddenStatus, byteArray.constData(), ::std::min(sizeof(m_columnsHiddenStatus), (size_t)byteArray.size()));
} }
auto state = settings.value("headerState").toByteArray(); val = settings.value("columns_version");
if (!state.isEmpty()) if (!val.isNull() && val.toInt() == COLUMNS_VERSION)
header()->restoreState(state); {
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(); settings.endGroup();
} }
@ -1495,6 +1536,7 @@ void BlocksTreeWidget::saveSettings()
QSettings settings(profiler_gui::ORGANAZATION_NAME, profiler_gui::APPLICATION_NAME); QSettings settings(profiler_gui::ORGANAZATION_NAME, profiler_gui::APPLICATION_NAME);
settings.beginGroup("tree_widget"); settings.beginGroup("tree_widget");
settings.setValue("regime", static_cast<uint8_t>(m_mode)); settings.setValue("regime", static_cast<uint8_t>(m_mode));
settings.setValue("columns_version", COLUMNS_VERSION);
settings.setValue("columns", QByteArray(m_columnsHiddenStatus, COL_COLUMNS_NUMBER)); settings.setValue("columns", QByteArray(m_columnsHiddenStatus, COL_COLUMNS_NUMBER));
settings.setValue("headerState", header()->saveState()); settings.setValue("headerState", header()->saveState());
settings.endGroup(); settings.endGroup();

View File

@ -52,6 +52,8 @@
* : limitations under the License. * : limitations under the License.
************************************************************************/ ************************************************************************/
#include <QList>
#include <QTreeWidgetItem>
#include "common_functions.h" #include "common_functions.h"
template <class T> template <class T>
@ -459,4 +461,22 @@ namespace profiler_gui {
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
void deleteTreeItem(QTreeWidgetItem* item)
{
if (item == nullptr)
{
return;
}
QList<QTreeWidgetItem*> stack;
stack.append(item);
while (!stack.isEmpty())
{
auto i = stack.takeFirst();
stack.append(i->takeChildren());
delete i;
}
}
} // end of namespace profiler_gui. } // end of namespace profiler_gui.

View File

@ -66,6 +66,8 @@
#include "common_types.h" #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. } // END of namespace profiler_gui.
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////

View File

@ -459,9 +459,14 @@ void DescriptorsTreeWidget::clearSilent(bool _global)
topLevelItems.push_back(item); 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) for (auto item : topLevelItems)
delete item; #endif
profiler_gui::deleteTreeItem(item);
}); });
} }

View File

@ -85,6 +85,7 @@ Globals::Globals()
, selected_block(::profiler_gui::numeric_max<decltype(selected_block)>()) , selected_block(::profiler_gui::numeric_max<decltype(selected_block)>())
, selected_block_id(::profiler_gui::numeric_max<decltype(selected_block_id)>()) , selected_block_id(::profiler_gui::numeric_max<decltype(selected_block_id)>())
, version(0) , version(0)
, max_rows_count(500 * 1000)
, frame_time(16700) , frame_time(16700)
, blocks_spacing(0) , blocks_spacing(0)
, blocks_size_min(2) , blocks_size_min(2)

View File

@ -213,6 +213,8 @@ namespace profiler_gui {
::profiler::block_id_t selected_block_id; ///< Current selected profiler block id ::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 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 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_spacing; ///< Minimum blocks spacing on diagram
int blocks_size_min; ///< Minimum blocks size on diagram int blocks_size_min; ///< Minimum blocks size on diagram

View File

@ -722,6 +722,22 @@ MainWindow::MainWindow() : Parent(), m_theme("default"), m_lastAddress("localhos
submenu->addAction(waction); 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<int>::max());
spinbox->setValue(static_cast<int>(EASY_GLOBALS.max_rows_count));
spinbox->setFixedWidth(px(100));
connect(spinbox, Overload<int>::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"); 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<uint32_t>(_value);
}
}
//////////////////////////////////////////////////////////////////////////
void MainWindow::onSpacingChange(int _value) void MainWindow::onSpacingChange(int _value)
{ {
EASY_GLOBALS.blocks_spacing = _value; EASY_GLOBALS.blocks_spacing = _value;
@ -1676,6 +1702,10 @@ void MainWindow::loadSettings()
if (!val.isNull()) if (!val.isNull())
EASY_GLOBALS.frame_time = val.toFloat(); 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"); val = settings.value("blocks_spacing");
if (!val.isNull()) if (!val.isNull())
EASY_GLOBALS.blocks_spacing = val.toInt(); EASY_GLOBALS.blocks_spacing = val.toInt();
@ -1853,6 +1883,7 @@ void MainWindow::saveSettingsAndGeometry()
settings.setValue("chrono_text_position", static_cast<int>(EASY_GLOBALS.chrono_text_position)); settings.setValue("chrono_text_position", static_cast<int>(EASY_GLOBALS.chrono_text_position));
settings.setValue("time_units", static_cast<int>(EASY_GLOBALS.time_units)); settings.setValue("time_units", static_cast<int>(EASY_GLOBALS.time_units));
settings.setValue("frame_time", EASY_GLOBALS.frame_time); 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_spacing", EASY_GLOBALS.blocks_spacing);
settings.setValue("blocks_size_min", EASY_GLOBALS.blocks_size_min); settings.setValue("blocks_size_min", EASY_GLOBALS.blocks_size_min);
settings.setValue("blocks_narrow_size", EASY_GLOBALS.blocks_narrow_size); settings.setValue("blocks_narrow_size", EASY_GLOBALS.blocks_narrow_size);

View File

@ -344,6 +344,7 @@ protected slots:
void onExpandAllClicked(bool); void onExpandAllClicked(bool);
void onCollapseAllClicked(bool); void onCollapseAllClicked(bool);
void onViewportInfoClicked(bool); void onViewportInfoClicked(bool);
void onMaxBlocksCountChange(int _value);
void onSpacingChange(int _value); void onSpacingChange(int _value);
void onMinSizeChange(int _value); void onMinSizeChange(int _value);
void onNarrowSizeChange(int _value); void onNarrowSizeChange(int _value);

View File

@ -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...)" # pragma message "TODO: include proper headers to be able to use pthread_setschedprio() on OSX and iOS (pthread.h is not enough...)"
#endif #endif
void setLowestThreadPriority() static void setLowestThreadPriority()
{ {
#ifdef _WIN32 #ifdef _WIN32
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST);
@ -95,13 +95,32 @@ ThreadPool& ThreadPool::instance()
ThreadPool::ThreadPool() 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 // N threads for main tasks
std::generate_n(std::back_inserter(m_threads), std::thread::hardware_concurrency(), [this] { std::generate_n(std::back_inserter(m_threads), count, [this] {
return std::thread(&ThreadPool::tasksWorker, 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 // One thread for background jobs
m_threads.emplace_back(&ThreadPool::jobsWorker, this); m_threads.emplace_back(&ThreadPool::jobsWorker, this);
} }
@ -110,6 +129,9 @@ ThreadPool::~ThreadPool()
{ {
m_interrupt.store(true, std::memory_order_release); m_interrupt.store(true, std::memory_order_release);
m_tasks.cv.notify_all(); m_tasks.cv.notify_all();
#ifdef EASY_THREADPOOL_SEPARATE_QT_THREAD
m_qtasks.cv.notify_all();
#endif
m_backgroundJobs.cv.notify_all(); m_backgroundJobs.cv.notify_all();
for (auto& thread : m_threads) for (auto& thread : m_threads)
thread.join(); thread.join();
@ -125,53 +147,69 @@ void ThreadPool::backgroundJob(std::function<void()>&& func)
void ThreadPool::enqueue(ThreadPoolTask& task) void ThreadPool::enqueue(ThreadPoolTask& task)
{ {
m_tasks.mutex.lock(); #ifdef EASY_THREADPOOL_SEPARATE_QT_THREAD
m_tasks.queue.emplace_back(task); if (task.creatingQtObjects())
m_tasks.mutex.unlock(); enqueue(task, m_qtasks);
m_tasks.cv.notify_one(); else
#endif
enqueue(task, m_tasks);
} }
void ThreadPool::dequeue(ThreadPoolTask& task) void ThreadPool::dequeue(ThreadPoolTask& task)
{ {
const std::lock_guard<std::mutex> 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<std::mutex> lock(tasks.mutex);
if (task.status() != TaskStatus::Enqueued) if (task.status() != TaskStatus::Enqueued)
return; 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) if (&it->get() == &task)
{ {
m_tasks.queue.erase(it); tasks.queue.erase(it);
break; break;
} }
} }
} }
void ThreadPool::tasksWorker() void ThreadPool::tasksWorker(Jobs<std::reference_wrapper<ThreadPoolTask> >& tasks)
{ {
while (true) while (!m_interrupt.load(std::memory_order_acquire))
{ {
std::unique_lock<std::mutex> lock(m_tasks.mutex); std::unique_lock<std::mutex> lock(tasks.mutex);
m_tasks.cv.wait(lock, [this] { return !m_tasks.queue.empty() || m_interrupt.load(std::memory_order_acquire); }); 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)) auto& task = tasks.queue.front().get();
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();
task.setStatus(TaskStatus::Processing); 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 // unlock to permit tasks execution for other worker threads and for adding new tasks
lock.unlock(); lock.unlock();
// execute task // execute task
task.execute(); task();
// lock again to check if there are new tasks in the queue // lock again to check if there are new tasks in the queue
lock.lock(); lock.lock();
@ -183,21 +221,15 @@ void ThreadPool::jobsWorker()
{ {
setLowestThreadPriority(); // Background thread has lowest priority setLowestThreadPriority(); // Background thread has lowest priority
while (true) while (!m_interrupt.load(std::memory_order_acquire))
{ {
std::unique_lock<std::mutex> lock(m_backgroundJobs.mutex); std::unique_lock<std::mutex> lock(m_backgroundJobs.mutex);
m_backgroundJobs.cv.wait(lock, [this] { m_backgroundJobs.cv.wait(lock, [this] {
return !m_backgroundJobs.queue.empty() || m_interrupt.load(std::memory_order_acquire); 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()); auto job = std::move(m_backgroundJobs.queue.front());
m_backgroundJobs.queue.pop_front(); m_backgroundJobs.queue.pop_front();

View File

@ -58,6 +58,18 @@
#include <thread> #include <thread>
#include <functional> #include <functional>
#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 class ThreadPool EASY_FINAL
{ {
friend ThreadPoolTask; friend ThreadPoolTask;
@ -70,8 +82,14 @@ class ThreadPool EASY_FINAL
std::condition_variable cv; std::condition_variable cv;
}; };
Jobs<std::reference_wrapper<ThreadPoolTask> > m_tasks; using TaskJobs = Jobs<std::reference_wrapper<ThreadPoolTask> >;
Jobs<std::function<void()> > m_backgroundJobs; using BackgroundJobs = Jobs<std::function<void()> >;
TaskJobs m_tasks;
#ifdef EASY_THREADPOOL_SEPARATE_QT_THREAD
TaskJobs m_qtasks;
#endif
BackgroundJobs m_backgroundJobs;
std::vector<std::thread> m_threads; std::vector<std::thread> m_threads;
std::atomic_bool m_interrupt; std::atomic_bool m_interrupt;
@ -89,7 +107,11 @@ private:
void enqueue(ThreadPoolTask& task); void enqueue(ThreadPoolTask& task);
void dequeue(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(); void jobsWorker();
}; // end of class ThreadPool. }; // end of class ThreadPool.

View File

@ -53,7 +53,10 @@
static std::atomic_bool s_dummy_flag {false}; 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<int8_t>(TaskStatus::Finished); m_status = static_cast<int8_t>(TaskStatus::Finished);
} }
@ -64,6 +67,11 @@ ThreadPoolTask::~ThreadPoolTask()
dequeue(); dequeue();
} }
bool ThreadPoolTask::creatingQtObjects() const
{
return m_creatingQtObjects;
}
void ThreadPoolTask::enqueue(Func&& func, std::atomic_bool& interruptFlag) void ThreadPoolTask::enqueue(Func&& func, std::atomic_bool& interruptFlag)
{ {
dequeue(); dequeue();
@ -101,7 +109,7 @@ TaskStatus ThreadPoolTask::status() const
return static_cast<TaskStatus>(m_status.load(std::memory_order_acquire)); return static_cast<TaskStatus>(m_status.load(std::memory_order_acquire));
} }
void ThreadPoolTask::execute() void ThreadPoolTask::operator()()
{ {
// execute if not cancelled // execute if not cancelled
{ {

View File

@ -93,13 +93,14 @@ private:
std::atomic_bool* m_interrupt; std::atomic_bool* m_interrupt;
std::mutex m_mutex; std::mutex m_mutex;
std::atomic<int8_t> m_status; std::atomic<int8_t> m_status;
const bool m_creatingQtObjects;
public: public:
ThreadPoolTask(const ThreadPoolTask&) = delete; ThreadPoolTask(const ThreadPoolTask&) = delete;
ThreadPoolTask(ThreadPoolTask&&) = delete; ThreadPoolTask(ThreadPoolTask&&) = delete;
ThreadPoolTask(); explicit ThreadPoolTask(bool creatingQtObjects = false);
~ThreadPoolTask(); ~ThreadPoolTask();
void enqueue(Func&& func, std::atomic_bool& interruptFlag); void enqueue(Func&& func, std::atomic_bool& interruptFlag);
@ -109,7 +110,9 @@ public:
private: private:
void execute(); bool creatingQtObjects() const;
void operator() ();
TaskStatus status() const; TaskStatus status() const;
void setStatus(TaskStatus status); void setStatus(TaskStatus status);

View File

@ -71,55 +71,48 @@
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
EASY_CONSTEXPR int BlockColorRole = Qt::UserRole + 1;
namespace { namespace {
EASY_CONSTEXPR int ColumnBit[COL_COLUMNS_NUMBER] = { EASY_CONSTEXPR bool HasToolTip[COL_COLUMNS_NUMBER] = {
-1 // COL_NAME = 0, false // COL_NAME = 0,
, true // COL_BEGIN,
, 0 // COL_BEGIN, , true // COL_TIME,
, true // COL_SELF_TIME,
, 1 // COL_TIME, , false // COL_SELF_TIME_PERCENT,
, 2 // COL_SELF_TIME, , true // COL_END,
, 3 // COL_TOTAL_TIME_PER_PARENT, , false // COL_PERCENT_PER_FRAME,
, 4 // COL_TOTAL_TIME_PER_FRAME, , true // COL_TOTAL_TIME_PER_FRAME,
, 5 // COL_TOTAL_TIME_PER_THREAD, , false // COL_PERCENT_SUM_PER_FRAME,
, true // COL_MIN_PER_FRAME,
, -1 // COL_SELF_TIME_PERCENT, , true // COL_MAX_PER_FRAME,
, -1 // COL_PERCENT_PER_PARENT, , true // COL_AVG_PER_FRAME,
, -1 // COL_PERCENT_PER_FRAME, , true // COL_MEDIAN_PER_FRAME,
, -1 // COL_PERCENT_SUM_PER_PARENT, , false // COL_NCALLS_PER_FRAME,
, -1 // COL_PERCENT_SUM_PER_FRAME, , true // COL_TOTAL_TIME_PER_THREAD,
, -1 // COL_PERCENT_SUM_PER_THREAD, , false // COL_PERCENT_SUM_PER_THREAD,
, true // COL_MIN_PER_THREAD,
, 6 // COL_END, , true // COL_MAX_PER_THREAD,
, true // COL_AVG_PER_THREAD,
, 7 // COL_MIN_PER_FRAME, , true // COL_MEDIAN_PER_THREAD,
, 8 // COL_MAX_PER_FRAME, , false // COL_NCALLS_PER_THREAD,
, 9 // COL_AVG_PER_FRAME, , false // COL_PERCENT_PER_PARENT,
, -1 // COL_NCALLS_PER_FRAME, , true // COL_TOTAL_TIME_PER_PARENT,
, false // COL_PERCENT_SUM_PER_PARENT,
, 10 // COL_MIN_PER_THREAD, , true // COL_MIN_PER_PARENT,
, 11 // COL_MAX_PER_THREAD, , true // COL_MAX_PER_PARENT,
, 12 // COL_AVG_PER_THREAD, , true // COL_AVG_PER_PARENT,
, -1 // COL_NCALLS_PER_THREAD, , true // COL_MEDIAN_PER_PARENT,
, false // COL_NCALLS_PER_PARENT,
, 13 // COL_MIN_PER_PARENT, , true // COL_ACTIVE_TIME,
, 14 // COL_MAX_PER_PARENT, , false // COL_ACTIVE_PERCENT,
, 15 // COL_AVG_PER_PARENT, , false // COL_PERCENT_PER_AREA,
, -1 // COL_NCALLS_PER_PARENT, , true // COL_TOTAL_TIME_PER_AREA,
, false // COL_PERCENT_SUM_PER_AREA,
, 16 // COL_ACTIVE_TIME, , true // COL_MIN_PER_AREA,
, -1 // COL_ACTIVE_PERCENT, , true // COL_MAX_PER_AREA,
, true // COL_AVG_PER_AREA,
, -1 // COL_PERCENT_PER_AREA, , true // COL_MEDIAN_PER_AREA,
, 17 // COL_TOTAL_TIME_PER_AREA, , false // COL_NCALLS_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,
}; };
} // end of namespace <noname>. } // end of namespace <noname>.
@ -192,19 +185,6 @@ bool TreeWidgetItem::isPartial() const
return m_partial; return m_partial;
} }
bool TreeWidgetItem::hasToolTip(int _column) const
{
const int bit = ColumnBit[_column];
return bit < 0 ? false : m_bHasToolTip.test(static_cast<size_t>(bit));
}
void TreeWidgetItem::setHasToolTip(int _column)
{
const int bit = ColumnBit[_column];
if (bit >= 0)
m_bHasToolTip.set(static_cast<size_t>(bit), true);
}
QVariant TreeWidgetItem::data(int _column, int _role) const QVariant TreeWidgetItem::data(int _column, int _role) const
{ {
if (_column == COL_NAME) if (_column == COL_NAME)
@ -233,15 +213,29 @@ QVariant TreeWidgetItem::data(int _column, int _role) const
case Qt::ToolTipRole: case Qt::ToolTipRole:
{ {
if (hasToolTip(_column)) const bool hasToolTip = HasToolTip[_column];
return QVariant::fromValue(QString("%1 ns").arg(data(_column, Qt::UserRole).toULongLong())); if (hasToolTip)
{
auto v = data(_column, Qt::UserRole);
if (!v.isNull())
return QString("%1 ns").arg(v.toULongLong());
}
break; break;
} }
case MinMaxBlockIndexRole:
{
auto v = Parent::data(_column, _role);
if (!v.isNull() || parent() == nullptr)
return v;
return QVariant::fromValue(m_block);
}
default: default:
{ {
if (_role != Qt::UserRole && _role != Qt::DisplayRole) auto v = Parent::data(_column, _role);
return QTreeWidgetItem::data(_column, _role); if (!v.isNull() || parent() == nullptr || (_role != Qt::UserRole && _role != Qt::DisplayRole))
return v;
return relevantData(_column, _role); return relevantData(_column, _role);
} }
} }
@ -269,7 +263,7 @@ QVariant TreeWidgetItem::relevantData(int _column, int _role) const
case COL_PERCENT_PER_AREA: case COL_PERCENT_PER_AREA:
case COL_PERCENT_SUM_PER_THREAD: case COL_PERCENT_SUM_PER_THREAD:
{ {
return Parent::data(_column, _role); return QVariant();
} }
default: default:
@ -278,10 +272,9 @@ QVariant TreeWidgetItem::relevantData(int _column, int _role) const
} }
} }
auto var = Parent::data(_column, _role); if (EASY_GLOBALS.display_only_relevant_stats && _role == Qt::DisplayRole)
if (!var.isNull() || (EASY_GLOBALS.display_only_relevant_stats && _role == Qt::DisplayRole))
{ {
return var; return QVariant();
} }
switch (_column) switch (_column)
@ -302,23 +295,27 @@ QVariant TreeWidgetItem::relevantData(int _column, int _role) const
case COL_AVG_PER_FRAME: case COL_AVG_PER_FRAME:
case COL_AVG_PER_THREAD: case COL_AVG_PER_THREAD:
case COL_AVG_PER_AREA: 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: 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: 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: case COL_PERCENT_SUM_PER_AREA:
{ {
return Parent::data(COL_PERCENT_PER_AREA, _role); return data(COL_PERCENT_PER_AREA, _role);
} }
default: default:
@ -327,7 +324,7 @@ QVariant TreeWidgetItem::relevantData(int _column, int _role) const
} }
} }
return var; return QVariant();
} }
QVariant TreeWidgetItem::partialForeground() const QVariant TreeWidgetItem::partialForeground() const
@ -384,24 +381,12 @@ profiler::thread_id_t TreeWidgetItem::threadId() const
return static_cast<profiler::thread_id_t>(parentItem->data(COL_NAME, Qt::UserRole).toULongLong()); return static_cast<profiler::thread_id_t>(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) 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); const profiler::timestamp_t nanosecondsTime = PROF_NANOSECONDS(_time);
setData(_column, Qt::UserRole, (quint64)nanosecondsTime); setData(_column, Qt::UserRole, (quint64)nanosecondsTime);
setHasToolTip(_column); //setHasToolTip(_column);
setText(_column, QString("%1%2").arg(_prefix).arg(profiler_gui::timeStringRealNs(_units, nanosecondsTime, 3))); 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); const profiler::timestamp_t nanosecondsTime = PROF_NANOSECONDS(_time);
setData(_column, Qt::UserRole, (quint64)nanosecondsTime); setData(_column, Qt::UserRole, (quint64)nanosecondsTime);
setHasToolTip(_column); //setHasToolTip(_column);
setText(_column, profiler_gui::timeStringRealNs(_units, nanosecondsTime, 3)); 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); const profiler::timestamp_t nanosecondsTime = PROF_NANOSECONDS(_time);
setData(_column, Qt::UserRole, (quint64)nanosecondsTime); setData(_column, Qt::UserRole, (quint64)nanosecondsTime);
setHasToolTip(_column); //setHasToolTip(_column);
setText(_column, QString::number(double(nanosecondsTime) * 1e-6, 'g', 9)); 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); const profiler::timestamp_t nanosecondsTime = PROF_NANOSECONDS(_time);
setData(_column, Qt::UserRole, (quint64)nanosecondsTime); 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)); setText(_column, QString("%1%2").arg(_prefix).arg(double(nanosecondsTime) * 1e-6, 0, 'g', 9));
} }

View File

@ -61,7 +61,6 @@
#include <QTreeWidget> #include <QTreeWidget>
#include <QStyledItemDelegate> #include <QStyledItemDelegate>
#include <easy/reader.h> #include <easy/reader.h>
#include <bitset>
#include "common_functions.h" #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 enum EasyColumnsIndexes
{ {
COL_UNKNOWN = -1, COL_UNKNOWN = -1,
@ -79,32 +82,34 @@ enum EasyColumnsIndexes
COL_TIME, COL_TIME,
COL_SELF_TIME, COL_SELF_TIME,
COL_TOTAL_TIME_PER_PARENT,
COL_TOTAL_TIME_PER_FRAME,
COL_TOTAL_TIME_PER_THREAD,
COL_SELF_TIME_PERCENT, 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_END,
COL_PERCENT_PER_FRAME,
COL_TOTAL_TIME_PER_FRAME,
COL_PERCENT_SUM_PER_FRAME,
COL_MIN_PER_FRAME, COL_MIN_PER_FRAME,
COL_MAX_PER_FRAME, COL_MAX_PER_FRAME,
COL_AVG_PER_FRAME, COL_AVG_PER_FRAME,
COL_MEDIAN_PER_FRAME,
COL_NCALLS_PER_FRAME, COL_NCALLS_PER_FRAME,
COL_TOTAL_TIME_PER_THREAD,
COL_PERCENT_SUM_PER_THREAD,
COL_MIN_PER_THREAD, COL_MIN_PER_THREAD,
COL_MAX_PER_THREAD, COL_MAX_PER_THREAD,
COL_AVG_PER_THREAD, COL_AVG_PER_THREAD,
COL_MEDIAN_PER_THREAD,
COL_NCALLS_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_MIN_PER_PARENT,
COL_MAX_PER_PARENT, COL_MAX_PER_PARENT,
COL_AVG_PER_PARENT, COL_AVG_PER_PARENT,
COL_MEDIAN_PER_PARENT,
COL_NCALLS_PER_PARENT, COL_NCALLS_PER_PARENT,
COL_ACTIVE_TIME, COL_ACTIVE_TIME,
@ -116,6 +121,7 @@ enum EasyColumnsIndexes
COL_MIN_PER_AREA, COL_MIN_PER_AREA,
COL_MAX_PER_AREA, COL_MAX_PER_AREA,
COL_AVG_PER_AREA, COL_AVG_PER_AREA,
COL_MEDIAN_PER_AREA,
COL_NCALLS_PER_AREA, COL_NCALLS_PER_AREA,
COL_COLUMNS_NUMBER COL_COLUMNS_NUMBER
@ -130,7 +136,6 @@ class TreeWidgetItem : public QTreeWidgetItem
const profiler::block_index_t m_block; const profiler::block_index_t m_block;
QRgb m_customBGColor; QRgb m_customBGColor;
std::bitset<21> m_bHasToolTip;
bool m_bMain; bool m_bMain;
bool m_partial; bool m_partial;
@ -147,14 +152,10 @@ public:
public: public:
bool isPartial() const; bool isPartial() const;
bool hasToolTip(int _column) const;
profiler::block_index_t block_index() const; profiler::block_index_t block_index() const;
profiler_gui::EasyBlock& guiBlock(); profiler_gui::EasyBlock& guiBlock();
const profiler::BlocksTree& block() const; const profiler::BlocksTree& block() const;
profiler::timestamp_t duration() const;
profiler::timestamp_t selfDuration() const;
profiler::thread_id_t threadId() const; profiler::thread_id_t threadId() const;
void setTimeSmart(int _column, profiler_gui::TimeUnits _units, const profiler::timestamp_t& _time, const QString& _prefix); void setTimeSmart(int _column, profiler_gui::TimeUnits _units, const profiler::timestamp_t& _time, const QString& _prefix);
@ -174,7 +175,7 @@ public:
private: private:
void setHasToolTip(int _column); //void setHasToolTip(int _column);
QVariant relevantData(int _column, int _role) const; QVariant relevantData(int _column, int _role) const;
QVariant partialForeground() const; QVariant partialForeground() const;

View File

@ -54,10 +54,13 @@
* : limitations under the License. * : limitations under the License.
************************************************************************/ ************************************************************************/
#include "tree_widget_loader.h" #include <map>
#include "tree_widget_item.h"
#include "globals.h" #include "globals.h"
#include "thread_pool.h" #include "thread_pool.h"
#include "tree_widget_item.h"
#include "tree_widget_loader.h"
#ifdef max #ifdef max
#undef max #undef max
@ -75,15 +78,94 @@
#define EASY_INIT_ATOMIC(v) {v} #define EASY_INIT_ATOMIC(v) {v}
#endif #endif
namespace {
struct ThreadData struct ThreadData
{ {
StatsMap stats; StatsMap stats;
IdItems iditems; IdItems iditems;
TreeWidgetItem* item = nullptr; TreeWidgetItem* item = nullptr;
}; };
using ThreadDataMap = std::unordered_map<profiler::thread_id_t, ThreadData, estd::hash<profiler::thread_id_t> >; using ThreadDataMap = std::unordered_map<profiler::thread_id_t, ThreadData, estd::hash<profiler::thread_id_t> >;
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 <noname>.
static void fillStatsColumns( static void fillStatsColumns(
TreeWidgetItem* item, TreeWidgetItem* item,
const profiler::BlockStatistics* stats, const profiler::BlockStatistics* stats,
@ -91,12 +173,19 @@ static void fillStatsColumns(
int min_column, int min_column,
int max_column, int max_column,
int avg_column, int avg_column,
int median_column,
int total_column, int total_column,
int n_calls_column int n_calls_column
) { ) {
item->setData(n_calls_column, Qt::UserRole, stats->calls_number); item->setData(n_calls_column, Qt::UserRole, stats->calls_number);
item->setText(n_calls_column, QString::number(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) if (stats->calls_number < 2)// && EASY_GLOBALS.display_only_relevant_stats)
{ {
return; return;
@ -106,21 +195,23 @@ static void fillStatsColumns(
const auto max_duration = easyBlock(stats->max_duration_block).tree.node->duration(); const auto max_duration = easyBlock(stats->max_duration_block).tree.node->duration();
const auto avg_duration = stats->average_duration(); const auto avg_duration = stats->average_duration();
const auto tot_duration = stats->total_duration; const auto tot_duration = stats->total_duration;
const auto median_duration = stats->median_duration;
item->setTimeSmart(min_column, units, min_duration); item->setTimeSmart(min_column, units, min_duration);
item->setTimeSmart(max_column, units, max_duration); item->setTimeSmart(max_column, units, max_duration);
item->setTimeSmart(avg_column, units, avg_duration); item->setTimeSmart(avg_column, units, avg_duration);
item->setTimeSmart(median_column, units, median_duration);
item->setTimeSmart(total_column, units, tot_duration); item->setTimeSmart(total_column, units, tot_duration);
if (stats->calls_number > 1 && tot_duration != 0) if (stats->calls_number > 1 && tot_duration != 0)
{ {
if (max_duration >= (tot_duration >> 1)) 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)) 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_MIN_PER_THREAD,
COL_MAX_PER_THREAD, COL_MAX_PER_THREAD,
COL_AVG_PER_THREAD, COL_AVG_PER_THREAD,
COL_MEDIAN_PER_THREAD,
COL_TOTAL_TIME_PER_THREAD, COL_TOTAL_TIME_PER_THREAD,
COL_NCALLS_PER_THREAD COL_NCALLS_PER_THREAD
); );
@ -148,6 +240,7 @@ inline void fillStatsColumnsFrame(TreeWidgetItem* item, const profiler::BlockSta
COL_MIN_PER_FRAME, COL_MIN_PER_FRAME,
COL_MAX_PER_FRAME, COL_MAX_PER_FRAME,
COL_AVG_PER_FRAME, COL_AVG_PER_FRAME,
COL_MEDIAN_PER_FRAME,
COL_TOTAL_TIME_PER_FRAME, COL_TOTAL_TIME_PER_FRAME,
COL_NCALLS_PER_FRAME COL_NCALLS_PER_FRAME
); );
@ -162,6 +255,7 @@ inline void fillStatsColumnsParent(TreeWidgetItem* item, const profiler::BlockSt
COL_MIN_PER_PARENT, COL_MIN_PER_PARENT,
COL_MAX_PER_PARENT, COL_MAX_PER_PARENT,
COL_AVG_PER_PARENT, COL_AVG_PER_PARENT,
COL_MEDIAN_PER_PARENT,
COL_TOTAL_TIME_PER_PARENT, COL_TOTAL_TIME_PER_PARENT,
COL_NCALLS_PER_PARENT COL_NCALLS_PER_PARENT
); );
@ -176,13 +270,15 @@ inline void fillStatsColumnsSelection(TreeWidgetItem* item, const profiler::Bloc
COL_MIN_PER_AREA, COL_MIN_PER_AREA,
COL_MAX_PER_AREA, COL_MAX_PER_AREA,
COL_AVG_PER_AREA, COL_AVG_PER_AREA,
COL_MEDIAN_PER_AREA,
COL_TOTAL_TIME_PER_AREA, COL_TOTAL_TIME_PER_AREA,
COL_NCALLS_PER_AREA COL_NCALLS_PER_AREA
); );
} }
TreeWidgetLoader::TreeWidgetLoader() TreeWidgetLoader::TreeWidgetLoader()
: m_bDone(EASY_INIT_ATOMIC(false)) : m_worker(true)
, m_bDone(EASY_INIT_ATOMIC(false))
, m_bInterrupt(EASY_INIT_ATOMIC(false)) , m_bInterrupt(EASY_INIT_ATOMIC(false))
, m_progress(EASY_INIT_ATOMIC(0)) , m_progress(EASY_INIT_ATOMIC(0))
, m_mode(TreeMode::Full) , 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) void TreeWidgetLoader::interrupt(bool _wait)
{ {
m_worker.dequeue(); m_worker.dequeue();
@ -248,21 +349,28 @@ void TreeWidgetLoader::interrupt(bool _wait)
{ {
if (!_wait) if (!_wait)
{ {
auto items = std::move(m_topLevelItems); auto topLevelItems = std::move(m_topLevelItems);
ThreadPool::instance().backgroundJob([=] {
#ifdef EASY_LAMBDA_MOVE_CAPTURE
ThreadPool::instance().backgroundJob([items = std::move(topLevelItems)] {
for (auto item : items) for (auto item : items)
delete item.second; #else
ThreadPool::instance().backgroundJob([=] {
for (auto item : topLevelItems)
#endif
profiler_gui::deleteTreeItem(item.second);
}); });
} }
else else
{ {
for (auto item : m_topLevelItems) for (auto item : m_topLevelItems)
delete item.second; profiler_gui::deleteTreeItem(item.second);
} }
} }
m_items.clear(); m_items.clear();
m_topLevelItems.clear(); m_topLevelItems.clear();
m_error.clear();
} }
void TreeWidgetLoader::fillTreeBlocks( void TreeWidgetLoader::fillTreeBlocks(
@ -280,6 +388,7 @@ void TreeWidgetLoader::fillTreeBlocks(
const auto decoratedNames = EASY_GLOBALS.use_decorated_thread_name; const auto decoratedNames = EASY_GLOBALS.use_decorated_thread_name;
const auto hexThreadIds = EASY_GLOBALS.hex_thread_id; const auto hexThreadIds = EASY_GLOBALS.hex_thread_id;
const auto timeUnits = EASY_GLOBALS.time_units; const auto timeUnits = EASY_GLOBALS.time_units;
const auto maxCount = EASY_GLOBALS.max_rows_count;
const auto blocks = std::ref(_blocks); const auto blocks = std::ref(_blocks);
const auto mode = m_mode; const auto mode = m_mode;
m_worker.enqueue([=] { m_worker.enqueue([=] {
@ -287,17 +396,48 @@ void TreeWidgetLoader::fillTreeBlocks(
{ {
case TreeMode::Full: 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; break;
} }
case TreeMode::Plain: case TreeMode::Plain:
{ {
setTreeInternalPlainTop(_beginTime, blocks, _left, _right, _strict, zeroBlocks, decoratedNames, hexThreadIds, timeUnits); setTreeInternalPlainTop(
_beginTime,
blocks,
_left,
_right,
_strict,
zeroBlocks,
decoratedNames,
hexThreadIds,
timeUnits
);
break; break;
} }
case TreeMode::SelectedArea: case TreeMode::SelectedArea:
{ {
setTreeInternalAggregateTop(_beginTime, blocks, _left, _right, _strict, zeroBlocks, decoratedNames, hexThreadIds, timeUnits); setTreeInternalAggregateTop(
_beginTime,
blocks,
_left,
_right,
_strict,
zeroBlocks,
decoratedNames,
hexThreadIds,
timeUnits
);
break; 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<profiler::thread_id_t, profiler::block_index_t, using BeginEndIndicesMap = std::unordered_map<profiler::thread_id_t, profiler::block_index_t,
::estd::hash<profiler::thread_id_t> >; ::estd::hash<profiler::thread_id_t> >;
@ -326,13 +458,79 @@ void TreeWidgetLoader::setTreeInternalTop(
bool _addZeroBlocks, bool _addZeroBlocks,
bool _decoratedThreadNames, bool _decoratedThreadNames,
bool _hexThreadId, bool _hexThreadId,
profiler_gui::TimeUnits _units profiler_gui::TimeUnits _units,
size_t _maxCount
) { ) {
BeginEndIndicesMap beginEndMap; BeginEndIndicesMap beginEndMap;
ThreadDataMap threadsMap; ThreadDataMap threadsMap;
auto total = static_cast<int>(_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"); const auto u_thread = profiler_gui::toUnicode("thread");
int i = 0, total = static_cast<int>(_blocks.size());
i = 0;
for (const auto& block : _blocks) for (const auto& block : _blocks)
{ {
if (interrupted()) if (interrupted())
@ -345,7 +543,7 @@ void TreeWidgetLoader::setTreeInternalTop(
if (startTime > _right || endTime < _left) if (startTime > _right || endTime < _left)
{ {
setProgress((95 * ++i) / total); setProgress(3 + (92 * ++i) / total);
continue; continue;
} }
@ -354,7 +552,7 @@ void TreeWidgetLoader::setTreeInternalTop(
const bool partial = _strict && (startTime < _left || endTime > _right); const bool partial = _strict && (startTime < _left || endTime > _right);
if (partial && duration != 0 && (startTime == _right || endTime == _left)) if (partial && duration != 0 && (startTime == _right || endTime == _left))
{ {
setProgress((95 * ++i) / total); setProgress(3 + (92 * ++i) / total);
continue; continue;
} }
@ -465,7 +663,7 @@ void TreeWidgetLoader::setTreeInternalTop(
if (partial && children_items_number == 0) if (partial && children_items_number == 0)
{ {
delete item; delete item;
setProgress((95 * ++i) / total); setProgress(3 + (92 * ++i) / total);
continue; continue;
} }
} }
@ -481,14 +679,14 @@ void TreeWidgetLoader::setTreeInternalTop(
item->setData(COL_SELF_TIME_PERCENT, Qt::UserRole, percentage); item->setData(COL_SELF_TIME_PERCENT, Qt::UserRole, percentage);
item->setText(COL_SELF_TIME_PERCENT, QString::number(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) if (gui_block.expanded)
item->setExpanded(true); item->setExpanded(true);
m_items.insert(std::make_pair(block.tree, item)); m_items.insert(std::make_pair(block.tree, item));
setProgress((95 * ++i) / total); setProgress(3 + (92 * ++i) / total);
} }
i = 0; i = 0;
@ -1088,7 +1286,7 @@ size_t TreeWidgetLoader::setTreeInternal(
const auto per_parent_stats = child.per_parent_stats; const auto per_parent_stats = child.per_parent_stats;
const auto per_frame_stats = child.per_frame_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 = duration == 0 ? 0 : profiler_gui::percent(duration, parent_duration);
auto percentage_sum = profiler_gui::percent(per_parent_stats->total_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); item->setData(COL_PERCENT_PER_PARENT, Qt::UserRole, percentage);
@ -1100,7 +1298,7 @@ size_t TreeWidgetLoader::setTreeInternal(
{ {
if (_parent != _frame) 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 = duration == 0 ? 0 : profiler_gui::percent(duration, parent_duration);
percentage_sum = profiler_gui::percent(per_frame_stats->total_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; profiler::timestamp_t total_duration = 0;
for (auto child_index : _children) for (auto child_index : _children)
@ -1212,6 +1412,45 @@ profiler::timestamp_t TreeWidgetLoader::calculateChildrenDurationRecursive(const
return total_duration; 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( size_t TreeWidgetLoader::setTreeInternalPlain(
const profiler::BlocksTreeRoot& threadRoot, const profiler::BlocksTreeRoot& threadRoot,
IdItems& iditems, IdItems& iditems,
@ -1322,7 +1561,8 @@ size_t TreeWidgetLoader::setTreeInternalPlain(
item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, percentage_per_thread); item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, percentage_per_thread);
item->setText(COL_PERCENT_SUM_PER_THREAD, QString::number(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->setData(COL_PERCENT_PER_FRAME, Qt::UserRole, percentage_sum);
item->setText(COL_PERCENT_PER_FRAME, QString::number(percentage_sum)); item->setText(COL_PERCENT_PER_FRAME, QString::number(percentage_sum));
item->setTimeSmart(COL_TIME, units, per_frame_stats->total_duration); item->setTimeSmart(COL_TIME, units, per_frame_stats->total_duration);
@ -1603,12 +1843,15 @@ void TreeWidgetLoader::updateStats(
auto stats_it = statsMap.find(id); auto stats_it = statsMap.find(id);
if (stats_it == statsMap.end()) if (stats_it == statsMap.end())
{ {
stats_it = statsMap.emplace(id, profiler::BlockStatistics(duration, index, 0)).first; stats_it = statsMap.emplace(id, loader::Stats(duration, index, 0)).first;
stats_it->second.total_children_duration = children_duration; auto& stat = stats_it->second.stats;
stat.total_children_duration = children_duration;
} }
else else
{ {
auto& stat = stats_it->second; auto& stat = stats_it->second.stats;
auto& durations = stats_it->second.durations;
++stat.calls_number; ++stat.calls_number;
stat.total_duration += duration; stat.total_duration += duration;
stat.total_children_duration += children_duration; stat.total_children_duration += children_duration;
@ -1622,18 +1865,22 @@ void TreeWidgetLoader::updateStats(
{ {
stat.min_duration_block = index; 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()) if (stats.empty())
{ {
return; return;
} }
calculate_medians(stats.begin(), stats.end());
std::deque<TreeWidgetItem*> queue; std::deque<TreeWidgetItem*> queue;
if (root->parent() != nullptr) 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()); auto stat_it = stats.find(item->block().node->id());
if (stat_it != stats.end()) 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->setData(COL_PERCENT_SUM_PER_AREA, Qt::UserRole, percent_per_selection);
item->setText(COL_PERCENT_SUM_PER_AREA, QString::number(percent_per_selection)); item->setText(COL_PERCENT_SUM_PER_AREA, QString::number(percent_per_selection));

View File

@ -68,11 +68,29 @@
class TreeWidgetItem; class TreeWidgetItem;
namespace loader {
struct Stats
{
struct Counter { uint32_t count = 0; };
profiler::BlockStatistics stats;
std::map<profiler::timestamp_t, Counter> 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<profiler::block_index_t, TreeWidgetItem*, ::estd::hash<profiler::block_index_t> >; using Items = ::std::unordered_map<profiler::block_index_t, TreeWidgetItem*, ::estd::hash<profiler::block_index_t> >;
using ThreadedItems = std::vector<std::pair<profiler::thread_id_t, TreeWidgetItem*> >; using ThreadedItems = std::vector<std::pair<profiler::thread_id_t, TreeWidgetItem*> >;
using RootsMap = std::unordered_map<profiler::thread_id_t, TreeWidgetItem*, estd::hash<profiler::thread_id_t> >; using RootsMap = std::unordered_map<profiler::thread_id_t, TreeWidgetItem*, estd::hash<profiler::thread_id_t> >;
using IdItems = std::unordered_map<profiler::block_id_t, std::pair<TreeWidgetItem*, int>, estd::hash<profiler::block_index_t> >; using IdItems = std::unordered_map<profiler::block_id_t, std::pair<TreeWidgetItem*, int>, estd::hash<profiler::block_index_t> >;
using StatsMap = std::unordered_map<profiler::block_id_t, profiler::BlockStatistics, estd::hash<profiler::block_id_t> >; using StatsMap = std::unordered_map<profiler::block_id_t, loader::Stats, estd::hash<profiler::block_id_t> >;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -90,6 +108,7 @@ class TreeWidgetLoader Q_DECL_FINAL
ThreadedItems m_topLevelItems; ///< ThreadedItems m_topLevelItems; ///<
Items m_items; ///< Items m_items; ///<
ThreadPoolTask m_worker; ///< ThreadPoolTask m_worker; ///<
QString m_error; ///<
std::atomic_bool m_bDone; ///< std::atomic_bool m_bDone; ///<
std::atomic_bool m_bInterrupt; ///< std::atomic_bool m_bInterrupt; ///<
std::atomic<int> m_progress; ///< std::atomic<int> m_progress; ///<
@ -106,6 +125,8 @@ public:
void takeTopLevelItems(ThreadedItems& _output); void takeTopLevelItems(ThreadedItems& _output);
void takeItems(Items& _output); void takeItems(Items& _output);
QString error() const;
void interrupt(bool _wait = false); void interrupt(bool _wait = false);
void fillTreeBlocks( void fillTreeBlocks(
const::profiler_gui::TreeBlocks& _blocks, const::profiler_gui::TreeBlocks& _blocks,
@ -130,7 +151,8 @@ private:
bool _addZeroBlocks, bool _addZeroBlocks,
bool _decoratedThreadNames, bool _decoratedThreadNames,
bool _hexThreadId, bool _hexThreadId,
::profiler_gui::TimeUnits _units ::profiler_gui::TimeUnits _units,
size_t _maxCount
); );
size_t setTreeInternal( size_t setTreeInternal(
@ -213,11 +235,23 @@ private:
int depth 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; 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 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. }; // END of class TreeWidgetLoader.