0
0
mirror of https://github.com/yse/easy_profiler.git synced 2024-12-26 16:11:02 +08:00

(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.

This commit is contained in:
Victor Zarubkin 2017-04-03 23:16:36 +03:00
parent 8b7a68266c
commit beba74d559
8 changed files with 698 additions and 7 deletions

View File

@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
descriptors_tree_widget.cpp descriptors_tree_widget.cpp
easy_chronometer_item.h easy_chronometer_item.h
easy_chronometer_item.cpp easy_chronometer_item.cpp
easy_frame_rate_viewer.h
easy_frame_rate_viewer.cpp
easy_graphics_item.h easy_graphics_item.h
easy_graphics_item.cpp easy_graphics_item.cpp
easy_graphics_scrollbar.h easy_graphics_scrollbar.h

View File

@ -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 <QGraphicsScene>
#include <QResizeEvent>
#include <QContextMenuEvent>
#include <QMenu>
#include <QAction>
#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<int>(0.5 + halfWidth / INTERVAL_WIDTH);
const int half = static_cast<int>(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<int>(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<qreal>(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<qreal>(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<quint64>(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<quint64>(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<quint64>(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<quint64>(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();
}
//////////////////////////////////////////////////////////////////////////

View File

@ -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 <QGraphicsView>
#include <QGraphicsItem>
#include <QTimer>
#include <vector>
#include <deque>
#include <easy/profiler.h>
//////////////////////////////////////////////////////////////////////////
class EasyFPSGraphicsItem : public QGraphicsItem
{
typedef QGraphicsItem Parent;
typedef EasyFPSGraphicsItem This;
typedef std::deque<std::pair<uint32_t, uint32_t> > FrameTimes;
std::vector<QPointF> 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

View File

@ -76,9 +76,12 @@ namespace profiler_gui {
, blocks_spacing(2) , blocks_spacing(2)
, blocks_size_min(2) , blocks_size_min(2)
, blocks_narrow_size(20) , blocks_narrow_size(20)
, max_fps_history(100)
, fps_timer_interval(1000)
, chrono_text_position(ChronoTextPosition_Center) , chrono_text_position(ChronoTextPosition_Center)
, time_units(TimeUnits_ms) , time_units(TimeUnits_ms)
, connected(false) , connected(false)
, fps_enabled(true)
, use_decorated_thread_name(true) , use_decorated_thread_name(true)
, enable_event_markers(true) , enable_event_markers(true)
, enable_statistics(true) , enable_statistics(true)

View File

@ -155,9 +155,12 @@ namespace profiler_gui {
int blocks_spacing; ///< Minimum blocks spacing on diagram int blocks_spacing; ///< Minimum blocks spacing on diagram
int blocks_size_min; ///< Minimum blocks size on diagram int blocks_size_min; ///< Minimum blocks size on diagram
int blocks_narrow_size; ///< Width indicating narrow blocks 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 ChronometerTextPosition chrono_text_position; ///< Selected interval text position
TimeUnits time_units; ///< Units type for time (milliseconds, microseconds, nanoseconds or auto-definition) 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 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 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_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) bool enable_statistics; ///< Enable gathering and using statistics (Disable if you want to consume less memory)

View File

@ -96,6 +96,7 @@
#include "blocks_tree_widget.h" #include "blocks_tree_widget.h"
#include "blocks_graphics_view.h" #include "blocks_graphics_view.h"
#include "descriptors_tree_widget.h" #include "descriptors_tree_widget.h"
#include "easy_frame_rate_viewer.h"
#include "globals.h" #include "globals.h"
#include <easy/easy_net.h> #include <easy/easy_net.h>
@ -156,8 +157,14 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("localhost"), m_lastP
auto treeWidget = new EasyHierarchyWidget(this); auto treeWidget = new EasyHierarchyWidget(this);
m_treeWidget->setWidget(treeWidget); 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::TopDockWidgetArea, m_graphicsView);
addDockWidget(Qt::BottomDockWidgetArea, m_treeWidget); addDockWidget(Qt::BottomDockWidgetArea, m_treeWidget);
addDockWidget(Qt::TopDockWidgetArea, m_fpsViewer);
#if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0
auto descTree = new EasyDescWidget(); auto descTree = new EasyDescWidget();
@ -411,7 +418,7 @@ EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("localhost"), m_lastP
l->setContentsMargins(33, 1, 1, 1); l->setContentsMargins(33, 1, 1, 1);
l->addWidget(new QLabel("Min blocks spacing, px", w), 0, Qt::AlignLeft); l->addWidget(new QLabel("Min blocks spacing, px", w), 0, Qt::AlignLeft);
auto spinbox = new QSpinBox(w); auto spinbox = new QSpinBox(w);
spinbox->setMinimum(0); spinbox->setRange(0, 400);
spinbox->setValue(EASY_GLOBALS.blocks_spacing); spinbox->setValue(EASY_GLOBALS.blocks_spacing);
spinbox->setFixedWidth(50); spinbox->setFixedWidth(50);
connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onSpacingChange(int))); 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->setContentsMargins(33, 1, 1, 1);
l->addWidget(new QLabel("Min blocks size, px", w), 0, Qt::AlignLeft); l->addWidget(new QLabel("Min blocks size, px", w), 0, Qt::AlignLeft);
spinbox = new QSpinBox(w); spinbox = new QSpinBox(w);
spinbox->setMinimum(1); spinbox->setRange(1, 400);
spinbox->setValue(EASY_GLOBALS.blocks_size_min); spinbox->setValue(EASY_GLOBALS.blocks_size_min);
spinbox->setFixedWidth(50); spinbox->setFixedWidth(50);
connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onMinSizeChange(int))); 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->setContentsMargins(33, 1, 1, 1);
l->addWidget(new QLabel("Blocks narrow size, px", w), 0, Qt::AlignLeft); l->addWidget(new QLabel("Blocks narrow size, px", w), 0, Qt::AlignLeft);
spinbox = new QSpinBox(w); spinbox = new QSpinBox(w);
spinbox->setMinimum(1); spinbox->setRange(1, 400);
spinbox->setValue(EASY_GLOBALS.blocks_narrow_size); spinbox->setValue(EASY_GLOBALS.blocks_narrow_size);
spinbox->setFixedWidth(50); spinbox->setFixedWidth(50);
connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onNarrowSizeChange(int))); 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->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"); submenu = menu->addMenu("Units");
actionGroup = new QActionGroup(this); actionGroup = new QActionGroup(this);
actionGroup->setExclusive(true); 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(graphicsView->view(), &EasyGraphicsView::intervalChanged, treeWidget->tree(), &EasyTreeWidget::setTreeBlocks);
connect(&m_readerTimer, &QTimer::timeout, this, &This::onFileReaderTimeout); connect(&m_readerTimer, &QTimer::timeout, this, &This::onFileReaderTimeout);
connect(&m_listenerTimer, &QTimer::timeout, this, &This::onListenerTimerTimeout); 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); 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) void EasyMainWindow::onEditBlocksClicked(bool)
{ {
if (m_descTreeDialog != nullptr) if (m_descTreeDialog != nullptr)
@ -1056,6 +1118,14 @@ void EasyMainWindow::loadSettings()
if (!flag.isNull()) if (!flag.isNull())
EASY_GLOBALS.use_decorated_thread_name = flag.toBool(); 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"); flag = settings.value("enable_statistics");
if (!flag.isNull()) if (!flag.isNull())
EASY_GLOBALS.enable_statistics = flag.toBool(); 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("auto_adjust_histogram_height", EASY_GLOBALS.auto_adjust_histogram_height);
settings.setValue("use_decorated_thread_name", EASY_GLOBALS.use_decorated_thread_name); settings.setValue("use_decorated_thread_name", EASY_GLOBALS.use_decorated_thread_name);
settings.setValue("enable_statistics", EASY_GLOBALS.enable_statistics); 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.setValue("encoding", QTextCodec::codecForLocale()->name());
settings.endGroup(); settings.endGroup();
@ -1122,6 +1194,9 @@ void EasyMainWindow::saveSettingsAndGeometry()
void EasyMainWindow::setDisconnected(bool _showMessage) void EasyMainWindow::setDisconnected(bool _showMessage)
{ {
if (m_fpsRequestTimer.isActive())
m_fpsRequestTimer.stop();
if (_showMessage) if (_showMessage)
QMessageBox::warning(this, "Warning", "Application was disconnected", QMessageBox::Close); 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<EasyFrameRateViewer*>(m_fpsViewer->widget())->addPoint(maxTime, avgTime);
}
else if (m_fpsRequestTimer.isActive())
{
QTimer::singleShot(100, this, &This::checkFrameTimeReady);
}
}
}
//////////////////////////////////////////////////////////////////////////
void EasyMainWindow::onListenerTimerTimeout() void EasyMainWindow::onListenerTimerTimeout()
{ {
if (!m_listener.connected()) if (!m_listener.connected())
@ -1579,6 +1683,9 @@ void EasyMainWindow::onConnectClicked(bool)
m_captureAction->setEnabled(true); m_captureAction->setEnabled(true);
SET_ICON(m_connectAction, ":/Connection-on"); 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_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange);
disconnect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange); 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_bInterrupt = ATOMIC_VAR_INIT(false);
m_bConnected = ATOMIC_VAR_INIT(false); m_bConnected = ATOMIC_VAR_INIT(false);
m_bStopReceive = 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() EasySocketListener::~EasySocketListener()
@ -1931,6 +2041,13 @@ bool EasySocketListener::connect(const char* _ipaddress, uint16_t _port, profile
bool EasySocketListener::startCapture() 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(); clearData();
profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_START_CAPTURE); profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_START_CAPTURE);
@ -1968,6 +2085,13 @@ void EasySocketListener::stopCapture()
void EasySocketListener::requestBlocksDescription() 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(); clearData();
profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_BLOCKS_DESCRIPTION); profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_BLOCKS_DESCRIPTION);
@ -1982,6 +2106,47 @@ void EasySocketListener::requestBlocksDescription()
m_regime = LISTENER_IDLE; 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() void EasySocketListener::listenCapture()
@ -2281,5 +2446,84 @@ void EasySocketListener::listenDescription()
delete[] buffer; 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<const ::profiler::net::Message*>(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;
}
}
}
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////

View File

@ -140,9 +140,12 @@ class EasySocketListener Q_DECL_FINAL
::std::thread m_thread; ///< ::std::thread m_thread; ///<
uint64_t m_receivedSize; ///< uint64_t m_receivedSize; ///<
uint16_t m_port; ///< uint16_t m_port; ///<
::std::atomic<uint32_t> m_frameMax; ///<
::std::atomic<uint32_t> m_frameAvg; ///<
::std::atomic_bool m_bInterrupt; ///< ::std::atomic_bool m_bInterrupt; ///<
::std::atomic_bool m_bConnected; ///< ::std::atomic_bool m_bConnected; ///<
::std::atomic_bool m_bStopReceive; ///< ::std::atomic_bool m_bStopReceive; ///<
::std::atomic_bool m_bFrameTimeReady; ///<
EasyListenerRegime m_regime; ///< EasyListenerRegime m_regime; ///<
public: public:
@ -165,6 +168,9 @@ public:
void stopCapture(); void stopCapture();
void requestBlocksDescription(); void requestBlocksDescription();
bool frameTime(uint32_t& _maxTime, uint32_t& _avgTime);
bool requestFrameTime();
template <class T> template <class T>
inline void send(const T& _message) { inline void send(const T& _message) {
m_easySocket.send(&_message, sizeof(T)); m_easySocket.send(&_message, sizeof(T));
@ -174,6 +180,7 @@ private:
void listenCapture(); void listenCapture();
void listenDescription(); void listenDescription();
void listenFrameTime();
}; // END of class EasySocketListener. }; // END of class EasySocketListener.
@ -192,6 +199,7 @@ protected:
QString m_lastAddress; QString m_lastAddress;
QDockWidget* m_treeWidget = nullptr; QDockWidget* m_treeWidget = nullptr;
QDockWidget* m_graphicsView = nullptr; QDockWidget* m_graphicsView = nullptr;
QDockWidget* m_fpsViewer = nullptr;
#if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0
QDockWidget* m_descTreeWidget = nullptr; QDockWidget* m_descTreeWidget = nullptr;
@ -203,6 +211,7 @@ protected:
class QMessageBox* m_listenerDialog = nullptr; class QMessageBox* m_listenerDialog = nullptr;
QTimer m_readerTimer; QTimer m_readerTimer;
QTimer m_listenerTimer; QTimer m_listenerTimer;
QTimer m_fpsRequestTimer;
::profiler::SerializedData m_serializedBlocks; ::profiler::SerializedData m_serializedBlocks;
::profiler::SerializedData m_serializedDescriptors; ::profiler::SerializedData m_serializedDescriptors;
EasyFileReader m_reader; EasyFileReader m_reader;
@ -257,7 +266,10 @@ protected slots:
void onSpacingChange(int _value); void onSpacingChange(int _value);
void onMinSizeChange(int _value); void onMinSizeChange(int _value);
void onNarrowSizeChange(int _value); void onNarrowSizeChange(int _value);
void onFpsIntervalChange(int _value);
void onFpsHistoryChange(int _value);
void onFileReaderTimeout(); void onFileReaderTimeout();
void onFrameTimeRequestTimeout();
void onListenerTimerTimeout(); void onListenerTimerTimeout();
void onFileReaderCancel(); void onFileReaderCancel();
void onEditBlocksClicked(bool); void onEditBlocksClicked(bool);
@ -273,6 +285,8 @@ protected slots:
void onBlockStatusChange(::profiler::block_id_t _id, ::profiler::EasyBlockStatus _status); void onBlockStatusChange(::profiler::block_id_t _id, ::profiler::EasyBlockStatus _status);
void checkFrameTimeReady();
private: private:
// Private non-virtual methods // Private non-virtual methods

View File

@ -226,25 +226,28 @@ int main(int argc, char* argv[])
cv_m.unlock(); cv_m.unlock();
cv.notify_all(); cv.notify_all();
#ifndef SAMPLE_NETWORK_TEST
std::atomic_bool stop = ATOMIC_VAR_INIT(false); std::atomic_bool stop = ATOMIC_VAR_INIT(false);
auto frame_time_printer_thread = std::thread([&stop]() auto frame_time_printer_thread = std::thread([&stop]()
{ {
while (!stop.load(std::memory_order_acquire)) while (!stop.load(std::memory_order_acquire))
{ {
std::cout << "Frame time: " << profiler::main_thread::frameTimeLocalMax() << " us\n"; std::cout << "Frame time: max " << profiler::main_thread::frameTimeLocalMax() << " us // avg " << profiler::main_thread::frameTimeLocalAvg() << " us\n";
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(500));
} }
}); });
#endif
modellingThread(); modellingThread();
#ifndef SAMPLE_NETWORK_TEST
stop.store(true, std::memory_order_release); stop.store(true, std::memory_order_release);
frame_time_printer_thread.join();
#endif
for(auto& t : threads) for(auto& t : threads)
t.join(); t.join();
frame_time_printer_thread.join();
auto end = std::chrono::system_clock::now(); auto end = std::chrono::system_clock::now();
auto elapsed = auto elapsed =
std::chrono::duration_cast<std::chrono::microseconds>(end - start); std::chrono::duration_cast<std::chrono::microseconds>(end - start);