0
0
mirror of https://github.com/yse/easy_profiler.git synced 2024-12-25 23:40:51 +08:00
easy_profiler/profiler_gui/arbitrary_value_inspector.cpp
2024-05-13 04:09:30 -04:00

3010 lines
96 KiB
C++

/************************************************************************
* file name : arbitrary_value_inspector.cpp
* ----------------- :
* creation time : 2017/11/30
* author : Victor Zarubkin
* email : v.s.zarubkin@gmail.com
* ----------------- :
* description : The file contains implementation of .
* ----------------- :
* change log : * 2017/11/30 Victor Zarubkin: initial commit.
* :
* : *
* ----------------- :
* license : Lightweight profiler library for c++
* : Copyright(C) 2016-2019 Sergey Yagovtsev, Victor Zarubkin
* :
* : Licensed under either of
* : * MIT license (LICENSE.MIT or http://opensource.org/licenses/MIT)
* : * Apache License, Version 2.0, (LICENSE.APACHE or http://www.apache.org/licenses/LICENSE-2.0)
* : at your option.
* :
* : The MIT License
* :
* : Permission is hereby granted, free of charge, to any person obtaining a copy
* : of this software and associated documentation files (the "Software"), to deal
* : in the Software without restriction, including without limitation the rights
* : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* : of the Software, and to permit persons to whom the Software is furnished
* : to do so, subject to the following conditions:
* :
* : The above copyright notice and this permission notice shall be included in all
* : copies or substantial portions of the Software.
* :
* : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* : INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* : PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* : LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* : TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* : USE OR OTHER DEALINGS IN THE SOFTWARE.
* :
* : The Apache License, Version 2.0 (the "License")
* :
* : You may not use this file except in compliance with the License.
* : You may obtain a copy of the License at
* :
* : http://www.apache.org/licenses/LICENSE-2.0
* :
* : Unless required by applicable law or agreed to in writing, software
* : distributed under the License is distributed on an "AS IS" BASIS,
* : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* : See the License for the specific language governing permissions and
* : limitations under the License.
************************************************************************/
#include <QAction>
#include <QActionGroup>
#include <QColor>
#include <QComboBox>
#include <QFileDialog>
#include <QFileInfo>
#include <QGraphicsScene>
#include <QHeaderView>
#include <QLabel>
#include <QMessageBox>
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QResizeEvent>
#include <QSettings>
#include <QSpinBox>
#include <QSplitter>
#include <QToolBar>
#include <QVariant>
#include <QVBoxLayout>
#include <list>
#include <set>
#include <cmath>
#include "arbitrary_value_inspector.h"
#include "dialog.h"
#include "globals.h"
#include "complexity_calculator.h"
//////////////////////////////////////////////////////////////////////////
EASY_CONSTEXPR int ChartBound = 2; ///< Top and bottom bounds for chart
EASY_CONSTEXPR int ChartBounds = ChartBound << 1;
void gaussFilter(std::vector<QPointF>& _points, int _windowSize, const std::atomic_bool& _interrupt)
{
if (_points.size() < 3 || _windowSize < 3 || _interrupt.load(std::memory_order_acquire))
return;
std::vector<QPointF> out;
out.reserve(_points.size());
for (size_t i = 0, size = _points.size(); i < size; ++i)
{
if (_interrupt.load(std::memory_order_acquire))
return;
const auto next = i + 1;
qreal sum = 0;
if (static_cast<int>(next) < _windowSize)
{
for (size_t j = 0; j <= i; ++j)
sum += _points[j].y();
sum /= i + 1;
}
else
{
for (size_t j = next - _windowSize; j <= i; ++j)
sum += _points[j].y();
sum /= _windowSize;
}
out.emplace_back(_points[i].x(), sum);
}
_points = std::move(out);
}
void medianFilter(std::vector<QPointF>& _points, int _windowSize, const std::atomic_bool& _interrupt)
{
if (_points.size() < 3 || _windowSize < 3 || _interrupt.load(std::memory_order_acquire))
return;
const auto windowSizeHalf = _windowSize >> 1;
std::vector<QPointF> out;
out.reserve(_points.size());
std::vector<qreal> window(static_cast<size_t>(_windowSize));
for (size_t i = 0, size = _points.size(); i < size; ++i)
{
if (_interrupt.load(std::memory_order_acquire))
return;
const auto next = i + 1;
if (next < windowSizeHalf)
{
int k = 0;
for (int j = static_cast<int>(next) - windowSizeHalf; k < _windowSize; ++j, ++k)
window[k] = _points[abs(j)].y();
}
else
{
int k = 0;
for (size_t j = next - windowSizeHalf; k < _windowSize; ++j, ++k)
window[k] = _points[j].y();
}
std::sort(window.begin(), window.end());
out.emplace_back(_points[i].x(), window[windowSizeHalf]);
}
_points = std::move(out);
}
void getChartPoints(const ArbitraryValuesCollection& _collection, Points& _points, qreal& _minValue, qreal& _maxValue)
{
_minValue = 1e300;
_maxValue = -1e300;
_points.clear();
const auto& values = _collection.values();
if (values.empty())
return;
_points.reserve(values.size());
for (auto value : values)
{
const qreal x = sceneX(value->begin());
const qreal y = profiler_gui::value2real(*value);
_points.emplace_back(x, y);
if (y > _maxValue)
_maxValue = y;
if (y < _minValue)
_minValue = y;
}
}
//////////////////////////////////////////////////////////////////////////
ArbitraryValuesCollection::ArbitraryValuesCollection()
: m_beginTime(0)
, m_minDuration(profiler_gui::numeric_max<decltype(m_minDuration)>())
, m_maxDuration(0)
, m_minValue(1e300)
, m_maxValue(-1e300)
, m_chartType(ChartType::Regular)
, m_jobType(0)
{
m_status = Idle;
m_bInterrupt = false;
}
ArbitraryValuesCollection::~ArbitraryValuesCollection()
{
interrupt();
}
ChartType ArbitraryValuesCollection::chartType() const
{
return m_chartType;
}
const ArbitraryValues& ArbitraryValuesCollection::values() const
{
return m_values;
}
const ComplexityValuesMap& ArbitraryValuesCollection::complexityMap() const
{
return m_complexityMap;
}
const Points& ArbitraryValuesCollection::points() const
{
return m_points;
}
ArbitraryValuesCollection::JobStatus ArbitraryValuesCollection::status() const
{
return static_cast<JobStatus>(m_status.load(std::memory_order_acquire));
}
profiler::timestamp_t ArbitraryValuesCollection::minDuration() const
{
return m_minDuration;
}
profiler::timestamp_t ArbitraryValuesCollection::maxDuration() const
{
return m_maxDuration;
}
qreal ArbitraryValuesCollection::minValue() const
{
return m_minValue;
}
qreal ArbitraryValuesCollection::maxValue() const
{
return m_maxValue;
}
void ArbitraryValuesCollection::collectValues(ChartType _chartType, profiler::thread_id_t _threadId, profiler::vin_t _valueId
, const char* _valueName, profiler::block_id_t _parentBlockId, int _index)
{
interrupt();
setStatus(InProgress);
m_points.clear();
m_chartType = _chartType;
m_jobType = ValuesJob;
if (m_chartType == ChartType::Complexity && profiler_gui::is_max(_parentBlockId))
{
setStatus(Ready);
return;
}
if (_valueId == 0)
m_worker.enqueue([=] { collectByName(_threadId, _valueName, _parentBlockId, _index); }, m_bInterrupt);
else
m_worker.enqueue([=] { collectById(_threadId, _valueId, _parentBlockId, _index); }, m_bInterrupt);
}
void ArbitraryValuesCollection::collectValuesAndPoints(ChartType _chartType, profiler::thread_id_t _threadId, profiler::vin_t _valueId
, const char* _valueName, profiler::timestamp_t _beginTime, profiler::block_id_t _parentBlockId, int _index)
{
interrupt();
setStatus(InProgress);
m_points.clear();
m_beginTime = _beginTime;
m_minValue = 1e300;
m_maxValue = -1e300;
m_chartType = _chartType;
m_jobType = ValuesJob | PointsJob;
if (m_chartType == ChartType::Complexity && profiler_gui::is_max(_parentBlockId))
{
setStatus(Ready);
return;
}
if (_valueId == 0)
m_worker.enqueue([=] { collectByName(_threadId, _valueName, _parentBlockId, _index); }, m_bInterrupt);
else
m_worker.enqueue([=] { collectById(_threadId, _valueId, _parentBlockId, _index); }, m_bInterrupt);
}
bool ArbitraryValuesCollection::calculatePoints(profiler::timestamp_t _beginTime)
{
if (status() != Ready || m_values.empty())
return false;
m_worker.dequeue();
setStatus(InProgress);
m_points.clear();
m_beginTime = _beginTime;
m_minValue = 1e300;
m_maxValue = -1e300;
m_jobType = PointsJob;
m_worker.enqueue([this]
{
getChartPoints(*this, m_points, m_minValue, m_maxValue);
setStatus(Ready);
}, m_bInterrupt);
return true;
}
void ArbitraryValuesCollection::interrupt()
{
m_worker.dequeue();
setStatus(Idle);
m_jobType = None;
m_values.clear();
m_complexityMap.clear();
profiler_gui::set_max(m_minDuration);
m_maxDuration = 0;
}
void ArbitraryValuesCollection::setStatus(JobStatus _status)
{
m_status.store(static_cast<uint8_t>(_status), std::memory_order_release);
}
void ArbitraryValuesCollection::collectById(profiler::thread_id_t _threadId, profiler::vin_t _valueId
, profiler::block_id_t _parentBlockId, int _index)
{
const bool doCalculatePoints = (m_jobType & PointsJob) != 0;
if (_threadId == 0)
{
const auto threadsCount = EASY_GLOBALS.profiler_blocks.size();
if (threadsCount != 0)
{
const bool calculatePointsInner = doCalculatePoints && threadsCount == 1;
for (const auto& it : EASY_GLOBALS.profiler_blocks)
{
if (!collectByIdForThread(it.second, _valueId, calculatePointsInner, _parentBlockId, _index))
return;
}
if (threadsCount > 1)
{
using Val = const profiler::ArbitraryValue*;
std::sort(m_values.begin(), m_values.end(), [] (Val lhs, Val rhs) {
return lhs->begin() < rhs->begin();
});
}
if (doCalculatePoints && !calculatePointsInner)
getChartPoints(*this, m_points, m_minValue, m_maxValue);
}
}
else
{
const auto t = EASY_GLOBALS.profiler_blocks.find(_threadId);
if (t != EASY_GLOBALS.profiler_blocks.end() &&
!collectByIdForThread(t->second, _valueId, doCalculatePoints, _parentBlockId, _index))
{
return;
}
}
if (m_chartType == ChartType::Complexity)
{
for (auto& it : m_complexityMap)
{
if (m_bInterrupt.load(std::memory_order_acquire))
return;
auto& durations = it.second;
std::sort(durations.begin(), durations.end());
}
}
setStatus(Ready);
}
bool ArbitraryValuesCollection::collectByIdForThread(const profiler::BlocksTreeRoot& _threadRoot
, profiler::vin_t _valueId, bool _calculatePoints, profiler::block_id_t _parentBlockId, int _index)
{
if (profiler_gui::is_max(_parentBlockId))
{
// All values
for (auto i : _threadRoot.events)
{
if (m_bInterrupt.load(std::memory_order_acquire))
return false;
const auto& block = easyBlock(i).tree;
const auto& desc = easyDescriptor(block.node->id());
if (desc.type() != profiler::BlockType::Value)
continue;
const auto value = block.value;
if (value->value_id() != _valueId)
continue;
if (_index >= 0 && (!value->isArray() || profiler_gui::valueArraySize(*value) <= _index))
continue;
m_values.push_back(value);
if (_calculatePoints)
addPoint(*value, _index);
}
return true;
}
return depthFirstSearch(_threadRoot, _calculatePoints, _parentBlockId, _index
, [=] (profiler::vin_t _id, const char*) -> bool { return _id == _valueId; });
}
void ArbitraryValuesCollection::collectByName(profiler::thread_id_t _threadId, const std::string _valueName
, profiler::block_id_t _parentBlockId, int _index)
{
const bool doCalculatePoints = (m_jobType & PointsJob) != 0;
if (_threadId == 0)
{
const auto threadsCount = EASY_GLOBALS.profiler_blocks.size();
if (threadsCount != 0)
{
const bool calculatePointsInner = doCalculatePoints && threadsCount == 1;
for (const auto& it : EASY_GLOBALS.profiler_blocks)
{
if (!collectByNameForThread(it.second, _valueName, calculatePointsInner, _parentBlockId, _index))
return;
}
if (threadsCount > 1)
{
using Val = const profiler::ArbitraryValue*;
std::sort(m_values.begin(), m_values.end(), [] (Val lhs, Val rhs) {
return lhs->begin() < rhs->begin();
});
}
if (doCalculatePoints && !calculatePointsInner)
getChartPoints(*this, m_points, m_minValue, m_maxValue);
}
}
else
{
const auto t = EASY_GLOBALS.profiler_blocks.find(_threadId);
if (t != EASY_GLOBALS.profiler_blocks.end() &&
!collectByNameForThread(t->second, _valueName, doCalculatePoints, _parentBlockId, _index))
{
return;
}
}
if (m_chartType == ChartType::Complexity)
{
for (auto& it : m_complexityMap)
{
if (m_bInterrupt.load(std::memory_order_acquire))
return;
auto& durations = it.second;
std::sort(durations.begin(), durations.end());
}
}
setStatus(Ready);
}
bool ArbitraryValuesCollection::collectByNameForThread(const profiler::BlocksTreeRoot& _threadRoot
, const std::string& _valueName, bool _calculatePoints, profiler::block_id_t _parentBlockId, int _index)
{
if (profiler_gui::is_max(_parentBlockId))
{
// All values
for (auto i : _threadRoot.events)
{
if (m_bInterrupt.load(std::memory_order_acquire))
return false;
const auto& block = easyBlock(i).tree;
const auto& desc = easyDescriptor(block.node->id());
if (desc.type() != profiler::BlockType::Value || _valueName != desc.name())
continue;
const auto value = block.value;
if (_index >= 0 && (!value->isArray() || profiler_gui::valueArraySize(*value) <= _index))
continue;
m_values.push_back(value);
if (_calculatePoints)
addPoint(*value, _index);
}
return true;
}
return depthFirstSearch(_threadRoot, _calculatePoints, _parentBlockId, _index
, [&_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, int _index, std::function<bool(profiler::vin_t, const char*)> _isSuitableValue)
{
if (_threadRoot.children.empty())
return true;
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& 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()))
{
if (_index < 0 || (value->isArray() && _index < profiler_gui::valueArraySize(*value)))
{
m_values.push_back(value);
if (_calculatePoints)
{
const auto val = addPoint(*value, _index);
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 (first < block.children.size())
{
if (block.node->id() == _parentBlockId || desc.id() == _parentBlockId)
{
if (!matchedParentId)
matchedParentIdStackDepth = stack.size();
matchedParentId = true;
lastMatchedParentDuration = block.node->duration();
}
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;
}
double ArbitraryValuesCollection::addPoint(const profiler::ArbitraryValue& _value, int _index)
{
const auto p = point(_value, _index);
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, int _index) const
{
const qreal x = PROF_MICROSECONDS(qreal(_value.begin() - m_beginTime));
const qreal y = profiler_gui::value2real(_value, std::max(_index, 0));
return {x, y};
}
//////////////////////////////////////////////////////////////////////////
ArbitraryValuesChartItem::ArbitraryValuesChartItem()
: Parent()
, m_workerMaxValue(0)
, m_workerMinValue(0)
, m_workerMaxDuration(0)
, m_workerMinDuration(0)
, m_maxDuration(0)
, m_minDuration(0)
, m_filterWindowSize(8)
, m_chartType(ChartType::Regular)
, m_filterType(FilterType::None)
{
}
ArbitraryValuesChartItem::~ArbitraryValuesChartItem()
{
}
void ArbitraryValuesChartItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*, QWidget*)
{
const auto widget = static_cast<const GraphicsSliderArea*>(scene()->parent());
const auto currentScale = widget->getWindowScale();
const bool bindMode = widget->bindMode();
const auto bottom = m_boundingRect.bottom();
const auto width = m_boundingRect.width() * currentScale;
_painter->save();
_painter->setTransform(QTransform::fromScale(1.0 / currentScale, 1), true);
const auto font_h = widget->fontHeight();
const auto drawImage = [=] {
if (!bindMode)
paintImage(_painter);
else
paintImage(_painter, currentScale, widget->minimum(), widget->maximum(), widget->value(), widget->sliderWidth());
};
if (!EASY_GLOBALS.scene.empty)
{
if (!profiler_gui::is_max(EASY_GLOBALS.selected_block) && m_chartType == ChartType::Regular)
{
const auto& block = easyBlocksTree(EASY_GLOBALS.selected_block);
qreal left = PROF_MICROSECONDS(qreal(block.node->begin() - EASY_GLOBALS.begin_time)) * currentScale;
qreal right = PROF_MICROSECONDS(qreal(block.node->end() - EASY_GLOBALS.begin_time)) * currentScale;
if (bindMode)
{
const auto scale = widget->range() / widget->sliderWidth();
const auto offset = widget->value() * currentScale;
left -= offset;
right -= offset;
left *= scale;
right *= scale;
}
const auto leftBorder = m_boundingRect.left() * currentScale;
const auto rightBorder = leftBorder + width - 2;
const auto l = estd::clamp(leftBorder, left, rightBorder);
const auto r = estd::clamp(leftBorder, right, rightBorder);
const auto sceneRect = scene()->sceneRect();
const auto w = r - l;
if (w > 1)
{
const auto color = (easyDescriptor(block.node->id()).color() & 0x00ffffff) | 0x30000000;
_painter->fillRect(QRectF(l, sceneRect.top() - 1, w, sceneRect.height() + 2), QColor::fromRgba(color));
}
drawImage();
_painter->setPen(QPen(QColor::fromRgba(0xb0000000), 2, Qt::DotLine));
_painter->drawLine(QLineF(l, sceneRect.top(), l, sceneRect.bottom()));
if (w > 1)
_painter->drawLine(QLineF(r, sceneRect.top(), r, sceneRect.bottom()));
}
else
{
drawImage();
}
const auto range = bindMode ? widget->sliderWidth() : widget->range();
paintMouseIndicator(_painter, m_boundingRect.top(), bottom, width,
m_boundingRect.height(), font_h, widget->value(), range);
}
else
{
drawImage();
}
// MODE
{
QRectF rect(3, m_boundingRect.top() - widget->margin(), width - 3, m_boundingRect.height() + widget->margins());
QRectF textBounds;
_painter->setPen(Qt::blue);
_painter->drawText(
rect,
Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip | Qt::TextIncludeTrailingSpaces,
QStringLiteral("MODE: "),
&textBounds
);
rect.adjust(textBounds.width(), 0, 0, 0);
_painter->setPen(profiler_gui::TEXT_COLOR);
_painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip, bindMode ? "Zoom" : "Overview");
}
_painter->setPen(Qt::darkGray);
_painter->drawLine(QLineF(0, bottom, width, bottom));
_painter->drawLine(QLineF(0, m_boundingRect.top(), width, m_boundingRect.top()));
_painter->restore();
}
void ArbitraryValuesChartItem::paintMouseIndicator(QPainter* _painter, qreal _top, qreal _bottom, qreal _width, qreal _height,
int _font_h, qreal _visibleRegionLeft, qreal _visibleRegionWidth)
{
if (_font_h == 0)
return;
const auto x = m_mousePos.x();
auto y = m_mousePos.y();
QString valueString;
// Horizontal
const bool visibleY = (_top < y && y < _bottom);
y = estd::clamp(_top, y, _bottom);
{
_height -= ChartBounds;
const int half_font_h = _font_h >> 1;
const auto yvalue = estd::clamp(_top + ChartBound, y, _bottom - ChartBound);
if (m_chartType == ChartType::Regular)
{
const auto value = m_minValue + ((_bottom - ChartBound - yvalue) / _height) * (m_maxValue - m_minValue);
valueString = QString::number(value, 'f', 3);
if (valueString.endsWith(QStringLiteral(".000")))
valueString.chop(4);
}
else
{
const auto value = m_minDuration +
static_cast<profiler::timestamp_t>(((_bottom - ChartBound - yvalue) / _height) * (m_maxDuration - m_minDuration));
valueString = profiler_gui::autoTimeStringRealNs(value, 3);
}
const int textWidth = _painter->fontMetrics().boundingRect(valueString).width() + 3;
const QRectF rect(0, y - _font_h - 2, _width - 3, 4 + (_font_h << 1));
_painter->setPen(Qt::blue);
qreal left = 0, right = _width - 3;
const Qt::AlignmentFlag alignment = x < textWidth ? Qt::AlignRight : Qt::AlignLeft;
if (y > _bottom - half_font_h)
{
_painter->drawText(rect, alignment | Qt::AlignTop, valueString);
}
else if (y < _top + half_font_h)
{
_painter->drawText(rect, alignment | Qt::AlignBottom, valueString);
}
else
{
_painter->drawText(rect, alignment | Qt::AlignVCenter, valueString);
if (x < textWidth)
right = _width - textWidth - 3;
else
left = textWidth;
}
if (visibleY)
_painter->drawLine(QLineF(left, y, right, y));
}
// Vertical
if (0 <= x && x <= _width)
{
if (m_chartType == ChartType::Regular)
{
const auto value = _visibleRegionLeft + _visibleRegionWidth * x / _width;
valueString = profiler_gui::timeStringReal(EASY_GLOBALS.time_units, value, 3);
}
else
{
const auto value = m_minValue + (m_maxValue - m_minValue) * x / _width;
valueString = QString::number(value, 'f', 3);
}
const int textWidth = _painter->fontMetrics().boundingRect(valueString).width() + 6;
const int textWidthHalf = textWidth >> 1;
qreal left = x - textWidthHalf;
if (x < textWidthHalf)
left = 0;
else if (x > (_width - textWidthHalf))
left = _width - textWidth;
const QRectF rect(left, _bottom + 2, textWidth, _font_h);
_painter->drawText(rect, Qt::AlignCenter, valueString);
_painter->drawLine(QLineF(x, _top, x, _bottom));
}
}
bool ArbitraryValuesChartItem::updateImage()
{
if (!Parent::updateImage())
return false;
const auto widget = static_cast<const GraphicsSliderArea*>(scene()->parent());
m_imageScaleUpdate = widget->range() / widget->sliderWidth();
m_imageOriginUpdate = widget->bindMode() ? (widget->value() - widget->sliderWidth() * 3) : widget->minimum();
// Ugly, but doen't use exceeded count of threads
const auto rect = m_boundingRect;
const auto scale = widget->getWindowScale();
const auto left = widget->minimum();
const auto right = widget->maximum();
const auto value = widget->value();
const auto window = widget->sliderWidth();
const auto bindMode = widget->bindMode();
const auto beginTime = EASY_GLOBALS.begin_time;
const auto autoHeight = EASY_GLOBALS.auto_adjust_chart_height;
if (m_chartType == ChartType::Regular)
{
m_worker.enqueue([=]
{
updateRegularImageAsync(rect, scale, left, right, right - left, value, window, bindMode, beginTime,
autoHeight);
}, m_bReady);
}
else
{
m_worker.enqueue([=]
{
updateComplexityImageAsync(rect, scale, left, right, right - left, value, window, bindMode, beginTime,
autoHeight);
}, m_bReady);
}
return true;
}
void ArbitraryValuesChartItem::onImageUpdated()
{
m_maxValue = m_workerMaxValue;
m_minValue = m_workerMinValue;
m_maxDuration = m_workerMaxDuration;
m_minDuration = m_workerMinDuration;
}
void ArbitraryValuesChartItem::drawGrid(QPainter& _painter, int _width, int _height) const
{
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 maxColumnHeight = _boundingRect.height();
const auto viewScale = _range / _width;
if (_bindMode)
{
m_workerImageScale = viewScale;
m_workerImageOrigin = _value - _width * 3;
m_workerImage = new QImage(screenWidth * 7 + 0.5, _boundingRect.height(), QImage::Format_ARGB32);
}
else
{
m_workerImageScale = 1;
m_workerImageOrigin = _minimum;
m_workerImage = new QImage(screenWidth + 0.5, _boundingRect.height(), 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;
LeftBounds leftBounds;
leftBounds.reserve(m_collections.size());
if (_bindMode)
{
_minimum = m_workerImageOrigin;
_maximum = m_workerImageOrigin + _width * 7;
realScale *= viewScale;
offset = _minimum * realScale;
}
const auto right = std::min(_value + _width, _maximum);
qreal minValue = 1e300, maxValue = -1e300;
for (const auto& c : m_collections)
{
if (isReady())
return;
const auto& collection = *c.ptr;
const auto& points = collection.points();
if (points.empty())
{
leftBounds.emplace_back(points.end());
continue;
}
if (_bindMode)
{
auto first = std::lower_bound(points.begin(), points.end(), _minimum, [](const QPointF& point, qreal x)
{
return point.x() < x;
});
if (first != points.end())
{
if (first != points.begin())
--first;
}
else
{
first = points.begin() + points.size() - 1;
}
leftBounds.emplace_back(first);
if (_autoAdjust)
{
for (auto it = first; it != points.end() && it->x() < right && !isReady(); ++it)
{
if (it->x() < _value)
continue;
const auto value = it->y();
minValue = std::min(minValue, value);
maxValue = std::max(maxValue, value);
}
continue;
}
}
else
{
leftBounds.emplace_back(points.begin());
}
minValue = std::min(minValue, collection.minValue());
maxValue = std::max(maxValue, collection.maxValue());
}
if (minValue > maxValue)
{
// No points
m_workerMinValue = 0;
m_workerMaxValue = 0;
setReady(true);
return;
}
m_workerMinValue = minValue;
m_workerMaxValue = maxValue;
if (isReady())
return;
const bool singleValue = fabs(maxValue - minValue) < 2 * std::numeric_limits<qreal>::epsilon();
const auto middle = _boundingRect.height() * 0.5;
const qreal height = std::max(maxValue - minValue, 0.01);
const auto gety = [&_boundingRect, maxValue, height, singleValue, middle] (qreal y)
{
if (singleValue)
{
y = middle;
}
else
{
y = maxValue - y;
y /= height;
y *= _boundingRect.height() - ChartBounds;
y += ChartBound;
}
return y;
};
size_t i = 0;
for (const auto& c : m_collections)
{
if (isReady())
return;
const auto& points = c.ptr->points();
if (points.empty())
{
++i;
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));
}
const auto first = leftBounds[i];
if (c.chartPenStyle == ChartPenStyle::Points)
{
qreal prevX = 1e300, prevY = 1e300;
for (auto it = first; it != points.end() && it->x() < _maximum; ++it)
{
if (it->x() < _minimum)
continue;
if (isReady())
return;
const qreal x = it->x() * realScale - offset;
const qreal y = gety(it->y());
const auto dx = fabs(x - prevX), dy = fabs(y - prevY);
if (dx > 1 || dy > 1)
{
p.drawPoint(QPointF{x, y});
prevX = x;
prevY = y;
}
}
}
else if (first != points.end() && first->x() < _maximum)
{
QPointF p1 = *first;
qreal x = p1.x() * realScale - offset;
qreal y = gety(p1.y());
p1.setX(x);
p1.setY(y);
auto it = first;
for (++it; it != points.end(); ++it)
{
if (isReady())
return;
QPointF p2 = *it;
x = p2.x() * realScale - offset;
y = gety(p2.y());
p2.setX(x);
p2.setY(y);
if (it->x() >= _minimum)
{
const auto dx = fabs(x - p1.x()), dy = fabs(y - p1.y());
if (dx > 1 || dy > 1)
p.drawLine(p1, p2);
else
continue;
}
if (it->x() >= _maximum)
break;
p1 = p2;
}
}
if (c.selected)
{
auto color = profiler_gui::darken(c.color, 0.65f);
if (profiler_gui::alpha(color) < 0xc0)
p.setPen(QColor::fromRgba(profiler::colors::modify_alpha32(color, 0xc0000000)));
else
p.setPen(QColor::fromRgba(color));
p.setBrush(QColor::fromRgba(0xc8ffffff));
qreal prevX = -offset * 2, prevY = -500;
for (auto it = first; it != points.end() && it->x() < _maximum; ++it)
{
if (it->x() < _minimum)
continue;
if (isReady())
return;
const qreal x = it->x() * realScale - offset;
const qreal y = gety(it->y());
const auto dx = x - prevX, dy = y - prevY;
const auto delta = estd::sqr(dx) + estd::sqr(dy);
if (delta > 25)
{
p.drawEllipse(QPointF {x, y}, 3, 3);
prevX = x;
prevY = y;
}
}
}
++i;
}
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 globalSceneLeft = _minimum;
//const auto maxColumnHeight = _boundingRect.height();
if (_bindMode)
{
m_workerImageScale = _range / _width;
m_workerImageOrigin = _value - _width * 3;
m_workerImage = new QImage(screenWidth * 7 + 0.5, rectHeight, QImage::Format_ARGB32);
}
else
{
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;
}
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;
}
using LeftBounds = std::vector<ComplexityValuesMap::const_iterator>;
qreal left = minValue, right = maxValue;
LeftBounds leftBounds;
leftBounds.reserve(m_collections.size());
if (_bindMode)
{
const auto valueRange = maxValue - minValue;
_minimum = m_workerImageOrigin;
_maximum = m_workerImageOrigin + _width * 7;
left = minValue + valueRange * (_minimum - globalSceneLeft) / _range;
right = minValue + valueRange * (_maximum - globalSceneLeft) / _range;
if (_autoAdjust)
{
minDuration = profiler_gui::numeric_max<profiler::timestamp_t>();
maxDuration = 0;
}
maxValue = minValue + valueRange * (_value + _width - globalSceneLeft) / _range;
minValue += valueRange * (_value - globalSceneLeft) / _range;
}
for (const auto& c : m_collections)
{
if (isReady())
return;
const auto& complexityMap = c.ptr->complexityMap();
if (complexityMap.empty())
{
leftBounds.emplace_back(complexityMap.end());
continue;
}
if (!_bindMode)
{
leftBounds.emplace_back(complexityMap.begin());
continue;
}
auto first = complexityMap.lower_bound(left);
if (first != complexityMap.end())
{
if (first != complexityMap.begin())
--first;
}
else
{
auto last = complexityMap.rbegin();
first = (++last).base();
}
leftBounds.emplace_back(first);
if (_autoAdjust)
{
for (auto it = first; it != complexityMap.end() && it->first < maxValue && !isReady(); ++it)
{
const auto value = it->first;
if (value < minValue || it->second.empty())
continue;
minDuration = std::min(minDuration, it->second.front());
maxDuration = std::max(maxDuration, it->second.back());
}
}
}
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 qreal imageWidth = m_workerImage->width();
const auto xmiddle = imageWidth * 0.5;
singleValue = fabs(right - left) < 2 * std::numeric_limits<qreal>::epsilon();
const qreal width = std::max(right - left, 0.01);
const auto getx = [=] (qreal x)
{
if (singleValue)
{
x = xmiddle;
}
else
{
x -= left;
x *= imageWidth / width;
}
return x;
};
std::vector<QPointF> averages;
size_t i = 0;
for (const auto& c : m_collections)
{
if (isReady())
return;
const auto& complexityMap = c.ptr->complexityMap();
if (complexityMap.empty())
{
++i;
continue;
}
const auto first = leftBounds[i++];
if (first == complexityMap.end() || first->first > right)
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));
}
const bool drawApproximateLine = c.selected || m_collections.size() == 1;
averages.clear();
if (drawApproximateLine)
averages.reserve(complexityMap.size());
auto it = first;
while (it != complexityMap.end() && it->first < left)
++it;
if (it == complexityMap.end() || it->first > right)
continue;
qreal x = getx(it->first);
profiler::timestamp_t average = 0;
for (auto duration : it->second)
{
average += duration;
p.drawPoint(QPointF {x, gety(duration)});
}
if (drawApproximateLine)
{
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)});
}
if (drawApproximateLine)
{
average /= it->second.size();
averages.emplace_back(x, gety(average));
}
if (it->first > right)
break;
}
if (drawApproximateLine)
{
// drawPolyLine() with 2 pixel width is VERY slow! Do not use it!
//p.drawPolyline(averages.data(), static_cast<int>(averages.size()));
// Draw polyline
{
QPointF p1 = averages.front();
auto averages_it = averages.begin();
for (++averages_it; averages_it != averages.end(); ++averages_it)
{
if (isReady())
return;
const QPointF& p2 = *averages_it;
const auto dx = fabs(p2.x() - p1.x()), dy = fabs(p2.y() - p1.y());
if (dx > 1 || dy > 1)
p.drawLine(p1, p2);
else
continue;
p1 = p2;
}
}
auto color = profiler_gui::darken(c.color, 0.65f);
if (profiler_gui::alpha(color) < 0xc0)
p.setPen(QColor::fromRgba(profiler::colors::modify_alpha32(color, 0xc0000000)));
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);
switch (m_filterType)
{
case FilterType::Median:
medianFilter(averages, m_filterWindowSize, m_bReady);
break;
case FilterType::Gauss:
gaussFilter(averages, m_filterWindowSize, m_bReady);
break;
default:
break;
}
if (isReady())
return;
QPainterPath pp;
pp.moveTo(averages.front());
const auto last = averages.size() - 1;
size_t last_k = 0;
size_t step = std::max(averages.size() / (size_t)40, (size_t)1);
const auto dubstep = step << 1; // lol :P
const auto iteration_step = 3 * step;
for (size_t k = iteration_step; k < averages.size(); k += iteration_step)
{
if (isReady())
return;
last_k = k;
pp.cubicTo(averages[k - dubstep], averages[k - step], averages[k]);
}
const auto rest = last - last_k;
if (rest < iteration_step)
{
if ((rest % 4) == 0)
{
step = rest / 4;
pp.cubicTo(averages[last - (step << 1)], averages[last - step], averages[last]);
}
else
{
step = std::max(rest / (size_t)4, (size_t)1);
const auto k1 = std::max(last - step, last_k);
const auto k0 = std::max(k1 - step, last_k);
pp.cubicTo(averages[k0], averages[k1], averages[last]);
}
}
p.drawPath(pp);
}
}
setReady(true);
}
void ArbitraryValuesChartItem::clear()
{
cancelAnyJob();
m_boundaryTimer.stop();
m_collections.clear();
m_minValue = m_maxValue = 0;
m_minDuration = m_maxDuration = 0;
setEmpty(true);
}
void ArbitraryValuesChartItem::update(Collections _collections)
{
cancelImageUpdate();
m_collections = std::move(_collections);
setEmpty(m_collections.empty());
updateImage();
}
void ArbitraryValuesChartItem::update(const ArbitraryValuesCollection* _selected)
{
cancelImageUpdate();
for (auto& collection : m_collections)
collection.selected = collection.ptr == _selected;
updateImage();
}
void ArbitraryValuesChartItem::setChartType(ChartType _chartType)
{
if (m_chartType == _chartType)
return;
cancelImageUpdate();
m_chartType = _chartType;
updateImage();
}
void ArbitraryValuesChartItem::setFilterType(FilterType _filterType)
{
if (m_filterType == _filterType)
return;
if (m_chartType == ChartType::Regular)
{
m_filterType = _filterType;
return;
}
cancelImageUpdate();
m_filterType = _filterType;
updateImage();
}
void ArbitraryValuesChartItem::setFilterWindowSize(int _size)
{
if (m_filterWindowSize == _size)
return;
if (m_chartType == ChartType::Regular || m_filterType == FilterType::None)
{
m_filterWindowSize = _size;
return;
}
cancelImageUpdate();
m_filterWindowSize = _size;
updateImage();
}
ChartType ArbitraryValuesChartItem::chartType() const
{
return m_chartType;
}
FilterType ArbitraryValuesChartItem::filterType() const
{
return m_filterType;
}
int ArbitraryValuesChartItem::filterWindowSize() const
{
return m_filterWindowSize;
}
//////////////////////////////////////////////////////////////////////////
GraphicsChart::GraphicsChart(QWidget* _parent)
: Parent(_parent)
, m_chartItem(new ArbitraryValuesChartItem())
{
m_imageItem = m_chartItem;
scene()->addItem(m_chartItem);
const auto rect = scene()->sceneRect();
m_chartItem->setPos(0, 0);
m_chartItem->setBoundingRect(0, rect.top() + margin(), scene()->width(), rect.height() - margins() - 1);
connect(&EASY_GLOBALS.events, &profiler_gui::GlobalSignals::autoAdjustChartChanged, this, &This::onAutoAdjustChartChanged);
m_chartItem->updateImage();
}
GraphicsChart::~GraphicsChart()
{
}
void GraphicsChart::onAutoAdjustChartChanged()
{
if (m_chartItem->isVisible())
m_chartItem->onModeChanged();
}
void GraphicsChart::clear()
{
cancelImageUpdate();
Parent::clear();
}
void GraphicsChart::cancelImageUpdate()
{
m_chartItem->clear();
}
void GraphicsChart::update(Collections _collections)
{
m_chartItem->update(std::move(_collections));
}
void GraphicsChart::update(const ArbitraryValuesCollection* _selected)
{
m_chartItem->update(_selected);
}
void GraphicsChart::setChartType(ChartType _chartType)
{
m_chartItem->setChartType(_chartType);
m_slider->setVisible(_chartType != ChartType::Complexity && !m_bBindMode);
}
void GraphicsChart::setFilterType(FilterType _filterType)
{
m_chartItem->setFilterType(_filterType);
}
void GraphicsChart::setFilterWindowSize(int _size)
{
m_chartItem->setFilterWindowSize(_size);
}
ChartType GraphicsChart::chartType() const
{
return m_chartItem->chartType();
}
FilterType GraphicsChart::filterType() const
{
return m_chartItem->filterType();
}
int GraphicsChart::filterWindowSize() const
{
return m_chartItem->filterWindowSize();
}
bool GraphicsChart::canShowSlider() const
{
return chartType() != ChartType::Complexity && !bindMode();
}
//////////////////////////////////////////////////////////////////////////
enum class ArbitraryColumns : uint8_t
{
Name = 0,
Type,
Value,
Vin,
Count
};
EASY_CONSTEXPR auto CheckColumn = int_cast(ArbitraryColumns::Name);
EASY_CONSTEXPR auto StdItemType = QTreeWidgetItem::UserType;
EASY_CONSTEXPR auto ValueItemType = QTreeWidgetItem::UserType + 1;
struct UsedValueTypes {
ArbitraryTreeWidgetItem* items[int_cast(profiler::DataType::TypesCount)];
UsedValueTypes(int = 0) { memset(items, 0, sizeof(items)); }
};
//////////////////////////////////////////////////////////////////////////
ArbitraryTreeWidgetItem::ArbitraryTreeWidgetItem(QTreeWidgetItem* _parent, bool _checkable, profiler::color_t _color, const profiler::ArbitraryValue& _value)
: Parent(_parent, ValueItemType)
, m_value(_value)
, m_color(_color)
, m_widthHint(0)
{
if (_checkable)
{
setFlags(flags() | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable);
setCheckState(CheckColumn, Qt::Unchecked);
}
else
{
setFlags(flags() & ~Qt::ItemIsUserCheckable);
}
}
ArbitraryTreeWidgetItem::~ArbitraryTreeWidgetItem()
{
interrupt();
}
QVariant ArbitraryTreeWidgetItem::data(int _column, int _role) const
{
if (_column == CheckColumn && _role == Qt::SizeHintRole)
return QSize(static_cast<int>(m_widthHint * (m_font.bold() ? 1.2f : 1.f)), 26);
if (_role == Qt::FontRole)
return m_font;
if (_column == int_cast(ArbitraryColumns::Vin) && _role == Qt::DisplayRole && getSelfIndexInArray() < 0)
return QString("0x%1").arg(m_value.value_id(), 0, 16);
return Parent::data(_column, _role);
}
const profiler::ArbitraryValue& ArbitraryTreeWidgetItem::value() const
{
return m_value;
}
void ArbitraryTreeWidgetItem::setWidthHint(int _width)
{
m_widthHint = _width;
}
void ArbitraryTreeWidgetItem::setBold(bool _isBold)
{
m_font.setBold(_isBold);
}
const ArbitraryValuesCollection* ArbitraryTreeWidgetItem::collection() const
{
return m_collection.get();
}
ArbitraryValuesCollection* ArbitraryTreeWidgetItem::collection()
{
return m_collection.get();
}
bool ArbitraryTreeWidgetItem::isArrayItem() const
{
return childCount() != 0;
}
profiler::block_id_t ArbitraryTreeWidgetItem::getParentBlockId(QTreeWidgetItem* _item) const
{
auto parentItem = _item->parent();
const auto parentRole = parentItem->data(int_cast(ArbitraryColumns::Type), Qt::UserRole).toInt();
switch (parentRole)
{
case 1:
return parentItem->data(int_cast(ArbitraryColumns::Vin), Qt::UserRole).toUInt();
case 2:
return getParentBlockId(parentItem);
default:
return EASY_GLOBALS.selected_block_id;
}
}
int ArbitraryTreeWidgetItem::getSelfIndexInArray() const
{
if (data(int_cast(ArbitraryColumns::Type), Qt::UserRole).toInt() != 3)
return -1;
return parent()->indexOfChild(const_cast<ArbitraryTreeWidgetItem*>(this));
}
void ArbitraryTreeWidgetItem::collectValues(profiler::thread_id_t _threadId, ChartType _chartType)
{
if (!m_collection)
m_collection = CollectionPtr(new ArbitraryValuesCollection);
else
m_collection->interrupt();
const auto parentBlockId = getParentBlockId(this);
const int index = getSelfIndexInArray();
EASY_CONSTEXPR auto nameColumn = int_cast(ArbitraryColumns::Name);
const auto name = index < 0 ? text(nameColumn).toStdString() : parent()->text(nameColumn).toStdString();
m_collection->collectValuesAndPoints(_chartType, _threadId, m_value.value_id(), name.c_str(),
EASY_GLOBALS.begin_time, parentBlockId, index);
}
void ArbitraryTreeWidgetItem::interrupt()
{
if (!m_collection)
return;
m_collection->interrupt();
m_collection.release();
}
profiler::color_t ArbitraryTreeWidgetItem::color() const
{
return m_color;
}
//////////////////////////////////////////////////////////////////////////
ArbitraryValuesWidget::ArbitraryValuesWidget(bool _isMainWidget, profiler::thread_id_t _threadId
, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId, QWidget* _parent)
: Parent(_parent)
, m_splitter(new QSplitter(Qt::Horizontal, this))
, m_treeWidget(new QTreeWidget(this))
, m_chart(new GraphicsChart(this))
, m_filterBoxLabel(new QLabel(tr(" Filter:"), this))
, m_filterComboBox(new QComboBox(this))
, m_filterWindowLabel(new QLabel(tr(" Window size:"), this))
, m_filterWindowPicker(new QSpinBox(this))
, m_exportToCsvAction(nullptr)
, m_boldItem(nullptr)
, m_threadId(_threadId)
, m_blockIndex(_blockIndex)
, m_blockId(_blockId)
, m_bInitialized(false)
, m_bMainWidget(_isMainWidget)
{
m_collectionsTimer.setInterval(100);
m_splitter->setHandleWidth(1);
m_splitter->setContentsMargins(0, 0, 0, 0);
m_splitter->addWidget(m_treeWidget);
m_splitter->addWidget(m_chart);
m_splitter->setStretchFactor(0, 1);
m_splitter->setStretchFactor(1, 1);
m_filterWindowPicker->setRange(3, 50);
m_filterWindowPicker->setSingleStep(1);
m_filterWindowPicker->setValue(8);
m_filterComboBox->addItem(tr("None"));
m_filterComboBox->addItem(tr("Gauss"));
m_filterComboBox->addItem(tr("Median"));
m_filterComboBox->setCurrentIndex(0);
auto tb = new QToolBar(this);
tb->setIconSize(applicationIconsSize());
auto action = tb->addAction(QIcon(imagePath("reload")), tr("Refresh values list"));
connect(action, &QAction::triggered, this, Overload<void>::of(&This::rebuild));
action = tb->addAction(QIcon(imagePath("window")), tr("Open new window"));
connect(action, &QAction::triggered, this, &This::onOpenInNewWindowClicked);
m_exportToCsvAction = tb->addAction(QIcon(imagePath("csv")), tr("Export to csv"));
connect(m_exportToCsvAction, &QAction::triggered, this, &This::onExportToCsvClicked);
tb->addSeparator();
auto actionGroup = new QActionGroup(this);
actionGroup->setExclusive(true);
auto actionRegulatChart = new QAction(QIcon(imagePath("yx-chart")), tr("Regular chart [ v(t) ]"), actionGroup);
actionRegulatChart->setCheckable(true);
actionRegulatChart->setChecked(true);
tb->addAction(actionRegulatChart);
auto actionComplexityChart = new QAction(QIcon(imagePath("big-o-chart")), tr("Complexity chart [ t(v) ]"), actionGroup);
actionComplexityChart->setCheckable(true);
tb->addAction(actionComplexityChart);
auto filtersWidget = new QWidget(this);
auto filtersLay = new QHBoxLayout(filtersWidget);
filtersLay->setContentsMargins(0, 0, 0, 0);
filtersLay->addWidget(m_filterBoxLabel);
filtersLay->addWidget(m_filterComboBox);
filtersLay->addWidget(m_filterWindowLabel);
filtersLay->addWidget(m_filterWindowPicker);
tb->addWidget(filtersWidget);
auto layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(tb);
layout->addWidget(m_splitter);
m_treeWidget->setAutoFillBackground(false);
m_treeWidget->setAlternatingRowColors(true);
m_treeWidget->setItemsExpandable(true);
m_treeWidget->setAnimated(true);
//m_treeWidget->setSortingEnabled(false);
m_treeWidget->setColumnCount(int_cast(ArbitraryColumns::Count));
m_treeWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
auto headerItem = new QTreeWidgetItem();
headerItem->setText(int_cast(ArbitraryColumns::Type), "Type");
headerItem->setText(int_cast(ArbitraryColumns::Name), "Name");
headerItem->setText(int_cast(ArbitraryColumns::Value), "Value");
headerItem->setText(int_cast(ArbitraryColumns::Vin), "ID");
m_treeWidget->setHeaderItem(headerItem);
connect(&m_collectionsTimer, &QTimer::timeout, this, &This::onCollectionsTimeout);
using profiler_gui::GlobalSignals;
auto globalEvents = &EASY_GLOBALS.events;
connect(globalEvents, &GlobalSignals::selectedBlockChanged, this, &This::onSelectedBlockChanged);
connect(globalEvents, &GlobalSignals::selectedBlockIdChanged, this, &This::onSelectedBlockIdChanged);
connect(globalEvents, &GlobalSignals::allDataGoingToBeDeleted, this, &This::clear);
connect(m_treeWidget->header(), &QHeaderView::sectionResized, this, &This::onHeaderSectionResized);
if (_isMainWidget)
{
connect(m_treeWidget, &QTreeWidget::itemDoubleClicked, this, &This::onItemDoubleClicked);
connect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
connect(m_treeWidget, &QTreeWidget::currentItemChanged, this, &This::onCurrentItemChanged);
connect(globalEvents, &GlobalSignals::fileOpened, this, Overload<void>::of(&This::rebuild));
connect(globalEvents, &GlobalSignals::selectedThreadChanged, this, &This::onSelectedThreadChanged);
}
loadSettings();
m_filterComboBox->setCurrentIndex(int_cast(m_chart->filterType()));
m_filterWindowPicker->setValue(m_chart->filterWindowSize());
m_filterBoxLabel->setVisible(m_chart->chartType() == ChartType::Complexity);
m_filterComboBox->setVisible(m_chart->chartType() == ChartType::Complexity);
m_filterWindowLabel->setVisible(m_chart->chartType() == ChartType::Complexity && m_chart->filterType() != FilterType::None);
m_filterWindowPicker->setVisible(m_chart->chartType() == ChartType::Complexity && m_chart->filterType() != FilterType::None);
if (m_chart->chartType() == ChartType::Complexity)
actionComplexityChart->setChecked(true);
else
actionRegulatChart->setChecked(true);
connect(actionRegulatChart, &QAction::triggered, this, &This::onRegularChartTypeChecked);
connect(actionComplexityChart, &QAction::triggered, this, &This::onComplexityChartTypeChecked);
connect(m_filterComboBox, Overload<int>::of(&QComboBox::currentIndexChanged), this, &This::onFilterComboBoxChanged);
connect(m_filterWindowPicker, Overload<int>::of(&QSpinBox::valueChanged), this, &This::onFilterWindowSizeChanged);
rebuild(_threadId, _blockIndex, _blockId);
}
ArbitraryValuesWidget::ArbitraryValuesWidget(QWidget* _parent)
: ArbitraryValuesWidget(true, EASY_GLOBALS.selected_thread, EASY_GLOBALS.selected_block, EASY_GLOBALS.selected_block_id, _parent)
{
m_exportToCsvAction->setEnabled(false);
}
void ArbitraryValuesWidget::showEvent(QShowEvent* event)
{
Parent::showEvent(event);
if (!m_bInitialized)
{
m_columnsMinimumWidth.resize(static_cast<size_t>(ArbitraryColumns::Count), 0);
#if !defined(_WIN32) && !defined(__APPLE__)
const auto padding = px(9);
#else
const auto padding = px(6);
#endif
auto header = m_treeWidget->header();
auto headerItem = m_treeWidget->headerItem();
auto f = header->font();
#if !defined(_WIN32) && !defined(__APPLE__)
f.setBold(true);
#endif
QFontMetrics fm(f);
const auto indicatorSize = header->isSortIndicatorShown() ? px(11) : 0;
for (int i = 0; i < static_cast<int>(m_columnsMinimumWidth.size()); ++i)
{
auto minSize = static_cast<int>(fm.boundingRect(headerItem->text(i)).width() * profiler_gui::FONT_METRICS_FACTOR + padding);
m_columnsMinimumWidth[i] = minSize;
if (header->isSortIndicatorShown() && header->sortIndicatorSection() == i)
{
minSize += indicatorSize;
}
if (header->sectionSize(i) < minSize)
{
header->resizeSection(i, minSize);
}
}
m_bInitialized = true;
}
}
void ArbitraryValuesWidget::onHeaderSectionResized(int logicalIndex, int /*oldSize*/, int newSize)
{
if (logicalIndex >= m_columnsMinimumWidth.size())
{
return;
}
auto header = m_treeWidget->header();
const auto indicatorSize = header->isSortIndicatorShown() && header->sortIndicatorSection() == logicalIndex ? px(11) : 0;
const auto minSize = m_columnsMinimumWidth[logicalIndex] + indicatorSize;
if (!m_bInitialized || newSize >= minSize)
{
return;
}
header->resizeSection(logicalIndex, minSize);
}
ArbitraryTreeWidgetItem* findSimilarItem(QTreeWidgetItem* _parentItem, ArbitraryTreeWidgetItem* _item)
{
const auto index = _item->getSelfIndexInArray();
for (int c = 0, childrenCount = _parentItem->childCount(); c < childrenCount; ++c)
{
auto child = _parentItem->child(c);
if (child->type() == ValueItemType)
{
auto item = reinterpret_cast<ArbitraryTreeWidgetItem*>(child);
if (item->getSelfIndexInArray() == index)
{
if (&_item->value() == &item->value())
{
return item;
}
else if (_item->value().value_id() == item->value().value_id() &&
_item->value().type() == item->value().type() &&
_item->value().isArray() == item->value().isArray() &&
_item->text(int_cast(ArbitraryColumns::Name)) == item->text(int_cast(ArbitraryColumns::Name)))
{
return item;
}
}
}
auto item = findSimilarItem(child, _item);
if (item != nullptr)
return item;
}
return nullptr;
}
ArbitraryValuesWidget::ArbitraryValuesWidget(const QList<ArbitraryTreeWidgetItem*>& _checkedItems
, QTreeWidgetItem* _currentItem, profiler::thread_id_t _threadId, profiler::block_index_t _blockIndex
, profiler::block_id_t _blockId, QWidget* _parent)
: ArbitraryValuesWidget(false, _threadId, _blockIndex, _blockId, _parent)
{
for (auto item : _checkedItems)
{
for (int i = 0, topLevelItemsCount = m_treeWidget->topLevelItemCount(); i < topLevelItemsCount; ++i)
{
auto foundItem = findSimilarItem(m_treeWidget->topLevelItem(i), item);
if (foundItem != nullptr)
{
const auto checkState = item->checkState(CheckColumn);
foundItem->setCheckState(CheckColumn, checkState);
if (checkState == Qt::Checked)
{
m_checkedItems.push_back(foundItem);
foundItem->collectValues(m_threadId, m_chart->chartType());
}
}
}
}
if (!m_checkedItems.empty())
{
m_exportToCsvAction->setEnabled(true);
m_collectionsTimer.start();
std::set<QTreeWidgetItem*> checked;
for (auto item : m_checkedItems)
{
if (item->getSelfIndexInArray() >= 0)
{
auto parentItem = item->parent();
if (checked.find(parentItem) != checked.end())
continue;
checked.insert(parentItem);
Qt::CheckState newState = Qt::Checked;
for (int i = 0, childCount = parentItem->childCount(); i < childCount; ++i)
{
auto child = parentItem->child(i);
if (child->checkState(CheckColumn) != Qt::Checked)
{
newState = Qt::PartiallyChecked;
break;
}
}
parentItem->setCheckState(CheckColumn, newState);
}
}
}
else
{
m_exportToCsvAction->setEnabled(false);
}
if (_currentItem != nullptr)
{
if (_currentItem->type() == ValueItemType)
{
auto item = reinterpret_cast<ArbitraryTreeWidgetItem*>(_currentItem);
for (int i = 0, topLevelItemsCount = m_treeWidget->topLevelItemCount(); i < topLevelItemsCount; ++i)
{
auto foundItem = findSimilarItem(m_treeWidget->topLevelItem(i), item);
if (foundItem != nullptr)
{
m_treeWidget->setCurrentItem(foundItem);
break;
}
}
}
else
{
const int col = static_cast<int>(ArbitraryColumns::Name);
auto items = m_treeWidget->findItems(_currentItem->text(col), Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive, col);
if (!items.empty())
m_treeWidget->setCurrentItem(items.front());
}
}
connect(m_treeWidget, &QTreeWidget::itemDoubleClicked, this, &This::onItemDoubleClicked);
connect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
connect(m_treeWidget, &QTreeWidget::currentItemChanged, this, &This::onCurrentItemChanged);
}
ArbitraryValuesWidget::~ArbitraryValuesWidget()
{
saveSettings();
}
void ArbitraryValuesWidget::clear()
{
if (m_collectionsTimer.isActive())
m_collectionsTimer.stop();
// Warning! Cancel image update first because it uses POINTERS which will be destroyed
// in m_treeWidget->clear()
m_chart->cancelImageUpdate();
m_exportToCsvAction->setEnabled(false);
m_checkedItems.clear();
m_treeWidget->clear();
m_boldItem = nullptr;
}
void ArbitraryValuesWidget::onSelectedThreadChanged(profiler::thread_id_t)
{
//rebuild();
}
void ArbitraryValuesWidget::onSelectedBlockChanged(uint32_t)
{
m_chart->scene()->update();
if (profiler_gui::is_max(EASY_GLOBALS.selected_block))
{
onSelectedBlockIdChanged(EASY_GLOBALS.selected_block_id);
return;
}
// TODO: find item corresponding to selected_block and make it bold
}
void ArbitraryValuesWidget::onSelectedBlockIdChanged(::profiler::block_id_t)
{
if (!profiler_gui::is_max(EASY_GLOBALS.selected_block))
return;
if (profiler_gui::is_max(EASY_GLOBALS.selected_block_id))
{
if (m_boldItem != nullptr)
{
m_boldItem->setBold(false);
m_boldItem = nullptr;
}
return;
}
// TODO: find item corresponding to selected_block_id and make it bold
}
void ArbitraryValuesWidget::onItemDoubleClicked(QTreeWidgetItem* _item, int)
{
if (_item == nullptr || _item->type() != ValueItemType || (_item->flags() & Qt::ItemIsUserCheckable) == 0)
return;
_item->setCheckState(CheckColumn, _item->checkState(CheckColumn) != Qt::Unchecked ? Qt::Unchecked : Qt::Checked);
}
void ArbitraryValuesWidget::onItemChanged(QTreeWidgetItem* _item, int _column)
{
if (_item == nullptr || _item->type() != ValueItemType || _column != CheckColumn)
return;
if (_item->checkState(CheckColumn) == Qt::PartiallyChecked)
return;
auto item = static_cast<ArbitraryTreeWidgetItem*>(_item);
if (item->checkState(CheckColumn) == Qt::Checked)
{
m_exportToCsvAction->setEnabled(true);
const auto prevSize = m_checkedItems.size();
if (!item->isArrayItem())
{
m_checkedItems.push_back(item);
item->collectValues(EASY_GLOBALS.selected_thread, m_chart->chartType());
if (item->getSelfIndexInArray() >= 0)
{
Qt::CheckState newState = Qt::Checked;
auto parentItem = item->parent();
for (int i = 0; i < parentItem->childCount(); ++i)
{
auto child = parentItem->child(i);
if (child->checkState(CheckColumn) != Qt::Checked)
{
newState = Qt::PartiallyChecked;
break;
}
}
disconnect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
parentItem->setCheckState(CheckColumn, newState);
connect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
}
}
else
{
disconnect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
for (int i = 0; i < item->childCount(); ++i)
{
auto child = static_cast<ArbitraryTreeWidgetItem*>(item->child(i));
if (child->checkState(CheckColumn) != Qt::Checked)
{
child->setCheckState(CheckColumn, Qt::Checked);
m_checkedItems.push_back(child);
child->collectValues(EASY_GLOBALS.selected_thread, m_chart->chartType());
}
}
connect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
}
if (prevSize != m_checkedItems.size())
{
if (!m_collectionsTimer.isActive())
m_collectionsTimer.start();
}
}
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().
// !!!
decltype(m_checkedItems) uncheckedItems;
if (!item->isArrayItem())
{
uncheckedItems.push_back(item);
m_checkedItems.removeOne(item);
if (item->getSelfIndexInArray() >= 0)
{
Qt::CheckState newState = Qt::Unchecked;
auto parentItem = item->parent();
for (int i = 0; i < parentItem->childCount(); ++i)
{
auto child = parentItem->child(i);
if (child->checkState(CheckColumn) != Qt::Unchecked)
{
newState = Qt::PartiallyChecked;
break;
}
}
disconnect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
parentItem->setCheckState(CheckColumn, newState);
connect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
}
}
else
{
disconnect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
for (int i = 0; i < item->childCount(); ++i)
{
auto child = static_cast<ArbitraryTreeWidgetItem*>(item->child(i));
if (child->checkState(CheckColumn) == Qt::Checked)
{
child->setCheckState(CheckColumn, Qt::Unchecked);
uncheckedItems.push_back(child);
m_checkedItems.removeOne(child);
}
}
connect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
}
if (!uncheckedItems.isEmpty())
{
const bool hasCheckedItems = !m_checkedItems.empty();
m_exportToCsvAction->setEnabled(hasCheckedItems);
onCollectionsTimeout();
for (auto uncheckedItem : uncheckedItems)
uncheckedItem->interrupt();
}
}
}
void ArbitraryValuesWidget::onCurrentItemChanged(QTreeWidgetItem* _current, QTreeWidgetItem*)
{
if (_current == nullptr || _current->type() != ValueItemType)
{
m_chart->update(nullptr);
return;
}
auto item = static_cast<const ArbitraryTreeWidgetItem*>(_current);
m_chart->update(item->collection());
}
void ArbitraryValuesWidget::rebuild()
{
rebuild(EASY_GLOBALS.selected_thread, EASY_GLOBALS.selected_block, EASY_GLOBALS.selected_block_id);
}
void ArbitraryValuesWidget::rebuild(profiler::thread_id_t _threadId, profiler::block_index_t _blockIndex
, profiler::block_id_t _blockId)
{
clear();
m_threadId = _threadId;
m_blockIndex = _blockIndex;
m_blockId = _blockId;
buildTree(_threadId, _blockIndex, _blockId);
m_treeWidget->expandAll();
for (int i = 0, columns = m_treeWidget->columnCount(); i < columns; ++i)
m_treeWidget->resizeColumnToContents(i);
if (m_bMainWidget && !profiler_gui::is_max(_blockIndex))
{
const auto& block = easyBlocksTree(_blockIndex);
if (easyDescriptor(block.node->id()).type() == profiler::BlockType::Value)
select(*block.value, false);
}
}
void ArbitraryValuesWidget::select(const profiler::ArbitraryValue& _value, bool _resetOthers)
{
if (!m_checkedItems.empty() && _resetOthers)
{
disconnect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
for (auto item : m_checkedItems)
item->setCheckState(CheckColumn, Qt::Unchecked);
decltype(m_checkedItems) uncheckedItems(std::move(m_checkedItems));
m_exportToCsvAction->setEnabled(false);
onCollectionsTimeout();
for (auto item : uncheckedItems)
item->interrupt();
connect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
}
auto items = m_treeWidget->findItems(easyDescriptor(_value.id()).name(), Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive, int_cast(ArbitraryColumns::Name));
if (!items.empty())
{
for (auto i : items)
{
if (i->type() != ValueItemType)
continue;
auto item = reinterpret_cast<ArbitraryTreeWidgetItem*>(i);
const auto& value = item->value();
if (value.value_id() != _value.value_id() || value.type() != _value.type())
continue;
m_treeWidget->setCurrentItem(item);
item->setCheckState(CheckColumn, Qt::Checked);
break;
}
}
}
void ArbitraryValuesWidget::onCollectionsTimeout()
{
if (m_checkedItems.empty())
{
if (m_collectionsTimer.isActive())
m_collectionsTimer.stop();
m_chart->update(Collections {});
return;
}
Collections collections;
collections.reserve(static_cast<size_t>(m_checkedItems.size()));
for (auto item : m_checkedItems)
{
if (item->collection()->status() != ArbitraryValuesCollection::InProgress)
{
collections.push_back(CollectionPaintData {item->collection(), item->color(),
ChartPenStyle::Line, item == m_treeWidget->currentItem()});
}
}
if (collections.size() == m_checkedItems.size())
{
if (m_collectionsTimer.isActive())
m_collectionsTimer.stop();
m_chart->update(std::move(collections));
}
}
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();
}
}
void ArbitraryValuesWidget::onRegularChartTypeChecked(bool _checked)
{
if (!_checked)
return;
m_chart->setChartType(ChartType::Regular);
m_filterWindowPicker->hide();
m_filterWindowLabel->hide();
m_filterComboBox->hide();
m_filterBoxLabel->hide();
repaint();
}
void ArbitraryValuesWidget::onComplexityChartTypeChecked(bool _checked)
{
if (!_checked)
return;
m_chart->setChartType(ChartType::Complexity);
m_filterBoxLabel->show();
m_filterComboBox->show();
m_filterWindowLabel->setVisible(m_filterComboBox->currentIndex() != 0);
m_filterWindowPicker->setVisible(m_filterComboBox->currentIndex() != 0);
repaint();
}
void ArbitraryValuesWidget::onFilterComboBoxChanged(int _index)
{
switch (_index)
{
case 1:
m_filterWindowLabel->show();
m_filterWindowPicker->show();
m_chart->setFilterType(FilterType::Gauss);
break;
case 2:
m_filterWindowLabel->show();
m_filterWindowPicker->show();
m_chart->setFilterType(FilterType::Median);
break;
default:
m_filterWindowLabel->hide();
m_filterWindowPicker->hide();
m_chart->setFilterType(FilterType::None);
break;
}
}
void ArbitraryValuesWidget::onFilterWindowSizeChanged(int _size)
{
m_chart->setFilterWindowSize(_size);
}
void ArbitraryValuesWidget::onExportToCsvClicked(bool)
{
if (m_checkedItems.empty())
return;
auto filename = QFileDialog::getSaveFileName(this, "Export arbitrary values to csv", EASY_GLOBALS.lastFileDir,
"CSV File Format (*.csv)");
if (filename.isEmpty())
return;
QFileInfo fileinfo(filename);
EASY_GLOBALS.lastFileDir = fileinfo.absoluteDir().canonicalPath();
if (fileinfo.suffix() != QStringLiteral("csv"))
filename += QStringLiteral(".csv");
QFile csv(filename);
if (!csv.open(QIODevice::WriteOnly | QIODevice::Text) || !csv.isOpen())
{
QMessageBox::warning(this, "Warning", "Can not open file for writing", QMessageBox::Close);
return;
}
for (auto item : m_checkedItems)
{
if (item->collection()->status() == ArbitraryValuesCollection::InProgress)
{
QMessageBox::warning(this, "Warning", "Not all values collected to the moment.\nTry again a bit later.",
QMessageBox::Close);
return;
}
}
if (m_chart->chartType() == ChartType::Regular)
{
for (auto item : m_checkedItems)
{
const auto header = QString(" ; ;\nname:;%1;\ntimestamp;value;\n").arg(item->text(int_cast(ArbitraryColumns::Name)));
csv.write(header.toStdString().c_str());
const auto& values = item->collection()->values();
for (auto value : values)
{
const auto str = QString("%1;%2;\n").arg(value->begin()).arg(profiler_gui::valueString(*value));
csv.write(str.toStdString().c_str());
}
}
}
else
{
for (auto item : m_checkedItems)
{
const auto header = QString(" ; ; ; ; ;\nname:;%1; ; ; ;\nvalue;avg duration;min duration;max duration;count;\n")
.arg(item->text(int_cast(ArbitraryColumns::Name)));
csv.write(header.toStdString().c_str());
const auto& complexityMap = item->collection()->complexityMap();
for (const auto& kv : complexityMap)
{
const auto& durations = kv.second;
auto val = QString::number(kv.first, 'f', 12);
const int last = val.size() - 1;
int c = last;
for (; c >= 0 && val[c] == QChar('0'); --c);
if (val[c] == QChar('.'))
--c;
if (c != last)
val.chop(last - c);
if (durations.empty())
{
const auto str = QString("%1;0;0;0;0;\n").arg(val);
csv.write(str.toStdString().c_str());
continue;
}
const auto avg = std::accumulate(durations.begin(), durations.end(), profiler::timestamp_t(0)) / durations.size();
const auto str = QString("%1;%2;%3;%4;%5;\n").arg(val).arg(avg).arg(durations.front())
.arg(durations.back()).arg(durations.size());
csv.write(str.toStdString().c_str());
}
}
}
csv.write("\n");
}
void ArbitraryValuesWidget::onOpenInNewWindowClicked(bool)
{
saveSettings();
auto viewer = new ArbitraryValuesWidget(m_checkedItems, m_treeWidget->currentItem(), m_threadId, m_blockIndex, m_blockId);
auto dialog = new Dialog(nullptr, "EasyProfiler", viewer, WindowHeader::AllButtons, QMessageBox::NoButton);
dialog->setProperty("stayVisible", true);
dialog->setAttribute(Qt::WA_DeleteOnClose, true);
connect(&EASY_GLOBALS.events, &profiler_gui::GlobalSignals::allDataGoingToBeDeleted, dialog, &QDialog::reject);
connect(&EASY_GLOBALS.events, &profiler_gui::GlobalSignals::closeEvent, dialog, &QDialog::reject);
// Load last dialog geometry
{
QSettings settings(profiler_gui::ORGANAZATION_NAME, profiler_gui::APPLICATION_NAME);
settings.beginGroup("ArbitraryValuesWidgetWindow");
auto geometry = settings.value("dialog/geometry").toByteArray();
settings.endGroup();
if (!geometry.isEmpty())
dialog->restoreGeometry(geometry);
}
dialog->show();
}
void ArbitraryValuesWidget::buildTree(profiler::thread_id_t _threadId, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId)
{
m_treeWidget->clear();
m_treeWidget->setColumnHidden(int_cast(ArbitraryColumns::Value), profiler_gui::is_max(_blockIndex));
if (_threadId != 0)
{
auto it = EASY_GLOBALS.profiler_blocks.find(_threadId);
if (it != EASY_GLOBALS.profiler_blocks.end())
{
auto threadItem = buildTreeForThread(it->second, _blockIndex, _blockId);
m_treeWidget->addTopLevelItem(threadItem);
}
}
else
{
for (const auto& it : EASY_GLOBALS.profiler_blocks)
{
auto threadItem = buildTreeForThread(it.second, _blockIndex, _blockId);
m_treeWidget->addTopLevelItem(threadItem);
}
}
}
QTreeWidgetItem* ArbitraryValuesWidget::buildTreeForThread(const profiler::BlocksTreeRoot& _threadRoot, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId)
{
auto fm = m_treeWidget->fontMetrics();
auto rootItem = new QTreeWidgetItem(StdItemType);
rootItem->setText(int_cast(ArbitraryColumns::Type), QStringLiteral("Thread"));
rootItem->setText(int_cast(ArbitraryColumns::Name),
profiler_gui::decoratedThreadName(EASY_GLOBALS.use_decorated_thread_name, _threadRoot, EASY_GLOBALS.hex_thread_id));
rootItem->setData(int_cast(ArbitraryColumns::Type), Qt::UserRole, 0);
bool hasParticularBlockIndex = !profiler_gui::is_max(_blockIndex);
if (hasParticularBlockIndex)
{
const auto& block = easyBlocksTree(_blockIndex);
const auto& desc = easyDescriptor(block.node->id());
if (desc.type() != profiler::BlockType::Block)
{
hasParticularBlockIndex = false;
profiler_gui::set_max(_blockIndex);
profiler_gui::set_max(_blockId);
/*
auto value = block.value;
const bool isString = value->type() == profiler::DataType::String;
auto valueItem = new ArbitraryTreeWidgetItem(rootItem, !isString, desc.color(), *value);
valueItem->setText(int_cast(ArbitraryColumns::Type), profiler_gui::valueTypeString(*value));
valueItem->setText(int_cast(ArbitraryColumns::Name), desc.name());
valueItem->setText(int_cast(ArbitraryColumns::Value), profiler_gui::valueString(*value));
valueItem->setData(int_cast(ArbitraryColumns::Type), Qt::UserRole, 2);
const auto sizeHintWidth = valueItem->sizeHint(CheckColumn).width();
valueItem->setWidthHint(std::max(sizeHintWidth, fm.width(valueItem->text(CheckColumn))) + 32);
return rootItem;
*/
}
else
{
_blockId = block.node->id();
}
}
const bool anyBlockId = profiler_gui::is_max(_blockId);
const bool hasParticularBlockId = !anyBlockId;
using Vins = std::unordered_map<profiler::vin_t, UsedValueTypes, estd::hash<profiler::vin_t> >;
using BlocksMap = std::unordered_map<profiler::block_id_t, QTreeWidgetItem*, estd::hash<profiler::block_id_t> >;
using GlobalValues = std::unordered_map<std::string, UsedValueTypes>;
using StackEntry = std::pair<profiler::block_index_t, profiler::block_index_t>;
using Stack = std::vector<StackEntry>;
Vins vins;
BlocksMap blocks;
GlobalValues globalValues;
QTreeWidgetItem* blockItem = nullptr;
if (hasParticularBlockId)
{
blockItem = new QTreeWidgetItem(rootItem, StdItemType);
blockItem->setText(int_cast(ArbitraryColumns::Type), QStringLiteral("Block"));
if (hasParticularBlockIndex)
blockItem->setText(int_cast(ArbitraryColumns::Name), easyBlockName(_blockIndex));
else
blockItem->setText(int_cast(ArbitraryColumns::Name), easyDescriptor(_blockId).name());
blockItem->setData(int_cast(ArbitraryColumns::Type), Qt::UserRole, 1);
blockItem->setData(int_cast(ArbitraryColumns::Vin), Qt::UserRole, _blockId);
blocks[_blockId] = blockItem;
}
// Depth-first search traverse
Stack stack;
for (const auto index : _threadRoot.children)
{
size_t matchedParentIdStackDepth = 0;
bool matchedParentId = anyBlockId;
stack.clear();
stack.emplace_back(index, static_cast<profiler::block_index_t>(0));
if (anyBlockId)
blockItem = nullptr;
while (!stack.empty())
{
auto& top = stack.back();
auto& first = top.second;
const auto i = top.first;
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;
const auto typeIndex = int_cast(value->type());
auto vin = value->value_id();
ArbitraryTreeWidgetItem** usedItems = nullptr;
ArbitraryTreeWidgetItem* valueItem = nullptr;
if (vin == 0)
{
auto result = globalValues.emplace(desc.name(), 0);
usedItems = result.first->second.items;
if (!result.second)
valueItem = *(usedItems + typeIndex);
}
else
{
auto result = vins.emplace(vin, 0);
usedItems = result.first->second.items;
if (!result.second)
valueItem = *(usedItems + typeIndex);
}
if (valueItem != nullptr)
{
if (i == _blockIndex)
valueItem->setText(int_cast(ArbitraryColumns::Value), profiler_gui::shortValueString(*value));
//continue; // already in set
if (value->isArray() && value->type() != profiler::DataType::String)
{
const int size = profiler_gui::valueArraySize(*value);
if (valueItem->childCount() < size)
{
for (int childIndex = valueItem->childCount(); childIndex < size; ++childIndex)
{
auto item = new ArbitraryTreeWidgetItem(valueItem, true, desc.color(), *value);
item->setText(int_cast(ArbitraryColumns::Name), QString("%1[%2]").arg(desc.name()).arg(childIndex));
item->setData(int_cast(ArbitraryColumns::Type), Qt::UserRole, 3);
if (i == _blockIndex)
item->setText(int_cast(ArbitraryColumns::Value), profiler_gui::valueString(*value, childIndex));
const auto sizeHintWidth = valueItem->sizeHint(CheckColumn).width();
item->setWidthHint(std::max(sizeHintWidth, fm.boundingRect(valueItem->text(CheckColumn)).width()) + 32);
}
auto typeString = profiler_gui::valueTypeString(*value);
valueItem->setText(int_cast(ArbitraryColumns::Type), typeString.replace("[]", "[%1]").arg(size));
}
}
}
else
{
if (blockItem == nullptr)
{
// Enter here only if anyBlockId is true.
// matchedParentIdStackDepth is always == 0 in such case.
const auto parentBlockIndex = stack[matchedParentIdStackDepth].first;
const auto& parentBlock = easyBlock(parentBlockIndex).tree;
const auto id = parentBlock.node->id();
auto it = blocks.find(id);
if (it != blocks.end())
{
blockItem = it->second;
}
else
{
blockItem = new QTreeWidgetItem(rootItem, StdItemType);
blockItem->setText(int_cast(ArbitraryColumns::Type), QStringLiteral("Block"));
blockItem->setText(int_cast(ArbitraryColumns::Name), easyBlockName(parentBlock));
blockItem->setData(int_cast(ArbitraryColumns::Type), Qt::UserRole, 1);
blockItem->setData(int_cast(ArbitraryColumns::Vin), Qt::UserRole, id);
blocks.emplace(id, blockItem);
}
}
const bool isString = value->type() == profiler::DataType::String;
valueItem = new ArbitraryTreeWidgetItem(blockItem, !isString, desc.color(), *value);
valueItem->setText(int_cast(ArbitraryColumns::Type), profiler_gui::valueTypeString(*value));
valueItem->setText(int_cast(ArbitraryColumns::Name), desc.name());
valueItem->setData(int_cast(ArbitraryColumns::Type), Qt::UserRole, 2);
if (i == _blockIndex)
valueItem->setText(int_cast(ArbitraryColumns::Value), profiler_gui::shortValueString(*value));
auto sizeHintWidth = valueItem->sizeHint(CheckColumn).width();
valueItem->setWidthHint(std::max(sizeHintWidth, fm.boundingRect(valueItem->text(CheckColumn)).width()) + 32);
*(usedItems + typeIndex) = valueItem;
if (value->isArray() && !isString)
{
const int size = profiler_gui::valueArraySize(*value);
if (valueItem->childCount() < size)
{
for (int childIndex = valueItem->childCount(); childIndex < size; ++childIndex)
{
auto item = new ArbitraryTreeWidgetItem(valueItem, true, desc.color(), *value);
item->setText(int_cast(ArbitraryColumns::Name), QString("%1[%2]").arg(desc.name()).arg(childIndex));
item->setData(int_cast(ArbitraryColumns::Type), Qt::UserRole, 3);
if (i == _blockIndex)
item->setText(int_cast(ArbitraryColumns::Value), profiler_gui::valueString(*value, childIndex));
sizeHintWidth = valueItem->sizeHint(CheckColumn).width();
item->setWidthHint(std::max(sizeHintWidth, fm.boundingRect(valueItem->text(CheckColumn)).width()) + 32);
}
auto typeString = profiler_gui::valueTypeString(*value);
valueItem->setText(int_cast(ArbitraryColumns::Type), typeString.replace("[]", "[%1]").arg(size));
}
}
}
}
if (first < block.children.size())
{
if (hasParticularBlockId && (block.node->id() == _blockId || desc.id() == _blockId))
{
if (!matchedParentId)
matchedParentIdStackDepth = stack.size();
matchedParentId = true;
}
const auto child = block.children[first++];
stack.emplace_back(child, static_cast<profiler::block_index_t>(0));
}
else
{
if (hasParticularBlockId && stack.size() == matchedParentIdStackDepth)
{
matchedParentId = false;
blockItem = nullptr;
}
stack.pop_back();
}
}
}
return rootItem;
}
void ArbitraryValuesWidget::loadSettings()
{
QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
settings.beginGroup("ArbitraryValuesWidget");
auto value = settings.value("chart/filterWindow");
if (!value.isNull())
m_chart->setFilterWindowSize(value.toInt());
value = settings.value("chart/filter");
if (!value.isNull())
m_chart->setFilterType(static_cast<FilterType>(value.toInt()));
value = settings.value("chart/type");
if (!value.isNull())
m_chart->setChartType(static_cast<ChartType>(value.toInt()));
if (!m_bMainWidget)
{
settings.endGroup();
settings.beginGroup("ArbitraryValuesWidgetWindow");
}
auto geometry = settings.value("hsplitter/geometry").toByteArray();
if (!geometry.isEmpty())
m_splitter->restoreGeometry(geometry);
auto state = settings.value("hsplitter/state").toByteArray();
if (!state.isEmpty())
m_splitter->restoreState(state);
settings.endGroup();
}
void ArbitraryValuesWidget::saveSettings()
{
QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
if (!m_bMainWidget)
{
settings.beginGroup("ArbitraryValuesWidgetWindow");
settings.setValue("dialog/geometry", parentWidget()->saveGeometry());
settings.setValue("hsplitter/geometry", m_splitter->saveGeometry());
settings.setValue("hsplitter/state", m_splitter->saveState());
}
else
{
settings.beginGroup("ArbitraryValuesWidget");
settings.setValue("hsplitter/geometry", m_splitter->saveGeometry());
settings.setValue("hsplitter/state", m_splitter->saveState());
settings.setValue("chart/type", static_cast<int>(m_chart->chartType()));
settings.setValue("chart/filter", static_cast<int>(m_chart->filterType()));
settings.setValue("chart/filterWindow", m_chart->filterWindowSize());
}
settings.endGroup();
}