From beba74d559b949de8b88ba78b604377f42c59904 Mon Sep 17 00:00:00 2001 From: Victor Zarubkin Date: Mon, 3 Apr 2017 23:16:36 +0300 Subject: [PATCH] (GUI) Added real-time FPS Monitor which shows current max/avg frame time in GUI even if profiler is disabled. You just need to connect to the profiled app. You can close (hide) FPS Monitor and it would not send network requests to the profiled application anymore. You can increase/decrease FPS Monitor requests interval in "Settings -> FPS Monitor -> Request interval, ms". Right click on FPS Monitor window to show context menu in which you can clear contents or hide FPS Monitor. --- profiler_gui/CMakeLists.txt | 2 + profiler_gui/easy_frame_rate_viewer.cpp | 297 ++++++++++++++++++++++++ profiler_gui/easy_frame_rate_viewer.h | 125 ++++++++++ profiler_gui/globals.cpp | 3 + profiler_gui/globals.h | 3 + profiler_gui/main_window.cpp | 250 +++++++++++++++++++- profiler_gui/main_window.h | 14 ++ sample/main.cpp | 11 +- 8 files changed, 698 insertions(+), 7 deletions(-) create mode 100644 profiler_gui/easy_frame_rate_viewer.cpp create mode 100644 profiler_gui/easy_frame_rate_viewer.h diff --git a/profiler_gui/CMakeLists.txt b/profiler_gui/CMakeLists.txt index 78e187e..4f80035 100644 --- a/profiler_gui/CMakeLists.txt +++ b/profiler_gui/CMakeLists.txt @@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME} descriptors_tree_widget.cpp easy_chronometer_item.h easy_chronometer_item.cpp + easy_frame_rate_viewer.h + easy_frame_rate_viewer.cpp easy_graphics_item.h easy_graphics_item.cpp easy_graphics_scrollbar.h diff --git a/profiler_gui/easy_frame_rate_viewer.cpp b/profiler_gui/easy_frame_rate_viewer.cpp new file mode 100644 index 0000000..458d4ed --- /dev/null +++ b/profiler_gui/easy_frame_rate_viewer.cpp @@ -0,0 +1,297 @@ +/************************************************************************ +* file name : easy_frame_rate_viewer.cpp +* ----------------- : +* creation time : 2017/04/02 +* author : Victor Zarubkin +* email : v.s.zarubkin@gmail.com +* ----------------- : +* description : This file contains implementation of EasyFrameRateViewer widget. +* ----------------- : +* change log : * 2017/04/02 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 +#include "easy_frame_rate_viewer.h" +#include "globals.h" + +const int INTERVAL_WIDTH = 20; + +////////////////////////////////////////////////////////////////////////// + +EasyFPSGraphicsItem::EasyFPSGraphicsItem() : Parent(nullptr) +{ + +} + +EasyFPSGraphicsItem::~EasyFPSGraphicsItem() +{ + +} + +////////////////////////////////////////////////////////////////////////// + +QRectF EasyFPSGraphicsItem::boundingRect() const +{ + return m_boundingRect; +} + +void EasyFPSGraphicsItem::setBoundingRect(const QRectF& _boundingRect) +{ + m_boundingRect = _boundingRect; +} + +void EasyFPSGraphicsItem::setBoundingRect(qreal x, qreal y, qreal w, qreal h) +{ + m_boundingRect.setRect(x, y, w, h); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyFPSGraphicsItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*, QWidget*) +{ + if (m_frames.empty()) + return; + + const int fontHeight = QFontMetrics(scene()->font()).height() + 2; + const qreal h = m_boundingRect.height() - (fontHeight << 1) - 4; + if (h < 0) + return; + + const qreal halfWidth = m_boundingRect.width() * 0.5 - INTERVAL_WIDTH; + const int halfMax = static_cast(0.5 + halfWidth / INTERVAL_WIDTH); + const int half = static_cast(m_frames.size() / 2); + const qreal top = fontHeight, bottom = h + 4 + fontHeight; + qreal y; + + _painter->save(); + + _painter->drawLine(QPointF(0, top), QPointF(m_boundingRect.width(), top)); + _painter->drawLine(QPointF(0, bottom), QPointF(m_boundingRect.width(), bottom)); + + _painter->setPen(Qt::lightGray); + y = m_boundingRect.height() * 0.5; + _painter->drawLine(QPointF(0, y), QPointF(m_boundingRect.width(), y)); + y -= h * 0.25; + _painter->drawLine(QPointF(0, y), QPointF(m_boundingRect.width(), y)); + y += h * 0.5; + _painter->drawLine(QPointF(0, y), QPointF(m_boundingRect.width(), y)); + + m_points1.reserve(m_frames.size()); + m_points2.reserve(m_frames.size()); + int n = 0; + qreal x = m_boundingRect.width() * 0.5 + std::min(halfMax, half) * INTERVAL_WIDTH, localMax = 0, localMin = 1e30; + const qreal xCurrent = x; + for (int i = static_cast(m_frames.size()) - 1; i > -1 && x >= 0; --i, x -= INTERVAL_WIDTH, ++n) + { + const auto& val = m_frames[i]; + + if (val.first > localMax) + localMax = val.first; + if (val.first < localMin) + localMin = val.first; + m_points1.emplace_back(x, static_cast(val.first) + 1e-3); + + if (val.second > localMax) + localMax = val.second; + if (val.second < localMin) + localMin = val.second; + m_points2.emplace_back(x, static_cast(val.second) + 1e-3); + + _painter->drawLine(QPointF(x, top + 1), QPointF(x, bottom - 1)); + } + + const auto delta = std::max(localMax - localMin, 1e-3); + _painter->setPen(Qt::black); + + _painter->drawText(5, 0, m_boundingRect.width() - 10, fontHeight, Qt::AlignVCenter | Qt::AlignLeft, QString("Slowest %1 FPS (%2)") + .arg(static_cast(1e6 / localMax)).arg(::profiler_gui::timeStringReal(EASY_GLOBALS.time_units, localMax, 1))); + + _painter->drawText(5, 0, xCurrent - 5, fontHeight, Qt::AlignVCenter | Qt::AlignRight, QString("Max current %1 FPS (%2)") + .arg(static_cast(1e6 / m_frames.back().first)).arg(::profiler_gui::timeStringReal(EASY_GLOBALS.time_units, m_frames.back().first, 1))); + + _painter->drawText(5, bottom, xCurrent - 5, fontHeight, Qt::AlignVCenter | Qt::AlignRight, QString("Avg current %1 FPS (%2)") + .arg(static_cast(1e6 / m_frames.back().second)).arg(::profiler_gui::timeStringReal(EASY_GLOBALS.time_units, m_frames.back().second, 1))); + + _painter->drawText(5, bottom, m_boundingRect.width() - 10, fontHeight, Qt::AlignVCenter | Qt::AlignLeft, QString("Fastest %1 FPS (%2)") + .arg(static_cast(1e6 / localMin)).arg(::profiler_gui::timeStringReal(EASY_GLOBALS.time_units, localMin, 1))); + + if (localMin < EASY_GLOBALS.frame_time && EASY_GLOBALS.frame_time < localMax) + { + y = fontHeight + 2 + h * (1. - (EASY_GLOBALS.frame_time - localMin) / delta); + _painter->setPen(Qt::DashLine); + _painter->drawLine(QPointF(0, y), QPointF(m_boundingRect.width(), y)); + } + + for (int i = 0; i < n; ++i) + { + auto& point1 = m_points1[i]; + point1.setY(fontHeight + 2 + h * (1. - (point1.y() - localMin) / delta)); + + auto& point2 = m_points2[i]; + point2.setY(fontHeight + 2 + h * (1. - (point2.y() - localMin) / delta)); + } + + _painter->setPen(QColor::fromRgba(0xa0ff0000)); + if (n > 1) + { + _painter->drawPolyline(m_points1.data(), n); + + _painter->setPen(QColor::fromRgba(0xa00000ff)); + _painter->drawPolyline(m_points2.data(), n); + } + else + { + _painter->drawPoint(m_points1.back()); + + _painter->setPen(QColor::fromRgba(0xa00000ff)); + _painter->drawPoint(m_points2.back()); + } + + _painter->restore(); + + m_points1.clear(); + m_points2.clear(); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyFPSGraphicsItem::clear() +{ + m_frames.clear(); +} + +void EasyFPSGraphicsItem::addPoint(uint32_t _maxFrameTime, uint32_t _avgFrameTime) +{ + m_frames.emplace_back(_maxFrameTime, _avgFrameTime); + if (m_frames.size() > EASY_GLOBALS.max_fps_history) + m_frames.pop_front(); +} + +////////////////////////////////////////////////////////////////////////// + +EasyFrameRateViewer::EasyFrameRateViewer(QWidget* _parent) : Parent(_parent), m_fpsItem(nullptr) +{ + setCacheMode(QGraphicsView::CacheNone); + //setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); + setViewportUpdateMode(QGraphicsView::FullViewportUpdate); + setOptimizationFlag(QGraphicsView::DontSavePainterState, true); + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + setContentsMargins(0, 0, 0, 0); + setScene(new QGraphicsScene(this)); + scene()->setSceneRect(0, 0, 50, 50); + + m_fpsItem = new EasyFPSGraphicsItem(); + m_fpsItem->setPos(0, 0); + m_fpsItem->setBoundingRect(0, 0, 50, 50); + scene()->addItem(m_fpsItem); + + centerOn(0, 0); +} + +EasyFrameRateViewer::~EasyFrameRateViewer() +{ + +} + +void EasyFrameRateViewer::clear() +{ + m_fpsItem->clear(); +} + +void EasyFrameRateViewer::addPoint(uint32_t _maxFrameTime, uint32_t _avgFrameTime) +{ + m_fpsItem->addPoint(_maxFrameTime, _avgFrameTime); + scene()->update(); +} + +void EasyFrameRateViewer::resizeEvent(QResizeEvent* _event) +{ + Parent::resizeEvent(_event); + + m_fpsItem->setBoundingRect(0, 0, _event->size().width(), _event->size().height()); + + scene()->setSceneRect(m_fpsItem->boundingRect()); + scene()->update(); +} + +void EasyFrameRateViewer::hideEvent(QHideEvent* _event) +{ + Parent::hideEvent(_event); + EASY_GLOBALS.fps_enabled = isVisible(); + clear(); +} + +void EasyFrameRateViewer::showEvent(QShowEvent* _event) +{ + Parent::showEvent(_event); + EASY_GLOBALS.fps_enabled = isVisible(); + clear(); +} + +void EasyFrameRateViewer::contextMenuEvent(QContextMenuEvent* _event) +{ + QMenu menu; + QAction* action = nullptr; + + action = menu.addAction("Clear"); + connect(action, &QAction::triggered, [this](bool){ clear(); }); + + action = menu.addAction("Hide"); + connect(action, &QAction::triggered, [this](bool){ parentWidget()->hide(); }); + + menu.exec(QCursor::pos()); + + _event->accept(); +} + +////////////////////////////////////////////////////////////////////////// + diff --git a/profiler_gui/easy_frame_rate_viewer.h b/profiler_gui/easy_frame_rate_viewer.h new file mode 100644 index 0000000..d74d314 --- /dev/null +++ b/profiler_gui/easy_frame_rate_viewer.h @@ -0,0 +1,125 @@ +/************************************************************************ +* file name : easy_frame_rate_viewer.cpp +* ----------------- : +* creation time : 2017/04/02 +* author : Victor Zarubkin +* email : v.s.zarubkin@gmail.com +* ----------------- : +* description : This file contains declaration of EasyFrameRateViewer widget. +* ----------------- : +* change log : * 2017/04/02 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__FRAME_RATE_VIEWER__H +#define EASY__FRAME_RATE_VIEWER__H + +#include +#include +#include +#include +#include +#include + +////////////////////////////////////////////////////////////////////////// + +class EasyFPSGraphicsItem : public QGraphicsItem +{ + typedef QGraphicsItem Parent; + typedef EasyFPSGraphicsItem This; + typedef std::deque > FrameTimes; + + std::vector m_points1, m_points2; + FrameTimes m_frames; + QRectF m_boundingRect; + +public: + + explicit EasyFPSGraphicsItem(); + virtual ~EasyFPSGraphicsItem(); + + QRectF boundingRect() const override; + void paint(QPainter* _painter, const QStyleOptionGraphicsItem* _option, QWidget* _widget = nullptr) override; + + void setBoundingRect(const QRectF& _boundingRect); + void setBoundingRect(qreal x, qreal y, qreal w, qreal h); + + void clear(); + void addPoint(uint32_t _maxFrameTime, uint32_t _avgFrameTime); + +}; // END of class EasyFPSGraphicsItem. + +////////////////////////////////////////////////////////////////////////// + +class EasyFrameRateViewer : public QGraphicsView +{ + Q_OBJECT + +private: + + typedef QGraphicsView Parent; + typedef EasyFrameRateViewer This; + + EasyFPSGraphicsItem* m_fpsItem; + +public: + + explicit EasyFrameRateViewer(QWidget* _parent = nullptr); + virtual ~EasyFrameRateViewer(); + + void resizeEvent(QResizeEvent* _event) override; + void hideEvent(QHideEvent* _event) override; + void showEvent(QShowEvent* _event) override; + void contextMenuEvent(QContextMenuEvent* _event) override; + +public slots: + + void clear(); + void addPoint(uint32_t _maxFrameTime, uint32_t _avgFrameTime); + +}; // END of class EasyFrameRateViewer. + +////////////////////////////////////////////////////////////////////////// + +#endif // EASY__FRAME_RATE_VIEWER__H diff --git a/profiler_gui/globals.cpp b/profiler_gui/globals.cpp index 4974f23..6441769 100644 --- a/profiler_gui/globals.cpp +++ b/profiler_gui/globals.cpp @@ -76,9 +76,12 @@ namespace profiler_gui { , blocks_spacing(2) , blocks_size_min(2) , blocks_narrow_size(20) + , max_fps_history(100) + , fps_timer_interval(1000) , chrono_text_position(ChronoTextPosition_Center) , time_units(TimeUnits_ms) , connected(false) + , fps_enabled(true) , use_decorated_thread_name(true) , enable_event_markers(true) , enable_statistics(true) diff --git a/profiler_gui/globals.h b/profiler_gui/globals.h index 1314775..4e1e404 100644 --- a/profiler_gui/globals.h +++ b/profiler_gui/globals.h @@ -155,9 +155,12 @@ namespace profiler_gui { int blocks_spacing; ///< Minimum blocks spacing on diagram int blocks_size_min; ///< Minimum blocks size on diagram int blocks_narrow_size; ///< Width indicating narrow blocks + int max_fps_history; ///< Max frames history displayed in FPS Monitor + int fps_timer_interval; ///< Interval in milliseconds for sending network requests to the profiled application (used by FPS Monitor) ChronometerTextPosition chrono_text_position; ///< Selected interval text position TimeUnits time_units; ///< Units type for time (milliseconds, microseconds, nanoseconds or auto-definition) bool connected; ///< Is connected to source (to be able to capture profiling information) + bool fps_enabled; ///< Is FPS Monitor enabled bool use_decorated_thread_name; ///< Add "Thread" to the name of each thread (if there is no one) bool enable_event_markers; ///< Enable event indicators painting (These are narrow rectangles at the bottom of each thread) bool enable_statistics; ///< Enable gathering and using statistics (Disable if you want to consume less memory) diff --git a/profiler_gui/main_window.cpp b/profiler_gui/main_window.cpp index 15598c8..12f1f46 100644 --- a/profiler_gui/main_window.cpp +++ b/profiler_gui/main_window.cpp @@ -96,6 +96,7 @@ #include "blocks_tree_widget.h" #include "blocks_graphics_view.h" #include "descriptors_tree_widget.h" +#include "easy_frame_rate_viewer.h" #include "globals.h" #include @@ -156,8 +157,14 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("localhost"), m_lastP auto treeWidget = new EasyHierarchyWidget(this); m_treeWidget->setWidget(treeWidget); + m_fpsViewer = new QDockWidget("FPS Monitor", this); + m_fpsViewer->setObjectName("ProfilerGUI_FPS"); + m_fpsViewer->setWidget(new EasyFrameRateViewer(this)); + m_fpsViewer->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea); + addDockWidget(Qt::TopDockWidgetArea, m_graphicsView); addDockWidget(Qt::BottomDockWidgetArea, m_treeWidget); + addDockWidget(Qt::TopDockWidgetArea, m_fpsViewer); #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 auto descTree = new EasyDescWidget(); @@ -411,7 +418,7 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("localhost"), m_lastP l->setContentsMargins(33, 1, 1, 1); l->addWidget(new QLabel("Min blocks spacing, px", w), 0, Qt::AlignLeft); auto spinbox = new QSpinBox(w); - spinbox->setMinimum(0); + spinbox->setRange(0, 400); spinbox->setValue(EASY_GLOBALS.blocks_spacing); spinbox->setFixedWidth(50); connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onSpacingChange(int))); @@ -426,7 +433,7 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("localhost"), m_lastP l->setContentsMargins(33, 1, 1, 1); l->addWidget(new QLabel("Min blocks size, px", w), 0, Qt::AlignLeft); spinbox = new QSpinBox(w); - spinbox->setMinimum(1); + spinbox->setRange(1, 400); spinbox->setValue(EASY_GLOBALS.blocks_size_min); spinbox->setFixedWidth(50); connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onMinSizeChange(int))); @@ -441,7 +448,7 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("localhost"), m_lastP l->setContentsMargins(33, 1, 1, 1); l->addWidget(new QLabel("Blocks narrow size, px", w), 0, Qt::AlignLeft); spinbox = new QSpinBox(w); - spinbox->setMinimum(1); + spinbox->setRange(1, 400); spinbox->setValue(EASY_GLOBALS.blocks_narrow_size); spinbox->setFixedWidth(50); connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onNarrowSizeChange(int))); @@ -452,6 +459,42 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("localhost"), m_lastP submenu->addAction(waction); + + + submenu = menu->addMenu("FPS Monitor"); + w = new QWidget(submenu); + l = new QHBoxLayout(w); + l->setContentsMargins(33, 1, 1, 1); + l->addWidget(new QLabel("Request interval, ms", w), 0, Qt::AlignLeft); + spinbox = new QSpinBox(w); + spinbox->setRange(1, 600000); + spinbox->setValue(EASY_GLOBALS.fps_timer_interval); + spinbox->setFixedWidth(50); + connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onFpsIntervalChange(int))); + l->addWidget(spinbox); + w->setLayout(l); + waction = new QWidgetAction(submenu); + waction->setDefaultWidget(w); + submenu->addAction(waction); + + w = new QWidget(submenu); + l = new QHBoxLayout(w); + l->setContentsMargins(33, 1, 1, 1); + l->addWidget(new QLabel("Max history size", w), 0, Qt::AlignLeft); + spinbox = new QSpinBox(w); + spinbox->setRange(2, 200); + spinbox->setValue(EASY_GLOBALS.max_fps_history); + spinbox->setFixedWidth(50); + connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onFpsHistoryChange(int))); + l->addWidget(spinbox); + w->setLayout(l); + waction = new QWidgetAction(submenu); + waction->setDefaultWidget(w); + submenu->addAction(waction); + + + + submenu = menu->addMenu("Units"); actionGroup = new QActionGroup(this); actionGroup->setExclusive(true); @@ -549,6 +592,7 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("localhost"), m_lastP connect(graphicsView->view(), &EasyGraphicsView::intervalChanged, treeWidget->tree(), &EasyTreeWidget::setTreeBlocks); connect(&m_readerTimer, &QTimer::timeout, this, &This::onFileReaderTimeout); connect(&m_listenerTimer, &QTimer::timeout, this, &This::onListenerTimerTimeout); + connect(&m_fpsRequestTimer, &QTimer::timeout, this, &This::onFrameTimeRequestTimeout); m_progress = new QProgressDialog("Loading file...", "Cancel", 0, 100, this); @@ -907,6 +951,24 @@ void EasyMainWindow::onNarrowSizeChange(int _value) ////////////////////////////////////////////////////////////////////////// +void EasyMainWindow::onFpsIntervalChange(int _value) +{ + EASY_GLOBALS.fps_timer_interval = _value; + + if (m_fpsRequestTimer.isActive()) + m_fpsRequestTimer.stop(); + + if (EASY_GLOBALS.connected) + m_fpsRequestTimer.start(_value); +} + +void EasyMainWindow::onFpsHistoryChange(int _value) +{ + EASY_GLOBALS.max_fps_history = _value; +} + +////////////////////////////////////////////////////////////////////////// + void EasyMainWindow::onEditBlocksClicked(bool) { if (m_descTreeDialog != nullptr) @@ -1056,6 +1118,14 @@ void EasyMainWindow::loadSettings() if (!flag.isNull()) EASY_GLOBALS.use_decorated_thread_name = flag.toBool(); + flag = settings.value("fps_timer_interval"); + if (!flag.isNull()) + EASY_GLOBALS.fps_timer_interval = flag.toInt(); + + flag = settings.value("max_fps_history"); + if (!flag.isNull()) + EASY_GLOBALS.max_fps_history = flag.toInt(); + flag = settings.value("enable_statistics"); if (!flag.isNull()) EASY_GLOBALS.enable_statistics = flag.toBool(); @@ -1115,6 +1185,8 @@ void EasyMainWindow::saveSettingsAndGeometry() settings.setValue("auto_adjust_histogram_height", EASY_GLOBALS.auto_adjust_histogram_height); settings.setValue("use_decorated_thread_name", EASY_GLOBALS.use_decorated_thread_name); settings.setValue("enable_statistics", EASY_GLOBALS.enable_statistics); + settings.setValue("fps_timer_interval", EASY_GLOBALS.fps_timer_interval); + settings.setValue("max_fps_history", EASY_GLOBALS.max_fps_history); settings.setValue("encoding", QTextCodec::codecForLocale()->name()); settings.endGroup(); @@ -1122,6 +1194,9 @@ void EasyMainWindow::saveSettingsAndGeometry() void EasyMainWindow::setDisconnected(bool _showMessage) { + if (m_fpsRequestTimer.isActive()) + m_fpsRequestTimer.stop(); + if (_showMessage) QMessageBox::warning(this, "Warning", "Application was disconnected", QMessageBox::Close); @@ -1138,6 +1213,35 @@ void EasyMainWindow::setDisconnected(bool _showMessage) ////////////////////////////////////////////////////////////////////////// +void EasyMainWindow::onFrameTimeRequestTimeout() +{ + if (EASY_GLOBALS.fps_enabled && EASY_GLOBALS.connected && m_listener.regime() == LISTENER_IDLE) + { + if (m_listener.requestFrameTime()) + { + QTimer::singleShot(100, this, &This::checkFrameTimeReady); + } + } +} + +void EasyMainWindow::checkFrameTimeReady() +{ + if (EASY_GLOBALS.fps_enabled && EASY_GLOBALS.connected && m_listener.regime() == LISTENER_IDLE) + { + uint32_t maxTime = 0, avgTime = 0; + if (m_listener.frameTime(maxTime, avgTime)) + { + static_cast(m_fpsViewer->widget())->addPoint(maxTime, avgTime); + } + else if (m_fpsRequestTimer.isActive()) + { + QTimer::singleShot(100, this, &This::checkFrameTimeReady); + } + } +} + +////////////////////////////////////////////////////////////////////////// + void EasyMainWindow::onListenerTimerTimeout() { if (!m_listener.connected()) @@ -1579,6 +1683,9 @@ void EasyMainWindow::onConnectClicked(bool) m_captureAction->setEnabled(true); SET_ICON(m_connectAction, ":/Connection-on"); + if (!m_fpsRequestTimer.isActive()) + m_fpsRequestTimer.start(EASY_GLOBALS.fps_timer_interval); + disconnect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange); disconnect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange); @@ -1807,6 +1914,9 @@ EasySocketListener::EasySocketListener() : m_receivedSize(0), m_port(0), m_regim m_bInterrupt = ATOMIC_VAR_INIT(false); m_bConnected = ATOMIC_VAR_INIT(false); m_bStopReceive = ATOMIC_VAR_INIT(false); + m_bFrameTimeReady = ATOMIC_VAR_INIT(false); + m_frameMax = ATOMIC_VAR_INIT(0); + m_frameAvg = ATOMIC_VAR_INIT(0); } EasySocketListener::~EasySocketListener() @@ -1931,6 +2041,13 @@ bool EasySocketListener::connect(const char* _ipaddress, uint16_t _port, profile bool EasySocketListener::startCapture() { + if (m_thread.joinable()) + { + m_bInterrupt.store(true, ::std::memory_order_release); + m_thread.join(); + m_bInterrupt.store(false, ::std::memory_order_release); + } + clearData(); profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_START_CAPTURE); @@ -1968,6 +2085,13 @@ void EasySocketListener::stopCapture() void EasySocketListener::requestBlocksDescription() { + if (m_thread.joinable()) + { + m_bInterrupt.store(true, ::std::memory_order_release); + m_thread.join(); + m_bInterrupt.store(false, ::std::memory_order_release); + } + clearData(); profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_BLOCKS_DESCRIPTION); @@ -1982,6 +2106,47 @@ void EasySocketListener::requestBlocksDescription() m_regime = LISTENER_IDLE; } +bool EasySocketListener::frameTime(uint32_t& _maxTime, uint32_t& _avgTime) +{ + if (m_bFrameTimeReady.exchange(false, ::std::memory_order_acquire)) + { + _maxTime = m_frameMax.load(::std::memory_order_acquire); + _avgTime = m_frameAvg.load(::std::memory_order_acquire); + return true; + } + + return false; +} + +bool EasySocketListener::requestFrameTime() +{ + if (m_regime != LISTENER_IDLE) + return false; + + if (m_thread.joinable()) + { + m_bInterrupt.store(true, ::std::memory_order_release); + m_thread.join(); + m_bInterrupt.store(false, ::std::memory_order_release); + } + + clearData(); + + profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_MAIN_FRAME_TIME_MAX_AVG_US); + m_easySocket.send(&request, sizeof(request)); + + if (m_easySocket.isDisconnected()) + { + m_bConnected.store(false, ::std::memory_order_release); + return false; + } + + m_bFrameTimeReady.store(false, ::std::memory_order_release); + m_thread = ::std::thread(&EasySocketListener::listenFrameTime, this); + + return true; +} + ////////////////////////////////////////////////////////////////////////// void EasySocketListener::listenCapture() @@ -2281,5 +2446,84 @@ void EasySocketListener::listenDescription() delete[] buffer; } +void EasySocketListener::listenFrameTime() +{ + // TODO: Merge functions listenDescription() and listenCapture() + + static const int buffer_size = sizeof(::profiler::net::TimestampMessage) << 2; + char buffer[buffer_size] = {}; + int seek = 0, bytes = 0; + + bool isListen = true; + while (isListen && !m_bInterrupt.load(::std::memory_order_acquire)) + { + if ((bytes - seek) == 0) + { + bytes = m_easySocket.receive(buffer, buffer_size); + + if (bytes == -1) + { + if (m_easySocket.isDisconnected()) + { + m_bConnected.store(false, ::std::memory_order_release); + isListen = false; + } + + seek = 0; + bytes = 0; + + continue; + } + + seek = 0; + } + + if (bytes == 0) + { + isListen = false; + break; + } + + char* buf = buffer + seek; + + if (bytes > 0) + { + auto message = reinterpret_cast(buf); + if (!message->isEasyNetMessage()) + continue; + + switch (message->type) + { + case profiler::net::MESSAGE_TYPE_ACCEPTED_CONNECTION: + { + //qInfo() << "Receive MESSAGE_TYPE_ACCEPTED_CONNECTION"; + seek += sizeof(profiler::net::Message); + break; + } + + case profiler::net::MESSAGE_TYPE_REPLY_MAIN_FRAME_TIME_MAX_AVG_US: + { + //qInfo() << "Receive MESSAGE_TYPE_REPLY_MAIN_FRAME_TIME_MAX_AVG_US"; + + seek += sizeof(profiler::net::TimestampMessage); + if (seek <= buffer_size) + { + profiler::net::TimestampMessage* timestampMessage = (profiler::net::TimestampMessage*)message; + m_frameMax.store(timestampMessage->maxValue, ::std::memory_order_release); + m_frameAvg.store(timestampMessage->avgValue, ::std::memory_order_release); + m_bFrameTimeReady.store(true, ::std::memory_order_release); + } + + isListen = false; + break; + } + + default: + break; + } + } + } +} + ////////////////////////////////////////////////////////////////////////// diff --git a/profiler_gui/main_window.h b/profiler_gui/main_window.h index 913c0ec..dbba6b1 100644 --- a/profiler_gui/main_window.h +++ b/profiler_gui/main_window.h @@ -140,9 +140,12 @@ class EasySocketListener Q_DECL_FINAL ::std::thread m_thread; ///< uint64_t m_receivedSize; ///< uint16_t m_port; ///< + ::std::atomic m_frameMax; ///< + ::std::atomic m_frameAvg; ///< ::std::atomic_bool m_bInterrupt; ///< ::std::atomic_bool m_bConnected; ///< ::std::atomic_bool m_bStopReceive; ///< + ::std::atomic_bool m_bFrameTimeReady; ///< EasyListenerRegime m_regime; ///< public: @@ -165,6 +168,9 @@ public: void stopCapture(); void requestBlocksDescription(); + bool frameTime(uint32_t& _maxTime, uint32_t& _avgTime); + bool requestFrameTime(); + template inline void send(const T& _message) { m_easySocket.send(&_message, sizeof(T)); @@ -174,6 +180,7 @@ private: void listenCapture(); void listenDescription(); + void listenFrameTime(); }; // END of class EasySocketListener. @@ -192,6 +199,7 @@ protected: QString m_lastAddress; QDockWidget* m_treeWidget = nullptr; QDockWidget* m_graphicsView = nullptr; + QDockWidget* m_fpsViewer = nullptr; #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 QDockWidget* m_descTreeWidget = nullptr; @@ -203,6 +211,7 @@ protected: class QMessageBox* m_listenerDialog = nullptr; QTimer m_readerTimer; QTimer m_listenerTimer; + QTimer m_fpsRequestTimer; ::profiler::SerializedData m_serializedBlocks; ::profiler::SerializedData m_serializedDescriptors; EasyFileReader m_reader; @@ -257,7 +266,10 @@ protected slots: void onSpacingChange(int _value); void onMinSizeChange(int _value); void onNarrowSizeChange(int _value); + void onFpsIntervalChange(int _value); + void onFpsHistoryChange(int _value); void onFileReaderTimeout(); + void onFrameTimeRequestTimeout(); void onListenerTimerTimeout(); void onFileReaderCancel(); void onEditBlocksClicked(bool); @@ -273,6 +285,8 @@ protected slots: void onBlockStatusChange(::profiler::block_id_t _id, ::profiler::EasyBlockStatus _status); + void checkFrameTimeReady(); + private: // Private non-virtual methods diff --git a/sample/main.cpp b/sample/main.cpp index ff4040e..a4d6cfb 100644 --- a/sample/main.cpp +++ b/sample/main.cpp @@ -226,25 +226,28 @@ int main(int argc, char* argv[]) cv_m.unlock(); cv.notify_all(); +#ifndef SAMPLE_NETWORK_TEST std::atomic_bool stop = ATOMIC_VAR_INIT(false); auto frame_time_printer_thread = std::thread([&stop]() { while (!stop.load(std::memory_order_acquire)) { - std::cout << "Frame time: " << profiler::main_thread::frameTimeLocalMax() << " us\n"; - std::this_thread::sleep_for(std::chrono::seconds(1)); + std::cout << "Frame time: max " << profiler::main_thread::frameTimeLocalMax() << " us // avg " << profiler::main_thread::frameTimeLocalAvg() << " us\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(500)); } }); +#endif modellingThread(); +#ifndef SAMPLE_NETWORK_TEST stop.store(true, std::memory_order_release); + frame_time_printer_thread.join(); +#endif for(auto& t : threads) t.join(); - frame_time_printer_thread.join(); - auto end = std::chrono::system_clock::now(); auto elapsed = std::chrono::duration_cast(end - start);