From d24e4a9e7ec01d55c968a6b684402790e33a4da1 Mon Sep 17 00:00:00 2001 From: Victor Zarubkin Date: Thu, 30 Nov 2017 22:21:08 +0300 Subject: [PATCH] #31 [GUI] Arbitrary values viewer progress --- easy_profiler_core/include/easy/reader.h | 35 +- .../include/easy/serialized_block.h | 3 + profiler_gui/CMakeLists.txt | 2 + profiler_gui/arbitrary_value_inspector.cpp | 345 ++++++++++++++++++ profiler_gui/arbitrary_value_inspector.h | 151 ++++++++ profiler_gui/globals.h | 4 + 6 files changed, 523 insertions(+), 17 deletions(-) create mode 100644 profiler_gui/arbitrary_value_inspector.cpp create mode 100644 profiler_gui/arbitrary_value_inspector.h diff --git a/easy_profiler_core/include/easy/reader.h b/easy_profiler_core/include/easy/reader.h index 0a50986..d968dfa 100644 --- a/easy_profiler_core/include/easy/reader.h +++ b/easy_profiler_core/include/easy/reader.h @@ -141,7 +141,7 @@ namespace profiler { BlocksTree(const This&) = delete; This& operator = (const This&) = delete; - BlocksTree() + BlocksTree() EASY_NOEXCEPT : node(nullptr) , per_parent_stats(nullptr) , per_frame_stats(nullptr) @@ -151,32 +151,33 @@ namespace profiler { } - BlocksTree(This&& that) : BlocksTree() + BlocksTree(This&& that) EASY_NOEXCEPT + : BlocksTree() { make_move(::std::forward(that)); } - This& operator = (This&& that) + This& operator = (This&& that) EASY_NOEXCEPT { make_move(::std::forward(that)); return *this; } - ~BlocksTree() + ~BlocksTree() EASY_NOEXCEPT { release_stats(per_thread_stats); release_stats(per_parent_stats); release_stats(per_frame_stats); } - bool operator < (const This& other) const + bool operator < (const This& other) const EASY_NOEXCEPT { if (node == nullptr || other.node == nullptr) return false; return node->begin() < other.node->begin(); } - void shrink_to_fit() + void shrink_to_fit() EASY_NOEXCEPT { //for (auto& child : children) // child.shrink_to_fit(); @@ -193,7 +194,7 @@ namespace profiler { private: - void make_move(This&& that) + void make_move(This&& that) EASY_NOEXCEPT { if (per_thread_stats != that.per_thread_stats) release_stats(per_thread_stats); @@ -241,11 +242,12 @@ namespace profiler { BlocksTreeRoot(const This&) = delete; This& operator = (const This&) = delete; - BlocksTreeRoot() : profiled_time(0), wait_time(0), thread_id(0), frames_number(0), blocks_number(0), depth(0) + BlocksTreeRoot() EASY_NOEXCEPT + : profiled_time(0), wait_time(0), thread_id(0), frames_number(0), blocks_number(0), depth(0) { } - BlocksTreeRoot(This&& that) + BlocksTreeRoot(This&& that) EASY_NOEXCEPT : children(::std::move(that.children)) , sync(::std::move(that.sync)) , events(::std::move(that.events)) @@ -259,7 +261,7 @@ namespace profiler { { } - This& operator = (This&& that) + This& operator = (This&& that) EASY_NOEXCEPT { children = ::std::move(that.children); sync = ::std::move(that.sync); @@ -274,26 +276,25 @@ namespace profiler { return *this; } - inline bool got_name() const + inline bool got_name() const EASY_NOEXCEPT { return !thread_name.empty(); } - inline const char* name() const + inline const char* name() const EASY_NOEXCEPT { return thread_name.c_str(); } - bool operator < (const This& other) const + bool operator < (const This& other) const EASY_NOEXCEPT { return thread_id < other.thread_id; } }; // END of class BlocksTreeRoot. - typedef ::profiler::BlocksTree::blocks_t blocks_t; - - typedef ::std::unordered_map<::profiler::thread_id_t, ::profiler::BlocksTreeRoot, ::profiler::passthrough_hash<::profiler::thread_id_t> > thread_blocks_tree_t; + using blocks_t = ::profiler::BlocksTree::blocks_t; + using thread_blocks_tree_t = ::std::unordered_map<::profiler::thread_id_t, ::profiler::BlocksTreeRoot, ::profiler::passthrough_hash<::profiler::thread_id_t> >; ////////////////////////////////////////////////////////////////////////// @@ -388,7 +389,7 @@ namespace profiler { ////////////////////////////////////////////////////////////////////////// - typedef ::std::vector descriptors_list_t; + using descriptors_list_t = ::std::vector; } // END of namespace profiler. diff --git a/easy_profiler_core/include/easy/serialized_block.h b/easy_profiler_core/include/easy/serialized_block.h index 1875761..b81ae7f 100644 --- a/easy_profiler_core/include/easy/serialized_block.h +++ b/easy_profiler_core/include/easy/serialized_block.h @@ -193,6 +193,9 @@ namespace profiler { public: + using BaseBlockData::id; + using Event::begin; + ~ArbitraryValue() = delete; const char* data() const { diff --git a/profiler_gui/CMakeLists.txt b/profiler_gui/CMakeLists.txt index 4a77c3e..8d1a6bd 100644 --- a/profiler_gui/CMakeLists.txt +++ b/profiler_gui/CMakeLists.txt @@ -12,6 +12,8 @@ if (Qt5Widgets_FOUND) endif () add_executable(profiler_gui ${APPLICATION_PLATFORM} main.cpp + arbitrary_value_inspector.h + arbitrary_value_inspector.cpp blocks_graphics_view.h blocks_graphics_view.cpp blocks_tree_widget.h diff --git a/profiler_gui/arbitrary_value_inspector.cpp b/profiler_gui/arbitrary_value_inspector.cpp new file mode 100644 index 0000000..51bcba5 --- /dev/null +++ b/profiler_gui/arbitrary_value_inspector.cpp @@ -0,0 +1,345 @@ +/************************************************************************ +* 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-2017 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 +#include +#include +#include +#include "arbitrary_value_inspector.h" +#include "globals.h" + +////////////////////////////////////////////////////////////////////////// + +ArbitraryValuesCollection::ArbitraryValuesCollection() +{ + m_bReady = ATOMIC_VAR_INIT(false); + m_bInterrupt = ATOMIC_VAR_INIT(false); +} + +ArbitraryValuesCollection::~ArbitraryValuesCollection() +{ + interrupt(); +} + +const ArbitraryValuesMap& ArbitraryValuesCollection::valuesMap() const +{ + return m_values; +} + +bool ArbitraryValuesCollection::ready() const +{ + return m_bReady.load(std::memory_order_acquire); +} + +size_t ArbitraryValuesCollection::size() const +{ + size_t totalSize = 0; + for (const auto& it : m_values) + totalSize += it.second.size(); + return totalSize; +} + +void ArbitraryValuesCollection::collectValues(profiler::thread_id_t _threadId, profiler::vin_t _valueId, const char* _valueName) +{ + using This = ArbitraryValuesCollection; + + interrupt(); + setReady(false); + m_values.clear(); + + if (_valueId == 0) + m_collectorThread = std::thread(&This::collectByName, this, _threadId, _valueName); + else + m_collectorThread = std::thread(&This::collectById, this, _threadId, _valueId); +} + +void ArbitraryValuesCollection::interrupt() +{ + if (!m_collectorThread.joinable()) + return; + + m_bInterrupt.store(true, std::memory_order_release); + m_collectorThread.join(); + m_bInterrupt.store(false, std::memory_order_release); + + setReady(true); + m_values.clear(); +} + +void ArbitraryValuesCollection::setReady(bool _ready) +{ + m_bReady.store(_ready, std::memory_order_release); +} + +void ArbitraryValuesCollection::collectById(profiler::thread_id_t _threadId, profiler::vin_t _valueId) +{ + if (_threadId == 0) + { + for (const auto& it : EASY_GLOBALS.profiler_blocks) + { + if (!collectByIdForThread(it.first, _valueId)) + return; + } + } + else + { + if (!collectByIdForThread(_threadId, _valueId)) + return; + } + + setReady(true); +} + +bool ArbitraryValuesCollection::collectByIdForThread(profiler::thread_id_t _threadId, profiler::vin_t _valueId) +{ + const auto t = EASY_GLOBALS.profiler_blocks.find(_threadId); + if (t == EASY_GLOBALS.profiler_blocks.end()) + return true; + + const auto& profThread = t->second; + auto& valuesList = m_values[_threadId]; + + for (auto i : profThread.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; + + valuesList.push_back(value); + } + + return true; +} + +void ArbitraryValuesCollection::collectByName(profiler::thread_id_t _threadId, const std::string _valueName) +{ + if (_threadId == 0) + { + for (const auto& it : EASY_GLOBALS.profiler_blocks) + { + if (!collectByNameForThread(it.first, _valueName)) + return; + } + } + else + { + if (!collectByNameForThread(_threadId, _valueName)) + return; + } + + setReady(true); +} + +bool ArbitraryValuesCollection::collectByNameForThread(profiler::thread_id_t _threadId, const std::string& _valueName) +{ + const auto t = EASY_GLOBALS.profiler_blocks.find(_threadId); + if (t == EASY_GLOBALS.profiler_blocks.end()) + return true; + + const auto& profThread = t->second; + auto& valuesList = m_values[_threadId]; + + for (auto i : profThread.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; + + valuesList.push_back(block.value); + } + + return true; +} + +////////////////////////////////////////////////////////////////////////// + +EasyArbitraryValueInspector::EasyArbitraryValueInspector(QWidget* _parent) + : Parent(_parent) +{ + +} + +EasyArbitraryValueInspector::~EasyArbitraryValueInspector() +{ + +} + +////////////////////////////////////////////////////////////////////////// + +EasyArbitraryValueItem::EasyArbitraryValueItem(const ::profiler::ArbitraryValue& _value, profiler::thread_id_t _threadId) + : Parent(nullptr) + , m_scale(1) + , m_color(0) +{ + QObject::connect(&m_timer, &QTimer::timeout, [this] { onTimeout(); }); + m_collection.collectValues(_threadId, _value.value_id(), easyDescriptor(_value.id()).name()); + m_timer.start(40); +} + +EasyArbitraryValueItem::~EasyArbitraryValueItem() +{ + +} + +void EasyArbitraryValueItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*, QWidget*) +{ + if (m_points.empty()) + return; + + _painter->save(); + + auto pen = _painter->pen(); + pen.setColor(QColor::fromRgba(m_color)); + pen.setWidth(2); + _painter->setPen(pen); + + if (m_points.size() == 1) + _painter->drawPoint(m_points.front()); + else + _painter->drawPolyline(m_points.data(), static_cast(m_points.size())); + + _painter->restore(); +} + +void EasyArbitraryValueItem::onScaleChanged(qreal _scale) +{ + const auto k = _scale / m_scale; + for (auto& p : m_points) + p.setX(p.x() * k); + m_scale = _scale; +} + +void EasyArbitraryValueItem::onTimeout() +{ + if (!m_collection.ready()) + return; + + m_timer.stop(); + + const auto size = m_collection.size(); + m_points.clear(); + m_points.reserve(size); + + if (size == 0) + return; + + const auto& valuesByThread = m_collection.valuesMap(); + + if (valuesByThread.size() == 1) + { + const auto& values = valuesByThread.begin()->second; + + m_color = 0; + for (auto value : values) + { + if (m_color == 0) + m_color = easyDescriptor(values.front()->id()).color(); + + // TODO: calculate y + const auto x = sceneX(value->begin()); + m_points.emplace_back(x, 0); + } + } + else + { + std::list threadIds; + for (const auto& it : valuesByThread) + threadIds.push_back(it.first); + + m_color = 0; + size_t i = 0; + while (!threadIds.empty()) + { + for (auto it = threadIds.begin(); it != threadIds.end();) + { + const auto& values = valuesByThread.at(*it); + if (i >= values.size()) + { + it = threadIds.erase(it); + continue; + } + + if (m_color == 0) + m_color = easyDescriptor(values.front()->id()).color(); + + // TODO: calculate y + const auto x = sceneX(values[i]->begin()); + m_points.emplace_back(x, 0); + + ++it; + } + } + + std::sort(m_points.begin(), m_points.end(), [](const QPointF& lhs, const QPointF& rhs) -> bool { + return lhs.x() < rhs.x(); + }); + } + + scene()->update(); +} + +////////////////////////////////////////////////////////////////////////// + diff --git a/profiler_gui/arbitrary_value_inspector.h b/profiler_gui/arbitrary_value_inspector.h new file mode 100644 index 0000000..a61b973 --- /dev/null +++ b/profiler_gui/arbitrary_value_inspector.h @@ -0,0 +1,151 @@ +/************************************************************************ +* file name : arbitrary_value_inspector.h +* ----------------- : +* creation time : 2017/11/30 +* author : Victor Zarubkin +* email : v.s.zarubkin@gmail.com +* ----------------- : +* description : The file contains declaration of . +* ----------------- : +* change log : * 2017/11/30 Victor Zarubkin: initial commit. +* : +* : * +* ----------------- : +* license : Lightweight profiler library for c++ +* : Copyright(C) 2016-2017 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. +************************************************************************/ + +#ifndef EASY_PROFILER_GUI_ARBITRARY_VALUE_INSPECTOR_H +#define EASY_PROFILER_GUI_ARBITRARY_VALUE_INSPECTOR_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +////////////////////////////////////////////////////////////////////////// + +using ArbitraryValues = std::vector; +using ArbitraryValuesMap = std::unordered_map >; + +class ArbitraryValuesCollection EASY_FINAL +{ + ArbitraryValuesMap m_values; + std::thread m_collectorThread; + std::atomic_bool m_bReady; + std::atomic_bool m_bInterrupt; + +public: + + explicit ArbitraryValuesCollection(); + ~ArbitraryValuesCollection(); + + const ArbitraryValuesMap& valuesMap() const; + bool ready() const; + size_t size() const; + + void collectValues(profiler::thread_id_t _threadId, profiler::vin_t _valueId, const char* _valueName); + void interrupt(); + +private: + + void setReady(bool _ready); + void collectById(profiler::thread_id_t _threadId, profiler::vin_t _valueId); + void collectByName(profiler::thread_id_t _threadId, const std::string _valueName); + bool collectByIdForThread(profiler::thread_id_t _threadId, profiler::vin_t _valueId); + bool collectByNameForThread(profiler::thread_id_t _threadId, const std::string& _valueName); + +}; // end of class ArbitraryValuesCollection. + +////////////////////////////////////////////////////////////////////////// + +class EasyArbitraryValueInspector : public QWidget +{ + Q_OBJECT + + using Parent = QWidget; + using This = EasyArbitraryValueInspector; + +public: + + explicit EasyArbitraryValueInspector(QWidget* _parent = nullptr); + ~EasyArbitraryValueInspector() override; + +}; // end of class EasyArbitraryValueInspector. + +////////////////////////////////////////////////////////////////////////// + +class EasyArbitraryValueItem : public QGraphicsItem +{ + using Parent = QGraphicsItem; + using This = EasyArbitraryValueItem; + using Points = std::vector; + + ArbitraryValuesCollection m_collection; + Points m_points; + QTimer m_timer; + qreal m_scale; + QRgb m_color; + +public: + + explicit EasyArbitraryValueItem(const profiler::ArbitraryValue& _value, profiler::thread_id_t _threadId); + ~EasyArbitraryValueItem() override; + + void paint(QPainter* _painter, const QStyleOptionGraphicsItem* _option, QWidget* _widget = nullptr) override; + + void onScaleChanged(qreal _scale); + +private: + + void onTimeout(); + +}; // end of class EasyArbitraryValueItem. + +////////////////////////////////////////////////////////////////////////// + +#endif // EASY_PROFILER_GUI_ARBITRARY_VALUE_INSPECTOR_H diff --git a/profiler_gui/globals.h b/profiler_gui/globals.h index 3f2637b..33244c1 100644 --- a/profiler_gui/globals.h +++ b/profiler_gui/globals.h @@ -238,6 +238,10 @@ inline ::profiler::BlocksTree& blocksTree(::profiler::block_index_t i) { return easyBlock(i).tree; } +inline qreal sceneX(profiler::timestamp_t _time) { + return PROF_MICROSECONDS(qreal(_time - EASY_GLOBALS.begin_time)); +} + inline QString imagePath(const QString& _resource) { return QString(":/images/%1/%2").arg(EASY_GLOBALS.theme).arg(_resource); }