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);