0
0
mirror of https://github.com/yse/easy_profiler.git synced 2024-12-25 23:40: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)
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)

View File

@ -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

View File

@ -136,7 +136,7 @@ using async_future = std::future<async_result_t>;
template <class T>
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)

View File

@ -72,6 +72,7 @@
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QMessageBox>
#include <QMoveEvent>
#include <QResizeEvent>
#include <QScrollBar>
@ -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<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_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
@ -1474,18 +1505,28 @@ void BlocksTreeWidget::loadSettings()
auto val = settings.value("regime");
if (!val.isNull())
{
m_mode = static_cast<TreeMode>(val.toUInt());
}
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()));
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<uint8_t>(m_mode));
settings.setValue("columns_version", COLUMNS_VERSION);
settings.setValue("columns", QByteArray(m_columnsHiddenStatus, COL_COLUMNS_NUMBER));
settings.setValue("headerState", header()->saveState());
settings.endGroup();

View File

@ -52,6 +52,8 @@
* : limitations under the License.
************************************************************************/
#include <QList>
#include <QTreeWidgetItem>
#include "common_functions.h"
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.

View File

@ -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.
//////////////////////////////////////////////////////////////////////////

View File

@ -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);
});
}

View File

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

View File

@ -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<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");
@ -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)
{
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<int>(EASY_GLOBALS.chrono_text_position));
settings.setValue("time_units", static_cast<int>(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);

View File

@ -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);

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...)"
#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<void()>&& 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<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)
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<std::reference_wrapper<ThreadPoolTask> >& tasks)
{
while (true)
while (!m_interrupt.load(std::memory_order_acquire))
{
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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();

View File

@ -58,6 +58,18 @@
#include <thread>
#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
{
friend ThreadPoolTask;
@ -70,8 +82,14 @@ class ThreadPool EASY_FINAL
std::condition_variable cv;
};
Jobs<std::reference_wrapper<ThreadPoolTask> > m_tasks;
Jobs<std::function<void()> > m_backgroundJobs;
using TaskJobs = Jobs<std::reference_wrapper<ThreadPoolTask> >;
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::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.

View File

@ -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<int8_t>(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<TaskStatus>(m_status.load(std::memory_order_acquire));
}
void ThreadPoolTask::execute()
void ThreadPoolTask::operator()()
{
// execute if not cancelled
{

View File

@ -93,13 +93,14 @@ private:
std::atomic_bool* m_interrupt;
std::mutex m_mutex;
std::atomic<int8_t> 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);

View File

@ -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 <noname>.
@ -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<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
{
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<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)
{
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));
}

View File

@ -61,7 +61,6 @@
#include <QTreeWidget>
#include <QStyledItemDelegate>
#include <easy/reader.h>
#include <bitset>
#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;

View File

@ -54,10 +54,13 @@
* : limitations under the License.
************************************************************************/
#include "tree_widget_loader.h"
#include "tree_widget_item.h"
#include <map>
#include "globals.h"
#include "thread_pool.h"
#include "tree_widget_item.h"
#include "tree_widget_loader.h"
#ifdef max
#undef max
@ -75,6 +78,8 @@
#define EASY_INIT_ATOMIC(v) {v}
#endif
namespace {
struct ThreadData
{
StatsMap stats;
@ -84,6 +89,83 @@ struct ThreadData
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(
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<profiler::thread_id_t, profiler::block_index_t,
::estd::hash<profiler::thread_id_t> >;
@ -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<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");
int i = 0, total = static_cast<int>(_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<TreeWidgetItem*> 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));

View File

@ -68,11 +68,29 @@
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 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 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; ///<
Items m_items; ///<
ThreadPoolTask m_worker; ///<
QString m_error; ///<
std::atomic_bool m_bDone; ///<
std::atomic_bool m_bInterrupt; ///<
std::atomic<int> 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.