diff --git a/profiler_gui/arbitrary_value_inspector.cpp b/profiler_gui/arbitrary_value_inspector.cpp index 9c92c1b..6765643 100644 --- a/profiler_gui/arbitrary_value_inspector.cpp +++ b/profiler_gui/arbitrary_value_inspector.cpp @@ -55,13 +55,16 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -77,37 +80,77 @@ EASY_CONSTEXPR int ChartBound = 2; ///< Top and bottom bounds for chart EASY_CONSTEXPR int ChartBounds = ChartBound << 1; -template -std::vector gauss(const std::vector& _points) +void gaussFilter(std::vector& _points, int _windowSize, const std::atomic_bool& _interrupt) { - if (_points.size() < 3) - return _points; + if (_points.size() < 3 || _windowSize < 3 || _interrupt.load(std::memory_order_acquire)) + return; std::vector 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; - if (next < window_size) + qreal sum = 0; + + if (static_cast(next) < _windowSize) { - 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) + for (size_t j = next - _windowSize; j <= i; ++j) sum += _points[j].y(); - sum /= window_size; - out.emplace_back(_points[i].x(), sum); + sum /= _windowSize; } + + out.emplace_back(_points[i].x(), sum); } - return out; + _points = std::move(out); +} + +void medianFilter(std::vector& _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 out; + out.reserve(_points.size()); + + std::vector window(static_cast(_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(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) @@ -599,7 +642,9 @@ ArbitraryValuesChartItem::ArbitraryValuesChartItem() , m_workerMinDuration(0) , m_maxDuration(0) , m_minDuration(0) + , m_filterWindowSize(8) , m_chartType(ChartType::Regular) + , m_filterType(FilterType::None) { } @@ -760,6 +805,8 @@ 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 @@ -1235,7 +1282,29 @@ void ArbitraryValuesChartItem::updateComplexityImageAsync(QRectF _boundingRect, if (drawApproximateLine) { - p.drawPolyline(averages.data(), static_cast(averages.size())); + // drawPolyLine() with 2 pixel width is VERY slow! Do not use it! + //p.drawPolyline(averages.data(), static_cast(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) @@ -1265,13 +1334,31 @@ void ArbitraryValuesChartItem::updateComplexityImageAsync(QRectF _boundingRect, pen.setWidth(2); p.setPen(pen); - //averages = std::move(gauss<8>(averages)); + 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 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) { + if (isReady()) + return; + pp.cubicTo(averages[k - 2 * step], averages[k - 1 * step], averages[k]); } @@ -1309,12 +1396,44 @@ void ArbitraryValuesChartItem::update(const ArbitraryValuesCollection* _selected void ArbitraryValuesChartItem::setChartType(ChartType _chartType) { - if (m_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) { - cancelImageUpdate(); - m_chartType = _chartType; - updateImage(); + 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 @@ -1322,6 +1441,16 @@ 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) @@ -1378,11 +1507,31 @@ void GraphicsChart::setChartType(ChartType _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(); +} + ////////////////////////////////////////////////////////////////////////// enum class ArbitraryColumns : uint8_t @@ -1493,6 +1642,10 @@ ArbitraryValuesWidget::ArbitraryValuesWidget(QWidget* _parent) , m_splitter(new QSplitter(Qt::Horizontal, this)) , m_treeWidget(new QTreeWidget(this)) , m_chart(new GraphicsChart(this)) + , m_filterBoxLabel(new QLabel(tr(" Approx filter:"), this)) + , m_filterComboBox(new QComboBox(this)) + , m_filterWindowLabel(new QLabel(tr(" Window size:"), this)) + , m_filterWindowPicker(new QSpinBox(this)) , m_boldItem(nullptr) { m_splitter->setHandleWidth(1); @@ -1502,6 +1655,15 @@ ArbitraryValuesWidget::ArbitraryValuesWidget(QWidget* _parent) m_splitter->setStretchFactor(0, 1); m_splitter->setStretchFactor(1, 1); + m_filterWindowPicker->setRange(3, 25); + 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()); @@ -1512,16 +1674,24 @@ ArbitraryValuesWidget::ArbitraryValuesWidget(QWidget* _parent) 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); + auto actionRegulatChart = new QAction(QIcon(imagePath("yx-chart")), tr("Regular chart"), actionGroup); + actionRegulatChart->setCheckable(true); + actionRegulatChart->setChecked(true); + tb->addAction(actionRegulatChart); - action = new QAction(tr("Complexity chart"), actionGroup); - action->setCheckable(true); - connect(action, &QAction::triggered, this, &This::onComplexityChartTypeChecked); - tb->addAction(action); + auto actionComplexityChart = new QAction(QIcon(imagePath("big-o-chart")), tr("Complexity chart"), 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); @@ -1557,6 +1727,24 @@ ArbitraryValuesWidget::ArbitraryValuesWidget(QWidget* _parent) 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::of(&QComboBox::currentIndexChanged), this, &This::onFilterComboBoxChanged); + connect(m_filterWindowPicker, Overload::of(&QSpinBox::valueChanged), this, &This::onFilterWindowSizeChanged); + rebuild(); } @@ -1722,6 +1910,12 @@ void ArbitraryValuesWidget::onRegularChartTypeChecked(bool _checked) return; m_chart->setChartType(ChartType::Regular); + + m_filterWindowPicker->hide(); + m_filterWindowLabel->hide(); + m_filterComboBox->hide(); + m_filterBoxLabel->hide(); + repaint(); } @@ -1731,9 +1925,44 @@ void ArbitraryValuesWidget::onComplexityChartTypeChecked(bool _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::buildTree(profiler::thread_id_t _threadId, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId) { m_treeWidget->clear(); @@ -1920,6 +2149,18 @@ void ArbitraryValuesWidget::loadSettings() if (!state.isEmpty()) m_splitter->restoreState(state); + 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(value.toInt())); + + value = settings.value("chart/type"); + if (!value.isNull()) + m_chart->setChartType(static_cast(value.toInt())); + settings.endGroup(); } @@ -1929,5 +2170,8 @@ void ArbitraryValuesWidget::saveSettings() settings.beginGroup("ArbitraryValuesWidget"); settings.setValue("hsplitter/geometry", m_splitter->saveGeometry()); settings.setValue("hsplitter/state", m_splitter->saveState()); + settings.setValue("chart/type", static_cast(m_chart->chartType())); + settings.setValue("chart/filter", static_cast(m_chart->filterType())); + settings.setValue("chart/filterWindow", m_chart->filterWindowSize()); settings.endGroup(); } diff --git a/profiler_gui/arbitrary_value_inspector.h b/profiler_gui/arbitrary_value_inspector.h index ebb24e7..eaf17f9 100644 --- a/profiler_gui/arbitrary_value_inspector.h +++ b/profiler_gui/arbitrary_value_inspector.h @@ -88,6 +88,13 @@ enum class ChartType : uint8_t Complexity ///< Complexity chart; X axis = value, Y axis = duration }; +enum class FilterType : uint8_t +{ + None = 0, + Gauss, + Median, +}; + enum class ComplexityType : uint8_t { Constant = 0, ///< O(1) @@ -195,7 +202,9 @@ class ArbitraryValuesChartItem : public GraphicsImageItem profiler::timestamp_t m_workerMinDuration; profiler::timestamp_t m_maxDuration; profiler::timestamp_t m_minDuration; + int m_filterWindowSize; ChartType m_chartType; + FilterType m_filterType; public: @@ -216,7 +225,12 @@ public: void update(Collections _collections); void update(const ArbitraryValuesCollection* _selected); void setChartType(ChartType _chartType); + void setFilterType(FilterType _filterType); + void setFilterWindowSize(int _size); + ChartType chartType() const; + FilterType filterType() const; + int filterWindowSize() const; private: @@ -263,7 +277,12 @@ public: void update(Collections _collections); void update(const ArbitraryValuesCollection* _selected); void setChartType(ChartType _chartType); + void setFilterType(FilterType _filterType); + void setFilterWindowSize(int _size); + ChartType chartType() const; + FilterType filterType() const; + int filterWindowSize() const; private slots: @@ -318,6 +337,10 @@ class ArbitraryValuesWidget : public QWidget class QSplitter* m_splitter; QTreeWidget* m_treeWidget; GraphicsChart* m_chart; + class QLabel* m_filterBoxLabel; + class QComboBox* m_filterComboBox; + class QLabel* m_filterWindowLabel; + class QSpinBox* m_filterWindowPicker; ArbitraryTreeWidgetItem* m_boldItem; public: @@ -342,6 +365,8 @@ private slots: void onCollectionsTimeout(); void onRegularChartTypeChecked(bool _checked); void onComplexityChartTypeChecked(bool _checked); + void onFilterComboBoxChanged(int _index); + void onFilterWindowSizeChanged(int _size); private: diff --git a/profiler_gui/images/attribution.txt b/profiler_gui/images/attribution.txt index e346622..136d96d 100644 --- a/profiler_gui/images/attribution.txt +++ b/profiler_gui/images/attribution.txt @@ -41,3 +41,4 @@ default/arrow-up-hover.svg - Icon made by Freepik from www.flaticon.com default/arrow-up-disabled.svg - Icon made by Freepik from www.flaticon.com default/arrow-left.svg - Icon made by Freepik from www.flaticon.com default/arrow-right.svg - Icon made by Freepik from www.flaticon.com +default/yx.svg - Icon made by Freepik from www.flaticon.com diff --git a/profiler_gui/images/default/big-o.svg b/profiler_gui/images/default/big-o.svg new file mode 100644 index 0000000..31b564f --- /dev/null +++ b/profiler_gui/images/default/big-o.svg @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/profiler_gui/images/default/yx.svg b/profiler_gui/images/default/yx.svg new file mode 100644 index 0000000..f39aa7c --- /dev/null +++ b/profiler_gui/images/default/yx.svg @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/profiler_gui/resources.qrc b/profiler_gui/resources.qrc index 74dffc4..7179892 100644 --- a/profiler_gui/resources.qrc +++ b/profiler_gui/resources.qrc @@ -47,5 +47,7 @@ images/default/arrow-down.svg images/default/arrow-down-hover.svg images/default/arrow-down-disabled.svg + images/default/yx.svg + images/default/big-o.svg