mirror of
https://github.com/yse/easy_profiler.git
synced 2025-01-14 00:27:55 +08:00
(profiler Reader) Added block_index field and replaced pointers SerilizedBlock* to block indexes;
(profiler GUI) Added plain vector of ProfBlocks (additional information for gui) for fast access; (ProfGraphicsView) Removed unnecessary methods; small refactoring; (ProfTreeWidget) Removed unnecessary map of tree items; (ProfGraphicsScrollbar) Added colorizing of minimap (green = low duration, red = long duration);
This commit is contained in:
parent
55cd5a5751
commit
4f0fabdfd6
@ -31,14 +31,14 @@ namespace profiler {
|
||||
|
||||
typedef uint32_t calls_number_t;
|
||||
|
||||
struct BlockStatistics
|
||||
struct BlockStatistics final
|
||||
{
|
||||
::profiler::timestamp_t total_duration; ///< Summary duration of all block calls
|
||||
::profiler::timestamp_t min_duration; ///< Cached block->duration() value. TODO: Remove this if memory consumption will be too high
|
||||
::profiler::timestamp_t max_duration; ///< Cached block->duration() value. TODO: Remove this if memory consumption will be too high
|
||||
const ::profiler::SerilizedBlock* min_duration_block; ///< Will be used in GUI to jump to the block with min duration
|
||||
const ::profiler::SerilizedBlock* max_duration_block; ///< Will be used in GUI to jump to the block with max duration
|
||||
::profiler::calls_number_t calls_number; ///< Block calls number
|
||||
::profiler::timestamp_t total_duration; ///< Summary duration of all block calls
|
||||
::profiler::timestamp_t min_duration; ///< Cached block->duration() value. TODO: Remove this if memory consumption will be too high
|
||||
::profiler::timestamp_t max_duration; ///< Cached block->duration() value. TODO: Remove this if memory consumption will be too high
|
||||
unsigned int min_duration_block; ///< Will be used in GUI to jump to the block with min duration
|
||||
unsigned int max_duration_block; ///< Will be used in GUI to jump to the block with max duration
|
||||
::profiler::calls_number_t calls_number; ///< Block calls number
|
||||
|
||||
// TODO: It is better to replace SerilizedBlock* with BlocksTree*, but this requires to store pointers in children list.
|
||||
|
||||
@ -46,18 +46,18 @@ namespace profiler {
|
||||
: total_duration(0)
|
||||
, min_duration(0)
|
||||
, max_duration(0)
|
||||
, min_duration_block(nullptr)
|
||||
, max_duration_block(nullptr)
|
||||
, min_duration_block(0)
|
||||
, max_duration_block(0)
|
||||
, calls_number(1)
|
||||
{
|
||||
}
|
||||
|
||||
BlockStatistics(::profiler::timestamp_t _duration, const ::profiler::SerilizedBlock* _block)
|
||||
BlockStatistics(::profiler::timestamp_t _duration, unsigned int _block_index)
|
||||
: total_duration(_duration)
|
||||
, min_duration(_duration)
|
||||
, max_duration(_duration)
|
||||
, min_duration_block(_block)
|
||||
, max_duration_block(_block)
|
||||
, min_duration_block(_block_index)
|
||||
, max_duration_block(_block_index)
|
||||
, calls_number(1)
|
||||
{
|
||||
}
|
||||
@ -95,19 +95,19 @@ namespace profiler {
|
||||
typedef ::std::list<BlocksTree> children_t;
|
||||
|
||||
children_t children; ///< List of children blocks. May be empty.
|
||||
::profiler::timestamp_t self_duration; ///< Self time (excluding children blocks). This is time which was not measured by children blocks.
|
||||
::profiler::SerilizedBlock* node; ///< Pointer to serilized data (type, name, begin, end etc.)
|
||||
::profiler::BlockStatistics* frame_statistics; ///< Pointer to statistics for this block within the parent (may be nullptr for top-level blocks)
|
||||
::profiler::BlockStatistics* total_statistics; ///< Pointer to statistics for this block within the bounds of all frames per current thread
|
||||
|
||||
unsigned int block_index; ///< Index of this block
|
||||
unsigned int total_children_number; ///< Number of all children including number of grandchildren (and so on)
|
||||
unsigned short depth; ///< Maximum number of sublevels (maximum children depth)
|
||||
|
||||
BlocksTree()
|
||||
: self_duration(0)
|
||||
, node(nullptr)
|
||||
: node(nullptr)
|
||||
, frame_statistics(nullptr)
|
||||
, total_statistics(nullptr)
|
||||
, block_index(0)
|
||||
, total_children_number(0)
|
||||
, depth(0)
|
||||
{
|
||||
@ -168,11 +168,11 @@ namespace profiler {
|
||||
}
|
||||
|
||||
children = ::std::move(that.children);
|
||||
self_duration = that.self_duration;
|
||||
node = that.node;
|
||||
frame_statistics = that.frame_statistics;
|
||||
total_statistics = that.total_statistics;
|
||||
|
||||
block_index = that.block_index;
|
||||
total_children_number = that.total_children_number;
|
||||
depth = that.depth;
|
||||
|
||||
@ -185,7 +185,7 @@ namespace profiler {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class BlocksTreeRoot
|
||||
class BlocksTreeRoot final
|
||||
{
|
||||
typedef BlocksTreeRoot This;
|
||||
|
||||
|
@ -53,7 +53,7 @@ const QRgb BACKGROUND_2 = 0x00ffffff;
|
||||
const QColor CHRONOMETER_COLOR = QColor(64, 64, 64, 64);
|
||||
const QRgb CHRONOMETER_TEXT_COLOR = 0xff302010;
|
||||
|
||||
const int TEST_PROGRESSION_BASE = 4;
|
||||
const unsigned int TEST_PROGRESSION_BASE = 4;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -168,7 +168,7 @@ void ProfGraphicsItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*
|
||||
}
|
||||
else
|
||||
{
|
||||
m_levelsIndexes[0] = level0.size() - 1;
|
||||
m_levelsIndexes[0] = static_cast<unsigned int>(level0.size() - 1);
|
||||
}
|
||||
|
||||
|
||||
@ -208,7 +208,7 @@ void ProfGraphicsItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*
|
||||
char state = 1;
|
||||
//bool changebrush = false;
|
||||
|
||||
for (unsigned int i = m_levelsIndexes[l], end = level.size(); i < end; ++i)
|
||||
for (unsigned int i = m_levelsIndexes[l], end = static_cast<unsigned int>(level.size()); i < end; ++i)
|
||||
{
|
||||
auto& item = level[i];
|
||||
|
||||
@ -455,7 +455,7 @@ void ProfGraphicsItem::setLevels(unsigned short _levels)
|
||||
m_levelsIndexes.resize(_levels, -1);
|
||||
}
|
||||
|
||||
void ProfGraphicsItem::reserve(unsigned short _level, size_t _items)
|
||||
void ProfGraphicsItem::reserve(unsigned short _level, unsigned int _items)
|
||||
{
|
||||
m_levels[_level].reserve(_items);
|
||||
}
|
||||
@ -467,34 +467,22 @@ const ProfGraphicsItem::Children& ProfGraphicsItem::items(unsigned short _level)
|
||||
return m_levels[_level];
|
||||
}
|
||||
|
||||
const ::profiler_gui::ProfBlockItem& ProfGraphicsItem::getItem(unsigned short _level, size_t _index) const
|
||||
const ::profiler_gui::ProfBlockItem& ProfGraphicsItem::getItem(unsigned short _level, unsigned int _index) const
|
||||
{
|
||||
return m_levels[_level][_index];
|
||||
}
|
||||
|
||||
::profiler_gui::ProfBlockItem& ProfGraphicsItem::getItem(unsigned short _level, size_t _index)
|
||||
::profiler_gui::ProfBlockItem& ProfGraphicsItem::getItem(unsigned short _level, unsigned int _index)
|
||||
{
|
||||
return m_levels[_level][_index];
|
||||
}
|
||||
|
||||
size_t ProfGraphicsItem::addItem(unsigned short _level)
|
||||
unsigned int ProfGraphicsItem::addItem(unsigned short _level)
|
||||
{
|
||||
m_levels[_level].emplace_back();
|
||||
return m_levels[_level].size() - 1;
|
||||
return static_cast<unsigned int>(m_levels[_level].size() - 1);
|
||||
}
|
||||
|
||||
size_t ProfGraphicsItem::addItem(unsigned short _level, const ::profiler_gui::ProfBlockItem& _item)
|
||||
{
|
||||
m_levels[_level].emplace_back(_item);
|
||||
return m_levels[_level].size() - 1;
|
||||
}
|
||||
|
||||
size_t ProfGraphicsItem::addItem(unsigned short _level, ::profiler_gui::ProfBlockItem&& _item)
|
||||
{
|
||||
m_levels[_level].emplace_back(::std::forward<::profiler_gui::ProfBlockItem&&>(_item));
|
||||
return m_levels[_level].size() - 1;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ProfChronometerItem::ProfChronometerItem() : QGraphicsItem(), m_left(0), m_right(0), m_font(QFont("CourierNew", 16, 2))
|
||||
@ -677,14 +665,14 @@ ProfGraphicsView::~ProfGraphicsView()
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ProfGraphicsView::fillTestChildren(ProfGraphicsItem* _item, const int _maxlevel, int _level, qreal _x, qreal _y, size_t _childrenNumber, size_t& _total_items)
|
||||
void ProfGraphicsView::fillTestChildren(ProfGraphicsItem* _item, const int _maxlevel, int _level, qreal _x, qreal _y, unsigned int _childrenNumber, unsigned int& _total_items)
|
||||
{
|
||||
size_t nchildren = _childrenNumber;
|
||||
unsigned int nchildren = _childrenNumber;
|
||||
_childrenNumber = TEST_PROGRESSION_BASE;
|
||||
|
||||
for (size_t i = 0; i < nchildren; ++i)
|
||||
for (unsigned int i = 0; i < nchildren; ++i)
|
||||
{
|
||||
size_t j = _item->addItem(_level);
|
||||
auto j = _item->addItem(_level);
|
||||
auto& b = _item->getItem(_level, j);
|
||||
b.color = ::profiler_gui::toRgb(30 + rand() % 225, 30 + rand() % 225, 30 + rand() % 225);
|
||||
b.state = 0;
|
||||
@ -712,7 +700,7 @@ void ProfGraphicsView::fillTestChildren(ProfGraphicsItem* _item, const int _maxl
|
||||
}
|
||||
}
|
||||
|
||||
void ProfGraphicsView::test(size_t _frames_number, size_t _total_items_number_estimate, int _rows)
|
||||
void ProfGraphicsView::test(unsigned int _frames_number, unsigned int _total_items_number_estimate, int _rows)
|
||||
{
|
||||
static const qreal X_BEGIN = 50;
|
||||
static const qreal Y_BEGIN = 0;
|
||||
@ -721,9 +709,9 @@ void ProfGraphicsView::test(size_t _frames_number, size_t _total_items_number_es
|
||||
|
||||
// Calculate items number for first level
|
||||
_rows = ::std::max(1, _rows);
|
||||
const size_t children_per_frame = static_cast<size_t>(0.5 + static_cast<double>(_total_items_number_estimate) / static_cast<double>(_rows * _frames_number));
|
||||
const auto children_per_frame = static_cast<unsigned int>(0.5 + static_cast<double>(_total_items_number_estimate) / static_cast<double>(_rows * _frames_number));
|
||||
const int max_depth = logn<TEST_PROGRESSION_BASE>(children_per_frame * (TEST_PROGRESSION_BASE - 1) * 0.5 + 1);
|
||||
const size_t first_level_children_count = children_per_frame * (1.0 - TEST_PROGRESSION_BASE) / (1.0 - pow(TEST_PROGRESSION_BASE, max_depth)) + 0.5;
|
||||
const auto first_level_children_count = static_cast<unsigned int>(static_cast<double>(children_per_frame) * (1.0 - TEST_PROGRESSION_BASE) / (1.0 - pow(TEST_PROGRESSION_BASE, max_depth)) + 0.5);
|
||||
|
||||
::std::vector<ProfGraphicsItem*> thread_items(_rows);
|
||||
for (int i = 0; i < _rows; ++i)
|
||||
@ -738,7 +726,7 @@ void ProfGraphicsView::test(size_t _frames_number, size_t _total_items_number_es
|
||||
}
|
||||
|
||||
// Calculate items number for each sublevel
|
||||
size_t chldrn = first_level_children_count;
|
||||
auto chldrn = first_level_children_count;
|
||||
for (int i = 1; i <= max_depth; ++i)
|
||||
{
|
||||
for (int i = 0; i < _rows; ++i)
|
||||
@ -751,7 +739,7 @@ void ProfGraphicsView::test(size_t _frames_number, size_t _total_items_number_es
|
||||
}
|
||||
|
||||
// Create required number of items
|
||||
size_t total_items = 0;
|
||||
unsigned int total_items = 0;
|
||||
qreal maxX = 0;
|
||||
const ProfGraphicsItem* longestItem = nullptr;
|
||||
for (int i = 0; i < _rows; ++i)
|
||||
@ -760,7 +748,7 @@ void ProfGraphicsView::test(size_t _frames_number, size_t _total_items_number_es
|
||||
qreal x = X_BEGIN, y = item->y();
|
||||
for (unsigned int i = 0; i < _frames_number; ++i)
|
||||
{
|
||||
size_t j = item->addItem(0);
|
||||
auto j = item->addItem(0);
|
||||
auto& b = item->getItem(0, j);
|
||||
b.color = ::profiler_gui::toRgb(30 + rand() % 225, 30 + rand() % 225, 30 + rand() % 225);
|
||||
b.state = 0;
|
||||
@ -796,7 +784,7 @@ void ProfGraphicsView::test(size_t _frames_number, size_t _total_items_number_es
|
||||
}
|
||||
}
|
||||
|
||||
printf("TOTAL ITEMS = %llu\n", total_items);
|
||||
printf("TOTAL ITEMS = %u\n", total_items);
|
||||
|
||||
// Calculate scene rect
|
||||
auto item = thread_items.back();
|
||||
@ -950,7 +938,7 @@ qreal ProfGraphicsView::setTree(ProfGraphicsItem* _item, const ::profiler::Block
|
||||
return 0;
|
||||
}
|
||||
|
||||
_item->reserve(_level, _children.size());
|
||||
_item->reserve(_level, static_cast<unsigned int>(_children.size()));
|
||||
|
||||
const auto next_level = _level + 1;
|
||||
qreal total_duration = 0, prev_end = 0, maxh = 0;
|
||||
@ -980,6 +968,11 @@ qreal ProfGraphicsView::setTree(ProfGraphicsItem* _item, const ::profiler::Block
|
||||
auto i = _item->addItem(_level);
|
||||
auto& b = _item->getItem(_level, i);
|
||||
|
||||
auto& gui_block = ::profiler_gui::EASY_GLOBALS.gui_blocks[child.block_index];
|
||||
gui_block.graphics_item = _item;
|
||||
gui_block.graphics_item_level = _level;
|
||||
gui_block.graphics_item_index = i;
|
||||
|
||||
if (next_level < _item->levels() && !child.children.empty())
|
||||
{
|
||||
b.children_begin = static_cast<unsigned int>(_item->items(next_level).size());
|
||||
|
@ -92,7 +92,7 @@ public:
|
||||
|
||||
\param _level Index of the level
|
||||
\param _items Desired number of items on this level */
|
||||
void reserve(unsigned short _level, size_t _items);
|
||||
void reserve(unsigned short _level, unsigned int _items);
|
||||
|
||||
/**\brief Returns reference to the array of items of specified level.
|
||||
|
||||
@ -103,40 +103,20 @@ public:
|
||||
|
||||
\param _level Index of the level
|
||||
\param _index Index of required item */
|
||||
const ::profiler_gui::ProfBlockItem& getItem(unsigned short _level, size_t _index) const;
|
||||
const ::profiler_gui::ProfBlockItem& getItem(unsigned short _level, unsigned int _index) const;
|
||||
|
||||
/**\brief Returns reference to the item with required index on specified level.
|
||||
|
||||
\param _level Index of the level
|
||||
\param _index Index of required item */
|
||||
::profiler_gui::ProfBlockItem& getItem(unsigned short _level, size_t _index);
|
||||
::profiler_gui::ProfBlockItem& getItem(unsigned short _level, unsigned int _index);
|
||||
|
||||
/** \brief Adds new item to required level.
|
||||
|
||||
\param _level Index of the level
|
||||
|
||||
\retval Index of the new created item */
|
||||
size_t addItem(unsigned short _level);
|
||||
|
||||
/** \brief Adds new item to required level.
|
||||
|
||||
Constructs new item using copy constructor.
|
||||
|
||||
\param _level Index of the level
|
||||
\param _item Reference to the source item to copy from
|
||||
|
||||
\retval Index of the new created item */
|
||||
size_t addItem(unsigned short _level, const ::profiler_gui::ProfBlockItem& _item);
|
||||
|
||||
/** \brief Adds new item to required level.
|
||||
|
||||
Constructs new item using move constructor.
|
||||
|
||||
\param _level Index of the level
|
||||
\param _item Reference to the source item to move from
|
||||
|
||||
\retval Index of the new created item */
|
||||
size_t addItem(unsigned short _level, ::profiler_gui::ProfBlockItem&& _item);
|
||||
unsigned int addItem(unsigned short _level);
|
||||
|
||||
/** \brief Finds top-level blocks which are intersects with required selection zone.
|
||||
|
||||
@ -251,7 +231,7 @@ public:
|
||||
void setScrollbar(ProfGraphicsScrollbar* _scrollbar);
|
||||
void clearSilent();
|
||||
|
||||
void test(size_t _frames_number, size_t _total_items_number_estimate, int _rows);
|
||||
void test(unsigned int _frames_number, unsigned int _total_items_number_estimate, int _rows);
|
||||
void setTree(const ::profiler::thread_blocks_tree_t& _blocksTree);
|
||||
|
||||
signals:
|
||||
@ -269,7 +249,7 @@ private:
|
||||
void updateScene();
|
||||
void scaleTo(qreal _scale);
|
||||
qreal setTree(ProfGraphicsItem* _item, const ::profiler::BlocksTree::children_t& _children, qreal& _height, qreal _y, unsigned short _level);
|
||||
void fillTestChildren(ProfGraphicsItem* _item, const int _maxlevel, int _level, qreal _x, qreal _y, size_t _childrenNumber, size_t& _total_items);
|
||||
void fillTestChildren(ProfGraphicsItem* _item, const int _maxlevel, int _level, qreal _x, qreal _y, unsigned int _childrenNumber, unsigned int& _total_items);
|
||||
|
||||
private slots:
|
||||
|
||||
|
@ -287,16 +287,22 @@ void ProfTreeWidget::setTreeBlocks(const ::profiler_gui::TreeBlocks& _blocks, ::
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ProfTreeWidget::clearSilent()
|
||||
void ProfTreeWidget::clearSilent(bool _global)
|
||||
{
|
||||
m_beginTime = -1;
|
||||
|
||||
setSortingEnabled(false);
|
||||
disconnect(this, &Parent::itemExpanded, this, &This::onItemExpand);
|
||||
|
||||
if (!_global)
|
||||
{
|
||||
for (auto item : m_items)
|
||||
{
|
||||
::profiler_gui::EASY_GLOBALS.gui_blocks[item->block()->block_index].tree_item = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
m_items.clear();
|
||||
m_itemblocks.clear();
|
||||
m_roots.clear();
|
||||
|
||||
const QSignalBlocker b(this);
|
||||
clear();
|
||||
@ -353,7 +359,8 @@ size_t ProfTreeWidget::setTreeInternal(const unsigned int _blocksNumber, const :
|
||||
item->setTextColor(::profiler_gui::SELECTED_THREAD_FOREGROUND);
|
||||
m_items.push_back(item);
|
||||
|
||||
const auto children_items_number = setTreeInternal(block.children, item, nullptr, m_beginTime, finishtime + 1000000000ULL, false);
|
||||
::profiler::timestamp_t children_duration = 0;
|
||||
const auto children_items_number = setTreeInternal(block.children, item, nullptr, m_beginTime, finishtime + 1000000000ULL, false, children_duration);
|
||||
|
||||
if (children_items_number > 0)
|
||||
{
|
||||
@ -366,6 +373,11 @@ size_t ProfTreeWidget::setTreeInternal(const unsigned int _blocksNumber, const :
|
||||
}
|
||||
|
||||
m_roots[threadTree.first] = item;
|
||||
|
||||
if (children_duration > 0)
|
||||
{
|
||||
item->setTimeSmart(COL_SELF_DURATION, children_duration);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -438,18 +450,13 @@ size_t ProfTreeWidget::setTreeInternal(const ::profiler_gui::TreeBlocks& _blocks
|
||||
}
|
||||
|
||||
auto item = new ProfTreeWidgetItem(block.tree, thread_item);
|
||||
duration = block.tree->node->block()->duration();
|
||||
duration = endTime - startTime;
|
||||
|
||||
item->setText(COL_NAME, block.tree->node->getBlockName());
|
||||
item->setTimeSmart(COL_DURATION, duration);
|
||||
item->setTimeSmart(COL_SELF_DURATION, block.tree->self_duration);
|
||||
item->setTimeMs(COL_BEGIN, startTime - m_beginTime);
|
||||
item->setTimeMs(COL_END, endTime - m_beginTime);
|
||||
|
||||
auto percentage = duration == 0 ? 100 : static_cast<int>(0.5 + 100. * static_cast<double>(block.tree->self_duration) / static_cast<double>(duration));
|
||||
item->setData(COL_SELF_DURATION_PERCENT, Qt::UserRole, percentage);
|
||||
item->setText(COL_SELF_DURATION_PERCENT, QString::number(percentage));
|
||||
|
||||
item->setData(COL_PERCENT_OF_PARENT, Qt::UserRole, 0);
|
||||
item->setText(COL_PERCENT_OF_PARENT, "");
|
||||
|
||||
@ -485,15 +492,27 @@ size_t ProfTreeWidget::setTreeInternal(const ::profiler_gui::TreeBlocks& _blocks
|
||||
m_items.push_back(item);
|
||||
|
||||
size_t children_items_number = 0;
|
||||
::profiler::timestamp_t children_duration = 0;
|
||||
if (!block.tree->children.empty())
|
||||
{
|
||||
children_items_number = setTreeInternal(block.tree->children, item, item, _left, _right, _strict);
|
||||
children_items_number = setTreeInternal(block.tree->children, item, item, _left, _right, _strict, children_duration);
|
||||
}
|
||||
|
||||
int percentage = 100;
|
||||
auto self_duration = duration - children_duration;
|
||||
if (children_duration > 0 && duration > 0)
|
||||
{
|
||||
percentage = static_cast<int>(0.5 + 100. * static_cast<double>(self_duration) / static_cast<double>(duration));
|
||||
}
|
||||
|
||||
item->setTimeSmart(COL_SELF_DURATION, self_duration);
|
||||
item->setData(COL_SELF_DURATION_PERCENT, Qt::UserRole, percentage);
|
||||
item->setText(COL_SELF_DURATION_PERCENT, QString::number(percentage));
|
||||
|
||||
if (children_items_number > 0 || !_strict || (startTime >= _left && endTime <= _right))
|
||||
{
|
||||
total_items += children_items_number + 1;
|
||||
m_itemblocks[block.tree->node] = item;
|
||||
::profiler_gui::EASY_GLOBALS.gui_blocks[block.tree->block_index].tree_item = item;
|
||||
|
||||
if (m_bColorRows)
|
||||
{
|
||||
@ -509,56 +528,64 @@ size_t ProfTreeWidget::setTreeInternal(const ::profiler_gui::TreeBlocks& _blocks
|
||||
|
||||
for (auto& it : threadsMap)
|
||||
{
|
||||
if (it.second->childCount() > 0)
|
||||
auto item = it.second;
|
||||
|
||||
if (item->childCount() > 0)
|
||||
{
|
||||
addTopLevelItem(it.second);
|
||||
addTopLevelItem(item);
|
||||
|
||||
if (it.first == ::profiler_gui::EASY_GLOBALS.selected_thread)
|
||||
{
|
||||
it.second->colorize(true);
|
||||
item->colorize(true);
|
||||
}
|
||||
|
||||
m_roots[it.first] = it.second;
|
||||
m_items.push_back(it.second);
|
||||
m_roots[it.first] = item;
|
||||
m_items.push_back(item);
|
||||
++total_items;
|
||||
|
||||
// Calculate clean duration (sum of all children durations)
|
||||
::profiler::timestamp_t children_duration = 0;
|
||||
auto itemBlock = item->block();
|
||||
for (const auto& child : itemBlock->children)
|
||||
{
|
||||
children_duration += child.node->block()->duration();
|
||||
}
|
||||
|
||||
item->setTimeSmart(COL_SELF_DURATION, children_duration);
|
||||
}
|
||||
else
|
||||
{
|
||||
delete it.second;
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
return total_items;
|
||||
}
|
||||
|
||||
size_t ProfTreeWidget::setTreeInternal(const ::profiler::BlocksTree::children_t& _children, ProfTreeWidgetItem* _parent, ProfTreeWidgetItem* _frame, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict)
|
||||
size_t ProfTreeWidget::setTreeInternal(const ::profiler::BlocksTree::children_t& _children, ProfTreeWidgetItem* _parent, ProfTreeWidgetItem* _frame, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict, ::profiler::timestamp_t& _duration)
|
||||
{
|
||||
size_t total_items = 0;
|
||||
for (const auto& child : _children)
|
||||
{
|
||||
const auto startTime = child.node->block()->getBegin();
|
||||
const auto endTime = child.node->block()->getEnd();
|
||||
const auto duration = endTime - startTime;
|
||||
_duration += duration;
|
||||
|
||||
if (startTime > _right || endTime < _left)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto item = new ProfTreeWidgetItem(&child, _parent);
|
||||
auto duration = child.node->block()->duration();
|
||||
|
||||
item->setText(COL_NAME, child.node->getBlockName());
|
||||
item->setTimeSmart(COL_DURATION, duration);
|
||||
item->setTimeSmart(COL_SELF_DURATION, child.self_duration);
|
||||
item->setTimeMs(COL_BEGIN, startTime - m_beginTime);
|
||||
item->setTimeMs(COL_END, endTime - m_beginTime);
|
||||
|
||||
auto percentage = duration == 0 ? 100 : static_cast<int>(0.5 + 100. * static_cast<double>(child.self_duration) / static_cast<double>(duration));
|
||||
item->setData(COL_SELF_DURATION_PERCENT, Qt::UserRole, percentage);
|
||||
item->setText(COL_SELF_DURATION_PERCENT, QString::number(percentage));
|
||||
|
||||
if (_frame != nullptr)
|
||||
{
|
||||
percentage = duration == 0 ? 0 : static_cast<int>(0.5 + 100. * static_cast<double>(duration) / static_cast<double>(_parent->duration()));
|
||||
auto percentage = duration == 0 ? 0 : static_cast<int>(0.5 + 100. * static_cast<double>(duration) / static_cast<double>(_parent->duration()));
|
||||
item->setData(COL_PERCENT_OF_PARENT, Qt::UserRole, percentage);
|
||||
item->setText(COL_PERCENT_OF_PARENT, QString::number(percentage));
|
||||
|
||||
@ -605,15 +632,27 @@ size_t ProfTreeWidget::setTreeInternal(const ::profiler::BlocksTree::children_t&
|
||||
m_items.push_back(item);
|
||||
|
||||
size_t children_items_number = 0;
|
||||
::profiler::timestamp_t children_duration = 0;
|
||||
if (!child.children.empty())
|
||||
{
|
||||
children_items_number = setTreeInternal(child.children, item, _frame ? _frame : item, _left, _right, _strict);
|
||||
children_items_number = setTreeInternal(child.children, item, _frame ? _frame : item, _left, _right, _strict, children_duration);
|
||||
}
|
||||
|
||||
int percentage = 100;
|
||||
auto self_duration = duration - children_duration;
|
||||
if (children_duration > 0 && duration > 0)
|
||||
{
|
||||
percentage = static_cast<int>(0.5 + 100. * static_cast<double>(self_duration) / static_cast<double>(duration));
|
||||
}
|
||||
|
||||
item->setTimeSmart(COL_SELF_DURATION, self_duration);
|
||||
item->setData(COL_SELF_DURATION_PERCENT, Qt::UserRole, percentage);
|
||||
item->setText(COL_SELF_DURATION_PERCENT, QString::number(percentage));
|
||||
|
||||
if (children_items_number > 0 || !_strict || (startTime >= _left && endTime <= _right))
|
||||
{
|
||||
total_items += children_items_number + 1;
|
||||
m_itemblocks[child.node] = item;
|
||||
::profiler_gui::EASY_GLOBALS.gui_blocks[child.block_index].tree_item = item;
|
||||
|
||||
if (m_bColorRows)
|
||||
{
|
||||
@ -715,21 +754,21 @@ void ProfTreeWidget::contextMenuEvent(QContextMenuEvent* _event)
|
||||
|
||||
void ProfTreeWidget::onJumpToMinItemClicked(ProfTreeWidgetItem* _item)
|
||||
{
|
||||
auto it = m_itemblocks.find(_item->block()->total_statistics->min_duration_block);
|
||||
if (it != m_itemblocks.end())
|
||||
auto item = ::profiler_gui::EASY_GLOBALS.gui_blocks[_item->block()->total_statistics->min_duration_block].tree_item;
|
||||
if (item != nullptr)
|
||||
{
|
||||
scrollToItem(it->second, QAbstractItemView::PositionAtCenter);
|
||||
setCurrentItem(it->second);
|
||||
scrollToItem(item, QAbstractItemView::PositionAtCenter);
|
||||
setCurrentItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
void ProfTreeWidget::onJumpToMaxItemClicked(ProfTreeWidgetItem* _item)
|
||||
{
|
||||
auto it = m_itemblocks.find(_item->block()->total_statistics->max_duration_block);
|
||||
if (it != m_itemblocks.end())
|
||||
auto item = ::profiler_gui::EASY_GLOBALS.gui_blocks[_item->block()->total_statistics->max_duration_block].tree_item;
|
||||
if (item != nullptr)
|
||||
{
|
||||
scrollToItem(it->second, QAbstractItemView::PositionAtCenter);
|
||||
setCurrentItem(it->second);
|
||||
scrollToItem(item, QAbstractItemView::PositionAtCenter);
|
||||
setCurrentItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,11 +110,9 @@ class ProfTreeWidget : public QTreeWidget
|
||||
protected:
|
||||
|
||||
typedef ::std::vector<ProfTreeWidgetItem*> Items;
|
||||
typedef ::std::unordered_map<const ::profiler::SerilizedBlock*, ProfTreeWidgetItem*, ::profiler_gui::do_no_hash<const ::profiler::SerilizedBlock*>::hasher_t> BlockItemMap;
|
||||
typedef ::std::unordered_map<::profiler::thread_id_t, ProfTreeWidgetItem*, ::profiler_gui::do_no_hash<::profiler::thread_id_t>::hasher_t> RootsMap;
|
||||
|
||||
Items m_items;
|
||||
BlockItemMap m_itemblocks;
|
||||
RootsMap m_roots;
|
||||
::profiler::timestamp_t m_beginTime;
|
||||
bool m_bColorRows;
|
||||
@ -125,7 +123,7 @@ public:
|
||||
ProfTreeWidget(const unsigned int _blocksNumber, const ::profiler::thread_blocks_tree_t& _blocksTree, QWidget* _parent = nullptr);
|
||||
virtual ~ProfTreeWidget();
|
||||
|
||||
void clearSilent();
|
||||
void clearSilent(bool _global = false);
|
||||
|
||||
public slots:
|
||||
|
||||
@ -139,7 +137,7 @@ protected:
|
||||
|
||||
size_t setTreeInternal(const ::profiler_gui::TreeBlocks& _blocks, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict);
|
||||
|
||||
size_t setTreeInternal(const ::profiler::BlocksTree::children_t& _children, ProfTreeWidgetItem* _parent, ProfTreeWidgetItem* _frame, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict);
|
||||
size_t setTreeInternal(const ::profiler::BlocksTree::children_t& _children, ProfTreeWidgetItem* _parent, ProfTreeWidgetItem* _frame, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict, ::profiler::timestamp_t& _duration);
|
||||
|
||||
void contextMenuEvent(QContextMenuEvent* _event) override;
|
||||
|
||||
|
@ -101,7 +101,7 @@ inline QRgb toRgb(unsigned int _red, unsigned int _green, unsigned int _blue)
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct ProfBlockItem
|
||||
struct ProfBlockItem final
|
||||
{
|
||||
const ::profiler::BlocksTree* block; ///< Pointer to profiler block
|
||||
qreal x; ///< x coordinate of the item (this is made qreal=double to avoid mistakes on very wide scene)
|
||||
@ -134,24 +134,24 @@ typedef ::std::vector<ProfBlockItem> ProfItems;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct ProfBlock
|
||||
struct ProfSelectedBlock final
|
||||
{
|
||||
const ::profiler::BlocksTreeRoot* root;
|
||||
const ::profiler::BlocksTree* tree;
|
||||
|
||||
ProfBlock() : root(nullptr), tree(nullptr)
|
||||
ProfSelectedBlock() : root(nullptr), tree(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ProfBlock(const ::profiler::BlocksTreeRoot* _root, const ::profiler::BlocksTree* _tree)
|
||||
ProfSelectedBlock(const ::profiler::BlocksTreeRoot* _root, const ::profiler::BlocksTree* _tree)
|
||||
: root(_root)
|
||||
, tree(_tree)
|
||||
{
|
||||
}
|
||||
|
||||
}; // END of struct ProfBlock.
|
||||
}; // END of struct ProfSelectedBlock.
|
||||
|
||||
typedef ::std::vector<ProfBlock> TreeBlocks;
|
||||
typedef ::std::vector<ProfSelectedBlock> TreeBlocks;
|
||||
|
||||
} // END of namespace profiler_gui.
|
||||
|
||||
|
@ -38,6 +38,7 @@ namespace profiler_gui {
|
||||
|
||||
ProfGlobals::ProfGlobals()
|
||||
: selected_thread(0)
|
||||
, selected_block(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -25,9 +25,16 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ProfGraphicsItem;
|
||||
class ProfTreeWidgetItem;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace profiler_gui {
|
||||
|
||||
class ProfGlobalSignals : public QObject
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ProfGlobalSignals final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@ -39,15 +46,32 @@ namespace profiler_gui {
|
||||
signals:
|
||||
|
||||
void selectedThreadChanged(::profiler::thread_id_t _id);
|
||||
void selectedBlockChanged(unsigned int _block_index);
|
||||
};
|
||||
|
||||
struct ProfGlobals
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct ProfBlock final
|
||||
{
|
||||
ProfGraphicsItem* graphics_item;
|
||||
ProfTreeWidgetItem* tree_item;
|
||||
unsigned short graphics_item_level;
|
||||
unsigned int graphics_item_index;
|
||||
};
|
||||
|
||||
typedef ::std::vector<ProfBlock> ProfBlocks;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct ProfGlobals final
|
||||
{
|
||||
static ProfGlobals& instance();
|
||||
|
||||
ProfGlobalSignals events;
|
||||
::profiler::thread_blocks_tree_t profiler_blocks;
|
||||
::profiler::thread_id_t selected_thread;
|
||||
ProfBlocks gui_blocks;
|
||||
::profiler::thread_id_t selected_thread;
|
||||
unsigned int selected_block;
|
||||
|
||||
private:
|
||||
|
||||
@ -56,6 +80,8 @@ namespace profiler_gui {
|
||||
|
||||
static ProfGlobals& EASY_GLOBALS = ProfGlobals::instance();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} // END of namespace profiler_gui.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -104,7 +104,7 @@ void ProfGraphicsSliderItem::setColor(QRgb _color)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ProfMinimapItem::ProfMinimapItem() : Parent(), m_pSource(nullptr), m_maxDuration(0), m_threadId(0)
|
||||
ProfMinimapItem::ProfMinimapItem() : Parent(), m_pSource(nullptr), m_maxDuration(0), m_minDuration(0), m_threadId(0)
|
||||
{
|
||||
|
||||
}
|
||||
@ -128,32 +128,37 @@ void ProfMinimapItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*
|
||||
|
||||
const auto currentScale = static_cast<const ProfGraphicsScrollbar*>(scene()->parent())->getWindowScale();
|
||||
const auto bottom = m_boundingRect.bottom();
|
||||
const auto coeff = (m_boundingRect.height() - 5) / m_maxDuration;
|
||||
const auto coeff = m_boundingRect.height() / (m_maxDuration - m_minDuration);
|
||||
const auto heightRevert = 1.0 / m_boundingRect.height();
|
||||
|
||||
QRectF rect;
|
||||
QBrush brush(Qt::SolidPattern);
|
||||
QRgb previousColor = 0;
|
||||
|
||||
brush.setColor(QColor::fromRgba(0x80808080));
|
||||
//brush.setColor(QColor::fromRgba(0x80808080));
|
||||
|
||||
_painter->save();
|
||||
_painter->setPen(Qt::NoPen);
|
||||
_painter->setBrush(brush);
|
||||
//_painter->setBrush(brush);
|
||||
_painter->setTransform(QTransform::fromScale(1.0 / currentScale, 1), true);
|
||||
|
||||
auto& items = *m_pSource;
|
||||
for (const auto& item : items)
|
||||
{
|
||||
//if (previousColor != item.color)
|
||||
//{
|
||||
// // Set background color brush for rectangle
|
||||
// previousColor = item.color;
|
||||
// brush.setColor(QColor::fromRgba(0x40000000 | item.color));
|
||||
// _painter->setBrush(brush);
|
||||
//}
|
||||
|
||||
// Draw rectangle
|
||||
auto h = 5 + item.width() * coeff;
|
||||
|
||||
const auto h = ::std::max((item.width() - m_minDuration) * coeff, 5.0);
|
||||
const auto col = h * heightRevert;
|
||||
const auto color = ::profiler_gui::toRgb(col * 255, (1.0 - col) * 255, 0); // item.color;
|
||||
|
||||
if (previousColor != color)
|
||||
{
|
||||
// Set background color brush for rectangle
|
||||
previousColor = color;
|
||||
brush.setColor(QColor::fromRgba(0x80000000 | color));
|
||||
_painter->setBrush(brush);
|
||||
}
|
||||
|
||||
rect.setRect(item.left() * currentScale, bottom - h, ::std::max(item.width() * currentScale, 1.0), h);
|
||||
_painter->drawRect(rect);
|
||||
}
|
||||
@ -184,12 +189,20 @@ void ProfMinimapItem::setSource(::profiler::thread_id_t _thread_id, const ::prof
|
||||
}
|
||||
|
||||
m_maxDuration = 0;
|
||||
m_minDuration = 1e30;
|
||||
for (const auto& item : *m_pSource)
|
||||
{
|
||||
if (item.width() > m_maxDuration)
|
||||
auto w = item.width();
|
||||
|
||||
if (w > m_maxDuration)
|
||||
{
|
||||
m_maxDuration = item.width();
|
||||
}
|
||||
|
||||
if (w < m_minDuration)
|
||||
{
|
||||
m_minDuration = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,7 @@ class ProfMinimapItem : public QGraphicsItem
|
||||
|
||||
QRectF m_boundingRect;
|
||||
qreal m_maxDuration;
|
||||
qreal m_minDuration;
|
||||
const ::profiler_gui::ProfItems* m_pSource;
|
||||
::profiler::thread_id_t m_threadId;
|
||||
|
||||
|
@ -113,8 +113,14 @@ void ProfMainWindow::loadFile(const std::string& stdfilename)
|
||||
|
||||
if (nblocks != 0)
|
||||
{
|
||||
static_cast<ProfTreeWidget*>(m_treeWidget->widget())->clearSilent(true);
|
||||
|
||||
m_lastFile = stdfilename;
|
||||
::profiler_gui::EASY_GLOBALS.selected_thread = 0;
|
||||
::profiler_gui::EASY_GLOBALS.profiler_blocks.swap(prof_blocks);
|
||||
::profiler_gui::EASY_GLOBALS.gui_blocks.resize(nblocks);
|
||||
memset(::profiler_gui::EASY_GLOBALS.gui_blocks.data(), 0, sizeof(::profiler_gui::ProfBlock) * nblocks);
|
||||
|
||||
static_cast<ProfGraphicsViewWidget*>(m_graphicsView->widget())->view()->setTree(::profiler_gui::EASY_GLOBALS.profiler_blocks);
|
||||
}
|
||||
}
|
||||
@ -133,7 +139,13 @@ void ProfMainWindow::onReloadFileClicked(bool)
|
||||
|
||||
if (nblocks != 0)
|
||||
{
|
||||
static_cast<ProfTreeWidget*>(m_treeWidget->widget())->clearSilent(true);
|
||||
|
||||
::profiler_gui::EASY_GLOBALS.selected_thread = 0;
|
||||
::profiler_gui::EASY_GLOBALS.profiler_blocks.swap(prof_blocks);
|
||||
::profiler_gui::EASY_GLOBALS.gui_blocks.resize(nblocks);
|
||||
memset(::profiler_gui::EASY_GLOBALS.gui_blocks.data(), 0, sizeof(::profiler_gui::ProfBlock) * nblocks);
|
||||
|
||||
static_cast<ProfGraphicsViewWidget*>(m_graphicsView->widget())->view()->setTree(::profiler_gui::EASY_GLOBALS.profiler_blocks);
|
||||
}
|
||||
}
|
||||
@ -149,11 +161,14 @@ void ProfMainWindow::onExitClicked(bool)
|
||||
|
||||
void ProfMainWindow::onTestViewportClicked(bool)
|
||||
{
|
||||
static_cast<ProfTreeWidget*>(m_treeWidget->widget())->clearSilent();
|
||||
static_cast<ProfTreeWidget*>(m_treeWidget->widget())->clearSilent(true);
|
||||
|
||||
auto view = static_cast<ProfGraphicsViewWidget*>(m_graphicsView->widget())->view();
|
||||
view->clearSilent();
|
||||
|
||||
::profiler_gui::EASY_GLOBALS.gui_blocks.clear();
|
||||
::profiler_gui::EASY_GLOBALS.profiler_blocks.clear();
|
||||
::profiler_gui::EASY_GLOBALS.selected_thread = 0;
|
||||
|
||||
view->test(18000, 40000000, 2);
|
||||
//view->test(3, 300, 1);
|
||||
|
@ -162,10 +162,10 @@ typedef ::std::unordered_map<::std::string, ::profiler::BlockStatistics*> StatsM
|
||||
automatically receive statistics update.
|
||||
|
||||
*/
|
||||
void update_statistics(StatsMap& _stats_map, ::profiler::SerilizedBlock* _current, ::profiler::BlockStatistics*& _stats)
|
||||
void update_statistics(StatsMap& _stats_map, const ::profiler::BlocksTree& _current, ::profiler::BlockStatistics*& _stats)
|
||||
{
|
||||
auto duration = _current->block()->duration();
|
||||
StatsMap::key_type key(_current->getBlockName());
|
||||
auto duration = _current.node->block()->duration();
|
||||
StatsMap::key_type key(_current.node->getBlockName());
|
||||
auto it = _stats_map.find(key);
|
||||
if (it != _stats_map.end())
|
||||
{
|
||||
@ -180,7 +180,7 @@ void update_statistics(StatsMap& _stats_map, ::profiler::SerilizedBlock* _curren
|
||||
if (duration > _stats->max_duration)
|
||||
{
|
||||
// update max duration
|
||||
_stats->max_duration_block = _current;
|
||||
_stats->max_duration_block = _current.block_index;
|
||||
_stats->max_duration = duration;
|
||||
}
|
||||
|
||||
@ -188,7 +188,7 @@ void update_statistics(StatsMap& _stats_map, ::profiler::SerilizedBlock* _curren
|
||||
if (duration < _stats->min_duration)
|
||||
{
|
||||
// update min duraton
|
||||
_stats->min_duration_block = _current;
|
||||
_stats->min_duration_block = _current.block_index;
|
||||
_stats->min_duration = duration;
|
||||
}
|
||||
|
||||
@ -198,7 +198,7 @@ void update_statistics(StatsMap& _stats_map, ::profiler::SerilizedBlock* _curren
|
||||
{
|
||||
// This is first time the block appear in the file.
|
||||
// Create new statistics.
|
||||
_stats = new ::profiler::BlockStatistics(duration, _current);
|
||||
_stats = new ::profiler::BlockStatistics(duration, _current.block_index);
|
||||
_stats_map.insert(::std::make_pair(key, _stats));
|
||||
}
|
||||
}
|
||||
@ -238,8 +238,7 @@ extern "C"{
|
||||
|
||||
::profiler::BlocksTree tree;
|
||||
tree.node = new ::profiler::SerilizedBlock(sz, data);
|
||||
tree.self_duration = tree.node->block()->duration();
|
||||
++blocks_counter;
|
||||
tree.block_index = ++blocks_counter;
|
||||
|
||||
if (::profiler::BLOCK_TYPE_THREAD_SIGN == baseData->getType())
|
||||
{
|
||||
@ -280,7 +279,7 @@ extern "C"{
|
||||
|
||||
for (auto& child : tree.children)
|
||||
{
|
||||
update_statistics(frame_statistics, child.node, child.frame_statistics);
|
||||
update_statistics(frame_statistics, child, child.frame_statistics);
|
||||
|
||||
children_duration += child.node->block()->duration();
|
||||
tree.total_children_number += child.total_children_number;
|
||||
@ -299,7 +298,6 @@ extern "C"{
|
||||
}
|
||||
}
|
||||
|
||||
tree.self_duration -= children_duration;
|
||||
tree.total_children_number += static_cast<unsigned int>(tree.children.size());
|
||||
++tree.depth;
|
||||
}
|
||||
@ -316,7 +314,7 @@ extern "C"{
|
||||
{
|
||||
PROFILER_BEGIN_BLOCK("Gather statistics")
|
||||
auto& current = root.tree.children.back();
|
||||
update_statistics(overall_statistics, current.node, current.total_statistics);
|
||||
update_statistics(overall_statistics, current, current.total_statistics);
|
||||
}
|
||||
|
||||
}
|
||||
@ -333,7 +331,7 @@ extern "C"{
|
||||
for (auto& frame : root.tree.children)
|
||||
{
|
||||
root.tree.total_children_number += frame.total_children_number;
|
||||
update_statistics(frame_statistics, frame.node, frame.frame_statistics);
|
||||
update_statistics(frame_statistics, frame, frame.frame_statistics);
|
||||
if (root.tree.depth < frame.depth)
|
||||
root.tree.depth = frame.depth;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user