0
0
mirror of https://github.com/yse/easy_profiler.git synced 2024-12-27 08:41:02 +08:00

#31 [Gui] Complexity charts for arbitrary value viewer

This commit is contained in:
Victor Zarubkin 2018-02-09 21:06:18 +03:00
parent b50a5bcbb3
commit 9860a565d3
3 changed files with 712 additions and 147 deletions

View File

@ -53,6 +53,7 @@
************************************************************************/ ************************************************************************/
#include <QPainter> #include <QPainter>
#include <QPainterPath>
#include <QGraphicsScene> #include <QGraphicsScene>
#include <QColor> #include <QColor>
#include <QHeaderView> #include <QHeaderView>
@ -63,6 +64,7 @@
#include <QSettings> #include <QSettings>
#include <QToolBar> #include <QToolBar>
#include <QAction> #include <QAction>
#include <QActionGroup>
#include <list> #include <list>
#include <cmath> #include <cmath>
#include "arbitrary_value_inspector.h" #include "arbitrary_value_inspector.h"
@ -74,6 +76,39 @@
EASY_CONSTEXPR int ChartBound = 2; ///< Top and bottom bounds for chart EASY_CONSTEXPR int ChartBound = 2; ///< Top and bottom bounds for chart
EASY_CONSTEXPR int ChartBounds = ChartBound << 1; EASY_CONSTEXPR int ChartBounds = ChartBound << 1;
template <size_t window_size>
std::vector<QPointF> gauss(const std::vector<QPointF>& _points)
{
if (_points.size() < 3)
return _points;
std::vector<QPointF> out;
out.reserve(_points.size());
for (size_t i = 0, size = _points.size(); i < size; ++i)
{
const auto next = i + 1;
if (next < window_size)
{
qreal sum = 0;
for (size_t j = 0; j <= i; ++j)
sum += _points[j].y();
sum /= i + 1;
out.emplace_back(_points[i].x(), sum);
}
else
{
qreal sum = 0;
for (size_t j = next - window_size; j <= i; ++j)
sum += _points[j].y();
sum /= window_size;
out.emplace_back(_points[i].x(), sum);
}
}
return out;
}
void getChartPoints(const ArbitraryValuesCollection& _collection, Points& _points, qreal& _minValue, qreal& _maxValue) void getChartPoints(const ArbitraryValuesCollection& _collection, Points& _points, qreal& _minValue, qreal& _maxValue)
{ {
_minValue = 1e300; _minValue = 1e300;
@ -144,8 +179,11 @@ void getChartPoints(const ArbitraryValuesCollection& _collection, Points& _point
ArbitraryValuesCollection::ArbitraryValuesCollection() ArbitraryValuesCollection::ArbitraryValuesCollection()
: m_beginTime(0) : m_beginTime(0)
, m_minDuration(profiler_gui::numeric_max<decltype(m_minDuration)>())
, m_maxDuration(0)
, m_minValue(1e300) , m_minValue(1e300)
, m_maxValue(-1e300) , m_maxValue(-1e300)
, m_chartType(ChartType::Regular)
, m_jobType(0) , m_jobType(0)
{ {
m_status = ATOMIC_VAR_INIT(Idle); m_status = ATOMIC_VAR_INIT(Idle);
@ -157,11 +195,21 @@ ArbitraryValuesCollection::~ArbitraryValuesCollection()
} }
ChartType ArbitraryValuesCollection::chartType() const
{
return m_chartType;
}
const ArbitraryValuesMap& ArbitraryValuesCollection::valuesMap() const const ArbitraryValuesMap& ArbitraryValuesCollection::valuesMap() const
{ {
return m_values; return m_values;
} }
const ComplexityValuesMap& ArbitraryValuesCollection::complexityMap() const
{
return m_complexityMap;
}
const Points& ArbitraryValuesCollection::points() const const Points& ArbitraryValuesCollection::points() const
{ {
return m_points; return m_points;
@ -180,6 +228,16 @@ size_t ArbitraryValuesCollection::size() const
return totalSize; return totalSize;
} }
profiler::timestamp_t ArbitraryValuesCollection::minDuration() const
{
return m_minDuration;
}
profiler::timestamp_t ArbitraryValuesCollection::maxDuration() const
{
return m_maxDuration;
}
qreal ArbitraryValuesCollection::minValue() const qreal ArbitraryValuesCollection::minValue() const
{ {
return m_minValue; return m_minValue;
@ -190,23 +248,30 @@ qreal ArbitraryValuesCollection::maxValue() const
return m_maxValue; return m_maxValue;
} }
void ArbitraryValuesCollection::collectValues(profiler::thread_id_t _threadId, profiler::vin_t _valueId, const char* _valueName void ArbitraryValuesCollection::collectValues(ChartType _chartType, profiler::thread_id_t _threadId, profiler::vin_t _valueId
, profiler::block_id_t _parentBlockId, bool _directParent) , const char* _valueName, profiler::block_id_t _parentBlockId, bool _directParent)
{ {
interrupt(); interrupt();
setStatus(InProgress); setStatus(InProgress);
m_points.clear(); m_points.clear();
m_chartType = _chartType;
m_jobType = ValuesJob; m_jobType = ValuesJob;
if (m_chartType == ChartType::Complexity && profiler_gui::is_max(_parentBlockId))
{
setStatus(Ready);
return;
}
if (_valueId == 0) if (_valueId == 0)
m_worker.enqueue([=] { collectByName(_threadId, _valueName, _parentBlockId, _directParent); }, m_bInterrupt); m_worker.enqueue([=] { collectByName(_threadId, _valueName, _parentBlockId, _directParent); }, m_bInterrupt);
else else
m_worker.enqueue([=] { collectById(_threadId, _valueId, _parentBlockId, _directParent); }, m_bInterrupt); m_worker.enqueue([=] { collectById(_threadId, _valueId, _parentBlockId, _directParent); }, m_bInterrupt);
} }
void ArbitraryValuesCollection::collectValues(profiler::thread_id_t _threadId, profiler::vin_t _valueId, const char* _valueName void ArbitraryValuesCollection::collectValuesAndPoints(ChartType _chartType, profiler::thread_id_t _threadId, profiler::vin_t _valueId
, profiler::timestamp_t _beginTime, profiler::block_id_t _parentBlockId, bool _directParent) , const char* _valueName, profiler::timestamp_t _beginTime, profiler::block_id_t _parentBlockId, bool _directParent)
{ {
interrupt(); interrupt();
@ -215,8 +280,15 @@ void ArbitraryValuesCollection::collectValues(profiler::thread_id_t _threadId, p
m_beginTime = _beginTime; m_beginTime = _beginTime;
m_minValue = 1e300; m_minValue = 1e300;
m_maxValue = -1e300; m_maxValue = -1e300;
m_chartType = _chartType;
m_jobType = ValuesJob | PointsJob; m_jobType = ValuesJob | PointsJob;
if (m_chartType == ChartType::Complexity && profiler_gui::is_max(_parentBlockId))
{
setStatus(Ready);
return;
}
if (_valueId == 0) if (_valueId == 0)
m_worker.enqueue([=] { collectByName(_threadId, _valueName, _parentBlockId, _directParent); }, m_bInterrupt); m_worker.enqueue([=] { collectByName(_threadId, _valueName, _parentBlockId, _directParent); }, m_bInterrupt);
else else
@ -252,6 +324,9 @@ void ArbitraryValuesCollection::interrupt()
setStatus(Idle); setStatus(Idle);
m_jobType = None; m_jobType = None;
m_values.clear(); m_values.clear();
m_complexityMap.clear();
profiler_gui::set_max(m_minDuration);
m_maxDuration = 0;
} }
void ArbitraryValuesCollection::setStatus(JobStatus _status) void ArbitraryValuesCollection::setStatus(JobStatus _status)
@ -262,10 +337,14 @@ void ArbitraryValuesCollection::setStatus(JobStatus _status)
void ArbitraryValuesCollection::collectById(profiler::thread_id_t _threadId, profiler::vin_t _valueId void ArbitraryValuesCollection::collectById(profiler::thread_id_t _threadId, profiler::vin_t _valueId
, profiler::block_id_t _parentBlockId, bool _directParent) , profiler::block_id_t _parentBlockId, bool _directParent)
{ {
const bool doCalculatePoints = (m_jobType & PointsJob) != 0;
if (_threadId == 0) if (_threadId == 0)
{ {
const auto threadsCount = EASY_GLOBALS.profiler_blocks.size(); const auto threadsCount = EASY_GLOBALS.profiler_blocks.size();
const bool calculatePointsInner = (m_jobType & PointsJob) != 0 && threadsCount == 1; if (threadsCount != 0)
{
const bool calculatePointsInner = doCalculatePoints && threadsCount == 1;
for (const auto& it : EASY_GLOBALS.profiler_blocks) for (const auto& it : EASY_GLOBALS.profiler_blocks)
{ {
@ -273,14 +352,15 @@ void ArbitraryValuesCollection::collectById(profiler::thread_id_t _threadId, pro
return; return;
} }
if ((m_jobType & PointsJob) != 0 && threadsCount > 1) if (doCalculatePoints && !calculatePointsInner)
getChartPoints(*this, m_points, m_minValue, m_maxValue); getChartPoints(*this, m_points, m_minValue, m_maxValue);
} }
}
else else
{ {
const auto t = EASY_GLOBALS.profiler_blocks.find(_threadId); const auto t = EASY_GLOBALS.profiler_blocks.find(_threadId);
if (t != EASY_GLOBALS.profiler_blocks.end() && if (t != EASY_GLOBALS.profiler_blocks.end() &&
!collectByIdForThread(t->second, _valueId, (m_jobType & PointsJob) != 0, _parentBlockId, _directParent)) !collectByIdForThread(t->second, _valueId, doCalculatePoints, _parentBlockId, _directParent))
{ {
return; return;
} }
@ -292,8 +372,11 @@ void ArbitraryValuesCollection::collectById(profiler::thread_id_t _threadId, pro
bool ArbitraryValuesCollection::collectByIdForThread(const profiler::BlocksTreeRoot& _threadRoot bool ArbitraryValuesCollection::collectByIdForThread(const profiler::BlocksTreeRoot& _threadRoot
, profiler::vin_t _valueId, bool _calculatePoints, profiler::block_id_t _parentBlockId, bool _directParent) , profiler::vin_t _valueId, bool _calculatePoints, profiler::block_id_t _parentBlockId, bool _directParent)
{ {
auto& valuesList = m_values[_threadRoot.thread_id]; if (profiler_gui::is_max(_parentBlockId))
{
// All values
auto& valuesList = m_values[_threadRoot.thread_id];
for (auto i : _threadRoot.events) for (auto i : _threadRoot.events)
{ {
if (m_bInterrupt.load(std::memory_order_acquire)) if (m_bInterrupt.load(std::memory_order_acquire))
@ -309,30 +392,28 @@ bool ArbitraryValuesCollection::collectByIdForThread(const profiler::BlocksTreeR
continue; continue;
valuesList.push_back(value); valuesList.push_back(value);
if (_calculatePoints) if (_calculatePoints)
{ addPoint(*value);
const auto p = point(*block.value);
if (p.y() > m_maxValue)
m_maxValue = p.y();
if (p.y() < m_minValue)
m_minValue = p.y();
m_points.push_back(p);
}
} }
return true; return true;
} }
return depthFirstSearch(_threadRoot, _calculatePoints, _parentBlockId, _directParent
, [=] (profiler::vin_t _id, const char*) -> bool { return _id == _valueId; });
}
void ArbitraryValuesCollection::collectByName(profiler::thread_id_t _threadId, const std::string _valueName void ArbitraryValuesCollection::collectByName(profiler::thread_id_t _threadId, const std::string _valueName
, profiler::block_id_t _parentBlockId, bool _directParent) , profiler::block_id_t _parentBlockId, bool _directParent)
{ {
const bool doCalculatePoints = (m_jobType & PointsJob) != 0;
if (_threadId == 0) if (_threadId == 0)
{ {
const auto threadsCount = EASY_GLOBALS.profiler_blocks.size(); const auto threadsCount = EASY_GLOBALS.profiler_blocks.size();
const bool calculatePointsInner = (m_jobType & PointsJob) != 0 && threadsCount == 1; if (threadsCount != 0)
{
const bool calculatePointsInner = doCalculatePoints && threadsCount == 1;
for (const auto& it : EASY_GLOBALS.profiler_blocks) for (const auto& it : EASY_GLOBALS.profiler_blocks)
{ {
@ -340,14 +421,15 @@ void ArbitraryValuesCollection::collectByName(profiler::thread_id_t _threadId, c
return; return;
} }
if ((m_jobType & PointsJob) != 0 && threadsCount > 1) if (doCalculatePoints && !calculatePointsInner)
getChartPoints(*this, m_points, m_minValue, m_maxValue); getChartPoints(*this, m_points, m_minValue, m_maxValue);
} }
}
else else
{ {
const auto t = EASY_GLOBALS.profiler_blocks.find(_threadId); const auto t = EASY_GLOBALS.profiler_blocks.find(_threadId);
if (t != EASY_GLOBALS.profiler_blocks.end() && if (t != EASY_GLOBALS.profiler_blocks.end() &&
!collectByNameForThread(t->second, _valueName, (m_jobType & PointsJob) != 0, _parentBlockId, _directParent)) !collectByNameForThread(t->second, _valueName, doCalculatePoints, _parentBlockId, _directParent))
{ {
return; return;
} }
@ -359,8 +441,11 @@ void ArbitraryValuesCollection::collectByName(profiler::thread_id_t _threadId, c
bool ArbitraryValuesCollection::collectByNameForThread(const profiler::BlocksTreeRoot& _threadRoot bool ArbitraryValuesCollection::collectByNameForThread(const profiler::BlocksTreeRoot& _threadRoot
, const std::string& _valueName, bool _calculatePoints, profiler::block_id_t _parentBlockId, bool _directParent) , const std::string& _valueName, bool _calculatePoints, profiler::block_id_t _parentBlockId, bool _directParent)
{ {
auto& valuesList = m_values[_threadRoot.thread_id]; if (profiler_gui::is_max(_parentBlockId))
{
// All values
auto& valuesList = m_values[_threadRoot.thread_id];
for (auto i : _threadRoot.events) for (auto i : _threadRoot.events)
{ {
if (m_bInterrupt.load(std::memory_order_acquire)) if (m_bInterrupt.load(std::memory_order_acquire))
@ -372,23 +457,130 @@ bool ArbitraryValuesCollection::collectByNameForThread(const profiler::BlocksTre
continue; continue;
valuesList.push_back(block.value); valuesList.push_back(block.value);
if (_calculatePoints)
addPoint(*block.value);
}
return true;
}
return depthFirstSearch(_threadRoot, _calculatePoints, _parentBlockId, _directParent
, [&_valueName] (profiler::vin_t, const char* _name) -> bool { return _valueName == _name; });
}
bool ArbitraryValuesCollection::depthFirstSearch(const profiler::BlocksTreeRoot& _threadRoot, bool _calculatePoints
, profiler::block_id_t _parentBlockId, bool _directParent, std::function<bool(profiler::vin_t, const char*)> _isSuitableValue)
{
if (_threadRoot.children.empty())
return true;
auto& valuesList = m_values[_threadRoot.thread_id];
using StackEntry = std::pair<profiler::block_index_t, profiler::block_index_t>;
using Stack = std::vector<StackEntry>;
Stack stack;
for (const auto index : _threadRoot.children)
{
if (m_bInterrupt.load(std::memory_order_acquire))
return false;
stack.clear();
stack.emplace_back(index, static_cast<profiler::block_index_t>(0));
profiler::timestamp_t lastMatchedParentDuration = 0;
size_t matchedParentIdStackDepth = 0;
bool matchedParentId = false;
while (!stack.empty())
{
if (m_bInterrupt.load(std::memory_order_acquire))
return false;
auto& top = stack.back();
auto& first = top.second;
const auto i = top.first;
const auto parent = stack.size() > 1 ? (++stack.rbegin())->first :
profiler_gui::numeric_max<profiler::block_index_t>();
const auto& block = easyBlock(i).tree;
const auto& desc = easyDescriptor(block.node->id());
if (desc.type() == profiler::BlockType::Value && matchedParentId)
{
const auto value = block.value;
if (_isSuitableValue(value->value_id(), desc.name()))
{
bool hit = false;
if (!_directParent)
{
hit = true;
}
else
{
const auto parentId = easyBlock(parent).tree.node->id();
hit = parentId == _parentBlockId || easyDescriptor(parentId).id() == _parentBlockId;
}
if (hit)
{
valuesList.push_back(value);
if (_calculatePoints) if (_calculatePoints)
{ {
const auto p = point(*block.value); const auto val = addPoint(*value);
if (m_chartType == ChartType::Complexity)
{
m_complexityMap[val].push_back(lastMatchedParentDuration);
if (lastMatchedParentDuration < m_minDuration)
m_minDuration = lastMatchedParentDuration;
if (lastMatchedParentDuration > m_maxDuration)
m_maxDuration = lastMatchedParentDuration;
}
}
}
}
}
if (p.y() > m_maxValue) if (first < block.children.size())
m_maxValue = p.y(); {
if (p.y() < m_minValue) if (block.node->id() == _parentBlockId || desc.id() == _parentBlockId)
m_minValue = p.y(); {
if (!matchedParentId)
matchedParentIdStackDepth = stack.size();
matchedParentId = true;
lastMatchedParentDuration = block.node->duration();
}
m_points.push_back(p); const auto child = block.children[first++];
stack.emplace_back(child, static_cast<profiler::block_index_t>(0));
}
else
{
if (stack.size() == matchedParentIdStackDepth)
matchedParentId = false;
stack.pop_back();
}
} }
} }
return true; return true;
} }
double ArbitraryValuesCollection::addPoint(const profiler::ArbitraryValue& _value)
{
const auto p = point(_value);
if (p.y() > m_maxValue)
m_maxValue = p.y();
if (p.y() < m_minValue)
m_minValue = p.y();
m_points.push_back(p);
return p.y();
}
QPointF ArbitraryValuesCollection::point(const profiler::ArbitraryValue& _value) const QPointF ArbitraryValuesCollection::point(const profiler::ArbitraryValue& _value) const
{ {
const qreal x = PROF_MICROSECONDS(qreal(_value.begin() - m_beginTime)); const qreal x = PROF_MICROSECONDS(qreal(_value.begin() - m_beginTime));
@ -402,6 +594,11 @@ ArbitraryValuesChartItem::ArbitraryValuesChartItem()
: Parent() : Parent()
, m_workerMaxValue(0) , m_workerMaxValue(0)
, m_workerMinValue(0) , m_workerMinValue(0)
, m_workerMaxDuration(0)
, m_workerMinDuration(0)
, m_maxDuration(0)
, m_minDuration(0)
, m_chartType(ChartType::Regular)
{ {
} }
@ -537,10 +734,23 @@ bool ArbitraryValuesChartItem::updateImage()
const auto bindMode = widget->bindMode(); const auto bindMode = widget->bindMode();
const auto beginTime = EASY_GLOBALS.begin_time; const auto beginTime = EASY_GLOBALS.begin_time;
const auto autoHeight = EASY_GLOBALS.auto_adjust_chart_height; const auto autoHeight = EASY_GLOBALS.auto_adjust_chart_height;
m_worker.enqueue([=] {
updateImageAsync(rect, scale, left, right, right - left, value, window, bindMode, if (m_chartType == ChartType::Regular)
beginTime, autoHeight); {
m_worker.enqueue([=]
{
updateRegularImageAsync(rect, scale, left, right, right - left, value, window, bindMode, beginTime,
autoHeight);
}, m_bReady); }, m_bReady);
}
else
{
m_worker.enqueue([=]
{
updateComplexityImageAsync(rect, scale, left, right, right - left, value, window, bindMode, beginTime,
autoHeight);
}, m_bReady);
}
return true; return true;
} }
@ -551,8 +761,36 @@ void ArbitraryValuesChartItem::onImageUpdated()
m_minValue = m_workerMinValue; m_minValue = m_workerMinValue;
} }
void ArbitraryValuesChartItem::updateImageAsync(QRectF _boundingRect, qreal _current_scale, qreal _minimum, qreal _maximum, qreal _range, void ArbitraryValuesChartItem::drawGrid(QPainter& _painter, int _width, int _height) const
qreal _value, qreal _width, bool _bindMode, profiler::timestamp_t _begin_time, bool _autoAdjust) {
auto pen = _painter.pen();
pen.setColor(Qt::darkGray);
pen.setStyle(Qt::DotLine);
_painter.setPen(pen);
const int left = 0;
const int top = 0;
const int hlines = _height / 20;
for (int i = 0; i < hlines; ++i)
{
const auto y = top + 20 + i * 20;
_painter.drawLine(left, y, left + _width, y);
}
const int vlines = _width / 20;
for (int i = 0; i < vlines; ++i)
{
const auto x = left + 20 + i * 20;
_painter.drawLine(x, top, x, top + _height);
}
_painter.setPen(Qt::SolidLine);
}
void ArbitraryValuesChartItem::updateRegularImageAsync(QRectF _boundingRect, qreal _current_scale, qreal _minimum
, qreal _maximum, qreal _range, qreal _value, qreal _width, bool _bindMode, profiler::timestamp_t _begin_time
, bool _autoAdjust)
{ {
const auto screenWidth = _boundingRect.width() * _current_scale; const auto screenWidth = _boundingRect.width() * _current_scale;
//const auto maxColumnHeight = _boundingRect.height(); //const auto maxColumnHeight = _boundingRect.height();
@ -577,33 +815,7 @@ void ArbitraryValuesChartItem::updateImageAsync(QRectF _boundingRect, qreal _cur
p.setRenderHint(QPainter::Antialiasing, true); p.setRenderHint(QPainter::Antialiasing, true);
// Draw grid // Draw grid
{ drawGrid(p, m_workerImage->width(), m_workerImage->height());
auto pen = p.pen();
pen.setColor(Qt::darkGray);
pen.setStyle(Qt::DotLine);
p.setPen(pen);
const int left = 0;
const int top = 0;
const int w = m_workerImage->width();
const int h = m_workerImage->height();
const int hlines = h / 20;
for (int i = 0; i < hlines; ++i)
{
const auto y = top + 20 + i * 20;
p.drawLine(left, y, left + w, y);
}
const int vlines = w / 20;
for (int i = 0; i < vlines; ++i)
{
const auto x = left + 20 + i * 20;
p.drawLine(x, top, x, top + h);
}
p.setPen(Qt::SolidLine);
}
if (m_collections.empty() || isReady()) if (m_collections.empty() || isReady())
{ {
@ -668,12 +880,8 @@ void ArbitraryValuesChartItem::updateImageAsync(QRectF _boundingRect, qreal _cur
continue; continue;
const auto value = it->y(); const auto value = it->y();
minValue = std::min(minValue, value);
if (minValue > value) maxValue = std::max(maxValue, value);
minValue = value;
if (maxValue < value)
maxValue = value;
} }
continue; continue;
@ -684,11 +892,8 @@ void ArbitraryValuesChartItem::updateImageAsync(QRectF _boundingRect, qreal _cur
leftBounds.emplace_back(points.begin()); leftBounds.emplace_back(points.begin());
} }
if (minValue > collection.minValue()) minValue = std::min(minValue, collection.minValue());
minValue = collection.minValue(); maxValue = std::max(maxValue, collection.maxValue());
if (maxValue < collection.maxValue())
maxValue = collection.maxValue();
} }
if (minValue > maxValue) if (minValue > maxValue)
@ -754,7 +959,7 @@ void ArbitraryValuesChartItem::updateImageAsync(QRectF _boundingRect, qreal _cur
const auto first = leftBounds[i]; const auto first = leftBounds[i];
if (c.chartType == ChartViewType::Points) if (c.chartPenStyle == ChartPenStyle::Points)
{ {
qreal prevX = 1e300, prevY = 1e300; qreal prevX = 1e300, prevY = 1e300;
for (auto it = first; it != points.end() && it->x() < _maximum; ++it) for (auto it = first; it != points.end() && it->x() < _maximum; ++it)
@ -852,6 +1057,219 @@ void ArbitraryValuesChartItem::updateImageAsync(QRectF _boundingRect, qreal _cur
setReady(true); setReady(true);
} }
void ArbitraryValuesChartItem::updateComplexityImageAsync(QRectF _boundingRect, qreal _current_scale, qreal _minimum
, qreal _maximum, qreal _range, qreal _value, qreal _width, bool _bindMode, profiler::timestamp_t _begin_time
, bool _autoAdjust)
{
const auto rectHeight = _boundingRect.height();
const auto screenWidth = _boundingRect.width() * _current_scale;
//const auto maxColumnHeight = _boundingRect.height();
m_workerImageScale = 1;
m_workerImageOrigin = _minimum;
m_workerImage = new QImage(screenWidth + 0.5, rectHeight, QImage::Format_ARGB32);
m_workerImage->fill(0);
QPainter p(m_workerImage);
p.setBrush(Qt::NoBrush);
p.setRenderHint(QPainter::Antialiasing, true);
// Draw grid
drawGrid(p, m_workerImage->width(), m_workerImage->height());
if (m_collections.empty() || isReady())
{
setReady(true);
return;
}
using LeftBounds = std::vector<Points::const_iterator>;
qreal realScale = _current_scale, offset = 0;
const auto right = std::min(_value + _width, _maximum);
qreal minValue = 1e300, maxValue = -1e300;
profiler::timestamp_t minDuration = profiler_gui::numeric_max<profiler::timestamp_t>(), maxDuration = 0;
for (const auto& c : m_collections)
{
if (isReady())
return;
const auto& collection = *c.ptr;
const auto& complexityMap = collection.complexityMap();
if (complexityMap.empty())
continue;
minValue = std::min(minValue, collection.minValue());
maxValue = std::max(maxValue, collection.maxValue());
minDuration = std::min(minDuration, collection.minDuration());
maxDuration = std::max(maxDuration, collection.maxDuration());
}
if (minValue > maxValue || minDuration > maxDuration)
{
// No points
m_workerMaxValue = 0;
m_workerMinValue = 0;
m_workerMaxDuration = 0;
m_workerMinDuration = 0;
setReady(true);
return;
}
m_workerMaxValue = maxValue;
m_workerMinValue = minValue;
m_workerMaxDuration = maxDuration;
m_workerMinDuration = minDuration;
if (isReady())
return;
const auto ymiddle = rectHeight * 0.5;
const auto dt = maxDuration - minDuration;
bool singleValue = dt == 0;
const qreal height = std::max(static_cast<qreal>(dt), 0.01);
const auto gety = [=] (profiler::timestamp_t duration)
{
qreal y;
if (singleValue)
{
y = ymiddle;
}
else
{
y = static_cast<qreal>(maxDuration - duration);
y /= height;
y *= rectHeight - ChartBounds;
y += ChartBound;
}
return y;
};
const auto xmiddle = screenWidth * 0.5;
singleValue = fabs(maxValue - minValue) < 2 * std::numeric_limits<qreal>::epsilon();
const qreal width = std::max(maxValue - minValue, 0.01);
const auto getx = [=] (qreal x)
{
if (singleValue)
{
x = xmiddle;
}
else
{
x -= minValue;
x *= screenWidth / width;
}
return x;
};
std::vector<QPointF> averages;
for (const auto& c : m_collections)
{
if (isReady())
return;
const auto& complexityMap = c.ptr->complexityMap();
if (complexityMap.empty())
continue;
if (c.selected)
{
auto pen = p.pen();
pen.setColor(QColor::fromRgba(c.color));
pen.setWidth(2);
p.setPen(pen);
}
else
{
p.setPen(QColor::fromRgba(c.color));
}
averages.clear();
averages.reserve(complexityMap.size());
auto it = complexityMap.begin();
qreal x = getx(it->first);
profiler::timestamp_t average = 0;
for (auto duration : it->second)
{
average += duration;
p.drawPoint(QPointF {x, gety(duration)});
}
average /= it->second.size();
averages.emplace_back(x, gety(average));
for (++it; it != complexityMap.end(); ++it)
{
if (isReady())
return;
x = getx(it->first);
average = 0;
for (auto duration : it->second)
{
average += duration;
p.drawPoint(QPointF {x, gety(duration)});
}
average /= it->second.size();
averages.emplace_back(x, gety(average));
}
//if (c.selected)
{
p.drawPolyline(averages.data(), static_cast<int>(averages.size()));
auto color = profiler_gui::darken(c.color, 0.65f);
if (profiler_gui::alpha(color) < 0xc0)
p.setPen(QColor::fromRgba(profiler::colors::modify_alpha32(0xc0000000, color)));
else
p.setPen(QColor::fromRgba(color));
p.setBrush(QColor::fromRgba(0xc8ffffff));
QPointF prev {-500., -500.};
for (const auto& point : averages)
{
if (isReady())
return;
const auto line = point - prev;
const auto delta = estd::sqr(line.x()) + estd::sqr(line.y());
if (delta > 25)
{
p.drawEllipse(point, 3, 3);
prev = point;
}
}
p.setBrush(Qt::NoBrush);
auto pen = p.pen();
pen.setWidth(2);
p.setPen(pen);
//averages = std::move(gauss<8>(averages));
QPainterPath pp;
pp.moveTo(averages.front());
const size_t step = std::max(averages.size() / (size_t)40, (size_t)1);
for (size_t k = 3 * step; k < averages.size(); k += 4 * step)
{
pp.cubicTo(averages[k - 2 * step], averages[k - 1 * step], averages[k]);
}
p.drawPath(pp);
}
}
setReady(true);
}
void ArbitraryValuesChartItem::clear() void ArbitraryValuesChartItem::clear()
{ {
cancelAnyJob(); cancelAnyJob();
@ -877,6 +1295,21 @@ void ArbitraryValuesChartItem::update(const ArbitraryValuesCollection* _selected
updateImage(); updateImage();
} }
void ArbitraryValuesChartItem::setChartType(ChartType _chartType)
{
if (m_chartType != _chartType)
{
cancelImageUpdate();
m_chartType = _chartType;
updateImage();
}
}
ChartType ArbitraryValuesChartItem::chartType() const
{
return m_chartType;
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
GraphicsChart::GraphicsChart(QWidget* _parent) GraphicsChart::GraphicsChart(QWidget* _parent)
@ -927,6 +1360,16 @@ void GraphicsChart::update(const ArbitraryValuesCollection* _selected)
m_chartItem->update(_selected); m_chartItem->update(_selected);
} }
void GraphicsChart::setChartType(ChartType _chartType)
{
m_chartItem->setChartType(_chartType);
}
ChartType GraphicsChart::chartType() const
{
return m_chartItem->chartType();
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
enum class ArbitraryColumns : uint8_t enum class ArbitraryColumns : uint8_t
@ -994,7 +1437,7 @@ ArbitraryValuesCollection* ArbitraryTreeWidgetItem::collection()
return m_collection.get(); return m_collection.get();
} }
void ArbitraryTreeWidgetItem::collectValues(profiler::thread_id_t _threadId) void ArbitraryTreeWidgetItem::collectValues(profiler::thread_id_t _threadId, ChartType _chartType)
{ {
if (!m_collection) if (!m_collection)
m_collection = CollectionPtr(new ArbitraryValuesCollection); m_collection = CollectionPtr(new ArbitraryValuesCollection);
@ -1011,8 +1454,9 @@ void ArbitraryTreeWidgetItem::collectValues(profiler::thread_id_t _threadId)
directParent = true; directParent = true;
} }
m_collection->collectValues(_threadId, m_vin, text(int_cast(ArbitraryColumns::Name)).toStdString().c_str(), m_collection->collectValuesAndPoints(_chartType, _threadId, m_vin
EASY_GLOBALS.begin_time, parentBlockId, directParent); , text(int_cast(ArbitraryColumns::Name)).toStdString().c_str(), EASY_GLOBALS.begin_time
, parentBlockId, directParent);
} }
void ArbitraryTreeWidgetItem::interrupt() void ArbitraryTreeWidgetItem::interrupt()
@ -1047,9 +1491,24 @@ ArbitraryValuesWidget::ArbitraryValuesWidget(QWidget* _parent)
auto tb = new QToolBar(this); auto tb = new QToolBar(this);
tb->setIconSize(applicationIconsSize()); tb->setIconSize(applicationIconsSize());
auto refreshButton = tb->addAction(QIcon(imagePath("reload")), tr("Refresh values list"));
refreshButton->setToolTip(tr("Refresh arbitrary values list.")); auto action = tb->addAction(QIcon(imagePath("reload")), tr("Refresh values list"));
connect(refreshButton, &QAction::triggered, this, &This::rebuild); action->setToolTip(tr("Refresh arbitrary values list."));
connect(action, &QAction::triggered, this, &This::rebuild);
auto actionGroup = new QActionGroup(this);
actionGroup->setExclusive(true);
action = new QAction(tr("Regular chart"), actionGroup);
action->setCheckable(true);
action->setChecked(true);
connect(action, &QAction::triggered, this, &This::onRegularChartTypeChecked);
tb->addAction(action);
action = new QAction(tr("Complexity chart"), actionGroup);
action->setCheckable(true);
connect(action, &QAction::triggered, this, &This::onComplexityChartTypeChecked);
tb->addAction(action);
auto layout = new QVBoxLayout(this); auto layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
@ -1097,7 +1556,11 @@ void ArbitraryValuesWidget::clear()
{ {
if (m_collectionsTimer.isActive()) if (m_collectionsTimer.isActive())
m_collectionsTimer.stop(); m_collectionsTimer.stop();
// Warning! Cancel image update first because it uses POINTERS which will be destroyed
// in m_treeWidget->clear()
m_chart->cancelImageUpdate(); m_chart->cancelImageUpdate();
m_checkedItems.clear(); m_checkedItems.clear();
m_treeWidget->clear(); m_treeWidget->clear();
m_boldItem = nullptr; m_boldItem = nullptr;
@ -1154,15 +1617,23 @@ void ArbitraryValuesWidget::onItemChanged(QTreeWidgetItem* _item, int _column)
if (item->checkState(CheckColumn) == Qt::Checked) if (item->checkState(CheckColumn) == Qt::Checked)
{ {
m_checkedItems.push_back(item); m_checkedItems.push_back(item);
item->collectValues(EASY_GLOBALS.selected_thread); item->collectValues(EASY_GLOBALS.selected_thread, m_chart->chartType());
if (!m_collectionsTimer.isActive()) if (!m_collectionsTimer.isActive())
m_collectionsTimer.start(100); m_collectionsTimer.start(100);
} }
else else
{ {
// !!!
// Warning! Spaghetti-code detected! :)
//
// m_chart passes POINTERS to updateImage() so at first we MUST cancel image update
// and only then we can invoke item->interrupt() because passed pointer will be destroyed
// in interrupt().
// !!!
m_checkedItems.removeOne(item); m_checkedItems.removeOne(item);
item->interrupt();
onCollectionsTimeout(); onCollectionsTimeout();
item->interrupt();
} }
} }
@ -1206,7 +1677,7 @@ void ArbitraryValuesWidget::onCollectionsTimeout()
if (item->collection()->status() != ArbitraryValuesCollection::InProgress) if (item->collection()->status() != ArbitraryValuesCollection::InProgress)
{ {
collections.push_back(CollectionPaintData {item->collection(), item->color(), collections.push_back(CollectionPaintData {item->collection(), item->color(),
ChartViewType::Line, item == m_treeWidget->currentItem()}); ChartPenStyle::Line, item == m_treeWidget->currentItem()});
} }
} }
@ -1218,6 +1689,38 @@ void ArbitraryValuesWidget::onCollectionsTimeout()
} }
} }
void ArbitraryValuesWidget::repaint()
{
if (!m_checkedItems.empty())
{
m_chart->cancelImageUpdate();
for (auto item : m_checkedItems)
item->collectValues(EASY_GLOBALS.selected_thread, m_chart->chartType());
if (!m_collectionsTimer.isActive())
m_collectionsTimer.start(100);
}
}
void ArbitraryValuesWidget::onRegularChartTypeChecked(bool _checked)
{
if (!_checked)
return;
m_chart->setChartType(ChartType::Regular);
repaint();
}
void ArbitraryValuesWidget::onComplexityChartTypeChecked(bool _checked)
{
if (!_checked)
return;
m_chart->setChartType(ChartType::Complexity);
repaint();
}
void ArbitraryValuesWidget::buildTree(profiler::thread_id_t _threadId, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId) void ArbitraryValuesWidget::buildTree(profiler::thread_id_t _threadId, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId)
{ {
m_treeWidget->clear(); m_treeWidget->clear();

View File

@ -65,8 +65,10 @@
#include <easy/reader.h> #include <easy/reader.h>
#include <easy/utility.h> #include <easy/utility.h>
#include <unordered_map> #include <unordered_map>
#include <map>
#include <vector> #include <vector>
#include <string> #include <string>
#include <functional>
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include "graphics_slider_area.h" #include "graphics_slider_area.h"
@ -77,6 +79,32 @@
using Points = std::vector<QPointF>; using Points = std::vector<QPointF>;
using ArbitraryValues = std::vector<const profiler::ArbitraryValue*>; using ArbitraryValues = std::vector<const profiler::ArbitraryValue*>;
using ArbitraryValuesMap = std::unordered_map<profiler::thread_id_t, ArbitraryValues, estd::hash<profiler::thread_id_t> >; using ArbitraryValuesMap = std::unordered_map<profiler::thread_id_t, ArbitraryValues, estd::hash<profiler::thread_id_t> >;
using Durations = std::vector<profiler::timestamp_t>;
using ComplexityValuesMap = std::map<double, Durations>;
enum class ChartType : uint8_t
{
Regular = 0, ///< Regular chart; X axis = time, Y axis = value
Complexity ///< Complexity chart; X axis = value, Y axis = duration
};
enum class ComplexityType : uint8_t
{
Constant = 0, ///< O(1)
Logarithmic, ///< O(logN)
Linear, ///< O(N)
Quasilinear, ///< O(N*logN)
Quadratic, ///< O(N^2)
Cubic, ///< O(N^3)
Exponential, ///< O(2^N)
Factorial, ///< O(N!)
};
enum class ChartPenStyle : uint8_t
{
Line = 0,
Points
};
class ArbitraryValuesCollection EASY_FINAL class ArbitraryValuesCollection EASY_FINAL
{ {
@ -90,13 +118,17 @@ private:
using This = ArbitraryValuesCollection; using This = ArbitraryValuesCollection;
ArbitraryValuesMap m_values; ArbitraryValuesMap m_values;
ComplexityValuesMap m_complexityMap;
Points m_points; Points m_points;
ThreadPoolTask m_worker; ThreadPoolTask m_worker;
profiler::timestamp_t m_beginTime; profiler::timestamp_t m_beginTime;
profiler::timestamp_t m_minDuration;
profiler::timestamp_t m_maxDuration;
qreal m_minValue; qreal m_minValue;
qreal m_maxValue; qreal m_maxValue;
std::atomic<uint8_t> m_status; std::atomic<uint8_t> m_status;
std::atomic_bool m_bInterrupt; std::atomic_bool m_bInterrupt;
ChartType m_chartType;
uint8_t m_jobType; uint8_t m_jobType;
public: public:
@ -104,17 +136,23 @@ public:
explicit ArbitraryValuesCollection(); explicit ArbitraryValuesCollection();
~ArbitraryValuesCollection(); ~ArbitraryValuesCollection();
ChartType chartType() const;
const ArbitraryValuesMap& valuesMap() const; const ArbitraryValuesMap& valuesMap() const;
const ComplexityValuesMap& complexityMap() const;
const Points& points() const; const Points& points() const;
JobStatus status() const; JobStatus status() const;
size_t size() const; size_t size() const;
profiler::timestamp_t minDuration() const;
profiler::timestamp_t maxDuration() const;
qreal minValue() const; qreal minValue() const;
qreal maxValue() const; qreal maxValue() const;
void collectValues(profiler::thread_id_t _threadId, profiler::vin_t _valueId, const char* _valueName, profiler::block_id_t _parentBlockId, bool _directParent); void collectValues(ChartType _chartType, profiler::thread_id_t _threadId, profiler::vin_t _valueId, const char* _valueName, profiler::block_id_t _parentBlockId, bool _directParent);
void collectValues(profiler::thread_id_t _threadId, profiler::vin_t _valueId, const char* _valueName, profiler::timestamp_t _beginTime, profiler::block_id_t _parentBlockId, bool _directParent);
bool calculatePoints(profiler::timestamp_t _beginTime); bool calculatePoints(profiler::timestamp_t _beginTime);
void collectValuesAndPoints(ChartType _chartType, profiler::thread_id_t _threadId, profiler::vin_t _valueId, const char* _valueName, profiler::timestamp_t _beginTime, profiler::block_id_t _parentBlockId, bool _directParent);
void interrupt(); void interrupt();
private: private:
@ -125,27 +163,19 @@ private:
bool collectByIdForThread(const profiler::BlocksTreeRoot& _threadRoot, profiler::vin_t _valueId, bool _calculatePoints, profiler::block_id_t _parentBlockId, bool _directParent); bool collectByIdForThread(const profiler::BlocksTreeRoot& _threadRoot, profiler::vin_t _valueId, bool _calculatePoints, profiler::block_id_t _parentBlockId, bool _directParent);
bool collectByNameForThread(const profiler::BlocksTreeRoot& _threadRoot, const std::string& _valueName, bool _calculatePoints, profiler::block_id_t _parentBlockId, bool _directParent); bool collectByNameForThread(const profiler::BlocksTreeRoot& _threadRoot, const std::string& _valueName, bool _calculatePoints, profiler::block_id_t _parentBlockId, bool _directParent);
bool depthFirstSearch(const profiler::BlocksTreeRoot& _threadRoot, bool _calculatePoints
, profiler::block_id_t _parentBlockId, bool _directParent, std::function<bool(profiler::vin_t, const char*)> _isSuitableValue);
double addPoint(const profiler::ArbitraryValue& _value);
QPointF point(const profiler::ArbitraryValue& _value) const; QPointF point(const profiler::ArbitraryValue& _value) const;
}; // end of class ArbitraryValuesCollection. }; // end of class ArbitraryValuesCollection.
enum class ChartType : uint8_t
{
Vt = 0, ///< V(t) chart; X axis = time, Y axis = value
Dv ///< D(v) chart; X axis = value, Y axis = duration
};
enum class ChartViewType : uint8_t
{
Line = 0,
Points
};
struct CollectionPaintData EASY_FINAL struct CollectionPaintData EASY_FINAL
{ {
const ArbitraryValuesCollection* ptr; const ArbitraryValuesCollection* ptr;
QRgb color; QRgb color;
ChartViewType chartType; ChartPenStyle chartPenStyle;
bool selected; bool selected;
}; };
@ -161,6 +191,11 @@ class ArbitraryValuesChartItem : public GraphicsImageItem
Collections m_collections; Collections m_collections;
qreal m_workerMaxValue; qreal m_workerMaxValue;
qreal m_workerMinValue; qreal m_workerMinValue;
profiler::timestamp_t m_workerMaxDuration;
profiler::timestamp_t m_workerMinDuration;
profiler::timestamp_t m_maxDuration;
profiler::timestamp_t m_minDuration;
ChartType m_chartType;
public: public:
@ -180,14 +215,21 @@ public:
void clear(); void clear();
void update(Collections _collections); void update(Collections _collections);
void update(const ArbitraryValuesCollection* _selected); void update(const ArbitraryValuesCollection* _selected);
void setChartType(ChartType _chartType);
ChartType chartType() const;
private: private:
void paintMouseIndicator(QPainter* _painter, qreal _top, qreal _bottom, qreal _width, qreal _height, int _font_h, void paintMouseIndicator(QPainter* _painter, qreal _top, qreal _bottom, qreal _width, qreal _height, int _font_h,
qreal _visibleRegionLeft, qreal _visibleRegionWidth); qreal _visibleRegionLeft, qreal _visibleRegionWidth);
void updateImageAsync(QRectF _boundingRect, qreal _current_scale, qreal _minimum, qreal _maximum, qreal _range, void updateRegularImageAsync(QRectF _boundingRect, qreal _current_scale, qreal _minimum, qreal _maximum
qreal _value, qreal _width, bool _bindMode, profiler::timestamp_t _begin_time, bool _autoAdjust); , qreal _range, qreal _value, qreal _width, bool _bindMode, profiler::timestamp_t _begin_time, bool _autoAdjust);
void updateComplexityImageAsync(QRectF _boundingRect, qreal _current_scale, qreal _minimum, qreal _maximum
, qreal _range, qreal _value, qreal _width, bool _bindMode, profiler::timestamp_t _begin_time, bool _autoAdjust);
void drawGrid(QPainter& _painter, int _width, int _height) const;
}; // end of class ArbitraryValuesChartItem. }; // end of class ArbitraryValuesChartItem.
@ -220,6 +262,8 @@ public:
void cancelImageUpdate(); void cancelImageUpdate();
void update(Collections _collections); void update(Collections _collections);
void update(const ArbitraryValuesCollection* _selected); void update(const ArbitraryValuesCollection* _selected);
void setChartType(ChartType _chartType);
ChartType chartType() const;
private slots: private slots:
@ -253,7 +297,7 @@ public:
const ArbitraryValuesCollection* collection() const; const ArbitraryValuesCollection* collection() const;
ArbitraryValuesCollection* collection(); ArbitraryValuesCollection* collection();
void collectValues(profiler::thread_id_t _threadId); void collectValues(profiler::thread_id_t _threadId, ChartType _chartType);
void interrupt(); void interrupt();
profiler::color_t color() const; profiler::color_t color() const;
@ -296,9 +340,13 @@ private slots:
void onItemChanged(QTreeWidgetItem* _item, int _column); void onItemChanged(QTreeWidgetItem* _item, int _column);
void onCurrentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*); void onCurrentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*);
void onCollectionsTimeout(); void onCollectionsTimeout();
void onRegularChartTypeChecked(bool _checked);
void onComplexityChartTypeChecked(bool _checked);
private: private:
void repaint();
void buildTree(profiler::thread_id_t _threadId, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId); void buildTree(profiler::thread_id_t _threadId, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId);
QTreeWidgetItem* buildTreeForThread(const profiler::BlocksTreeRoot& _threadRoot, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId); QTreeWidgetItem* buildTreeForThread(const profiler::BlocksTreeRoot& _threadRoot, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId);

View File

@ -22,7 +22,7 @@ int RESOURCE_LOADING_COUNT = 50;
#define SAMPLE_NETWORK_TEST #define SAMPLE_NETWORK_TEST
void localSleep(int magic=200000) void localSleep(uint64_t magic=200000)
{ {
//PROFILER_BEGIN_FUNCTION_BLOCK_GROUPED(profiler::colors::Blue); //PROFILER_BEGIN_FUNCTION_BLOCK_GROUPED(profiler::colors::Blue);
volatile int i = 0; volatile int i = 0;
@ -136,10 +136,20 @@ void calculatePhysics(){
//std::this_thread::sleep_for(std::chrono::milliseconds(8)); //std::this_thread::sleep_for(std::chrono::milliseconds(8));
} }
void frame(){ void quadratic_loop(uint64_t n)
{
EASY_FUNCTION(profiler::colors::Blue);
EASY_VALUE("N", n);
volatile int i = 0;
for (; i < n; ++i)
localSleep(n);
}
void frame(uint64_t n){
EASY_FUNCTION(profiler::colors::Magenta); EASY_FUNCTION(profiler::colors::Magenta);
prepareRender(); prepareRender();
calculatePhysics(); calculatePhysics();
quadratic_loop(n);
} }
void loadingResourcesThread(){ void loadingResourcesThread(){
@ -189,13 +199,17 @@ void renderThread(){
//std::unique_lock<std::mutex> lk(cv_m); //std::unique_lock<std::mutex> lk(cv_m);
//cv.wait(lk, []{return g_i == 1; }); //cv.wait(lk, []{return g_i == 1; });
EASY_THREAD("Render"); EASY_THREAD("Render");
uint64_t n = 20;
#ifdef SAMPLE_NETWORK_TEST #ifdef SAMPLE_NETWORK_TEST
while (true) { while (true) {
#else #else
for (int i = 0; i < RENDER_STEPS; i++){ for (int i = 0; i < RENDER_STEPS; i++){
#endif #endif
frame(); frame(n);
localSleep(1200000); localSleep(1200000);
n += 20;
if (n >= 700)
n = 20;
//std::this_thread::sleep_for(std::chrono::milliseconds(20)); //std::this_thread::sleep_for(std::chrono::milliseconds(20));
} }
} }