1 /************************************************************************
2 * file name : main_window.cpp
3 * ----------------- :
4 * creation time : 2016/06/26
5 * author : Victor Zarubkin
6 * email : v.s.zarubkin@gmail.com
7 * ----------------- :
8 * description : The file contains implementation of MainWindow for easy_profiler GUI.
9 * ----------------- :
10 * change log : * 2016/06/26 Victor Zarubkin: Initial commit.
11 * :
12 * : * 2016/06/27 Victor Zarubkin: Passing blocks number to EasyTreeWidget::setTree().
13 * :
14 * : * 2016/06/29 Victor Zarubkin: Added menu with tests.
15 * :
16 * : * 2016/06/30 Sergey Yagovtsev: Open file by command line argument
17 * :
18 * : *
19 * ----------------- :
20 * license : Lightweight profiler library for c++
21 * : Copyright(C) 2016-2017 Sergey Yagovtsev, Victor Zarubkin
22 * :
23 * : Licensed under either of
24 * : * MIT license (LICENSE.MIT or http://opensource.org/licenses/MIT)
25 * : * Apache License, Version 2.0, (LICENSE.APACHE or http://www.apache.org/licenses/LICENSE-2.0)
26 * : at your option.
27 * :
28 * : The MIT License
29 * :
30 * : Permission is hereby granted, free of charge, to any person obtaining a copy
31 * : of this software and associated documentation files (the "Software"), to deal
32 * : in the Software without restriction, including without limitation the rights
33 * : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
34 * : of the Software, and to permit persons to whom the Software is furnished
35 * : to do so, subject to the following conditions:
36 * :
37 * : The above copyright notice and this permission notice shall be included in all
38 * : copies or substantial portions of the Software.
39 * :
40 * : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
41 * : INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
42 * : PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
43 * : LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
44 * : TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
45 * : USE OR OTHER DEALINGS IN THE SOFTWARE.
46 * :
47 * : The Apache License, Version 2.0 (the "License")
48 * :
49 * : You may not use this file except in compliance with the License.
50 * : You may obtain a copy of the License at
51 * :
52 * : http://www.apache.org/licenses/LICENSE-2.0
53 * :
54 * : Unless required by applicable law or agreed to in writing, software
55 * : distributed under the License is distributed on an "AS IS" BASIS,
56 * : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
57 * : See the License for the specific language governing permissions and
58 * : limitations under the License.
59 ************************************************************************/
60
61 #include <chrono>
62 #include <fstream>
63
64 #include <QApplication>
65 #include <QCoreApplication>
66 #include <QStatusBar>
67 #include <QFileDialog>
68 #include <QAction>
69 #include <QMenu>
70 #include <QMenuBar>
71 #include <QPushButton>
72 #include <QCloseEvent>
73 #include <QSettings>
74 #include <QTextCodec>
75 #include <QFont>
76 #include <QProgressDialog>
77 #include <QSignalBlocker>
78 #include <QDebug>
79 #include <QToolBar>
80 #include <QToolButton>
81 #include <QWidgetAction>
82 #include <QSpinBox>
83 #include <QMessageBox>
84 #include <QLineEdit>
85 #include <QLabel>
86 #include <QDialog>
87 #include <QVBoxLayout>
88 #include <QFile>
89 #include <QFileInfo>
90 #include <QTextStream>
91 #include <QDragEnterEvent>
92 #include <QDragMoveEvent>
93 #include <QDragLeaveEvent>
94 #include <QDropEvent>
95 #include <QMimeData>
96 #include <QDateTime>
97
98 #include "main_window.h"
99 #include "blocks_tree_widget.h"
100 #include "blocks_graphics_view.h"
101 #include "descriptors_tree_widget.h"
102 #include "easy_frame_rate_viewer.h"
103 #include "globals.h"
104
105 #include <easy/easy_net.h>
106 #include <easy/profiler.h>
107
108 #ifdef max
109 #undef max
110 #endif
111
112 #ifdef min
113 #undef min
114 #endif
115
116 //////////////////////////////////////////////////////////////////////////
117
118 #define EASY_DEFAULT_WINDOW_TITLE "EasyProfiler"
119
120 const int LOADER_TIMER_INTERVAL = 40;
121 const auto NETWORK_CACHE_FILE = "easy_profiler_stream.cache";
122
123 //////////////////////////////////////////////////////////////////////////
124
UI_themes()125 inline const QStringList& UI_themes()
126 {
127 static const QStringList themes {
128 "default"
129 };
130
131 return themes;
132 }
133
134 //////////////////////////////////////////////////////////////////////////
135
clear_stream(std::stringstream & _stream)136 inline void clear_stream(std::stringstream& _stream)
137 {
138 #if defined(__GNUC__) && __GNUC__ < 5
139 // gcc 4 has a known bug which has been solved in gcc 5:
140 // std::stringstream has no swap() method :(
141 _stream.str(std::string());
142 #else
143 std::stringstream().swap(_stream);
144 #endif
145 }
146
loadTheme(const QString & _theme)147 inline void loadTheme(const QString& _theme)
148 {
149 QFile file(QStringLiteral(":/themes/") + _theme);
150 if (file.open(QFile::ReadOnly | QFile::Text))
151 {
152 QTextStream in(&file);
153 QString style = in.readAll();
154 if (!style.isEmpty())
155 qApp->setStyleSheet(style);
156 }
157 }
158
159 //////////////////////////////////////////////////////////////////////////
160
EasyDockWidget(const QString & title,QWidget * parent)161 EasyDockWidget::EasyDockWidget(const QString& title, QWidget* parent) : QDockWidget(title, parent)
162 {
163 auto floatingButton = new QPushButton();
164 floatingButton->setObjectName("EasyDockWidgetFloatButton");
165 floatingButton->setProperty("floating", isFloating());
166 connect(floatingButton, &QPushButton::clicked, [this, floatingButton] {
167 setFloating(!isFloating());
168 floatingButton->setProperty("floating", isFloating());
169 floatingButton->style()->unpolish(floatingButton);
170 floatingButton->style()->polish(floatingButton);
171 floatingButton->update();
172 });
173
174 auto closeButton = new QPushButton();
175 closeButton->setObjectName("EasyDockWidgetCloseButton");
176 connect(closeButton, &QPushButton::clicked, [this] {
177 close();
178 });
179
180 auto caption = new QWidget(this);
181 caption->setObjectName("EasyDockWidgetTitle");
182
183 auto lay = new QHBoxLayout(caption);
184 lay->setContentsMargins(0, 0, 0, 0);
185 lay->setSpacing(2);
186 lay->addWidget(new QLabel(title));
187 lay->addStretch(100);
188 lay->addWidget(floatingButton);
189 lay->addWidget(closeButton);
190
191 setTitleBarWidget(caption);
192 }
193
~EasyDockWidget()194 EasyDockWidget::~EasyDockWidget()
195 {
196 }
197
EasyMainWindow()198 EasyMainWindow::EasyMainWindow() : Parent(), m_theme("default"), m_lastAddress("localhost"), m_lastPort(::profiler::DEFAULT_PORT)
199 {
200 { QIcon icon(":/images/logo"); if (!icon.isNull()) QApplication::setWindowIcon(icon); }
201
202 setObjectName("ProfilerGUI_MainWindow");
203 setWindowTitle(EASY_DEFAULT_WINDOW_TITLE);
204 setDockNestingEnabled(true);
205 setAcceptDrops(true);
206 resize(800, 600);
207 setStatusBar(nullptr);
208
209 loadSettings();
210 loadTheme(m_theme);
211
212 m_graphicsView = new EasyDockWidget("Diagram", this);
213 m_graphicsView->setObjectName("ProfilerGUI_Diagram");
214 m_graphicsView->setMinimumHeight(50);
215 m_graphicsView->setAllowedAreas(Qt::AllDockWidgetAreas);
216
217 auto graphicsView = new EasyGraphicsViewWidget(this);
218 m_graphicsView->setWidget(graphicsView);
219
220 m_treeWidget = new EasyDockWidget("Hierarchy", this);
221 m_treeWidget->setObjectName("ProfilerGUI_Hierarchy");
222 m_treeWidget->setMinimumHeight(50);
223 m_treeWidget->setAllowedAreas(Qt::AllDockWidgetAreas);
224
225 auto treeWidget = new EasyHierarchyWidget(this);
226 m_treeWidget->setWidget(treeWidget);
227
228 m_fpsViewer = new EasyDockWidget("FPS Monitor", this);
229 m_fpsViewer->setObjectName("ProfilerGUI_FPS");
230 m_fpsViewer->setWidget(new EasyFrameRateViewer(this));
231 m_fpsViewer->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
232
233 addDockWidget(Qt::TopDockWidgetArea, m_graphicsView);
234 addDockWidget(Qt::BottomDockWidgetArea, m_treeWidget);
235 addDockWidget(Qt::TopDockWidgetArea, m_fpsViewer);
236
237 #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0
238 auto descTree = new EasyDescWidget();
239 m_descTreeWidget = new EasyDockWidget("Blocks");
240 m_descTreeWidget->setObjectName("ProfilerGUI_Blocks");
241 m_descTreeWidget->setMinimumHeight(50);
242 m_descTreeWidget->setAllowedAreas(Qt::AllDockWidgetAreas);
243 m_descTreeWidget->setWidget(descTree);
244 addDockWidget(Qt::BottomDockWidgetArea, m_descTreeWidget);
245 #endif
246
247
248 auto toolbar = addToolBar("FileToolbar");
249 toolbar->setIconSize(::profiler_gui::ICONS_SIZE);
250 toolbar->setObjectName("ProfilerGUI_FileToolbar");
251 toolbar->setContentsMargins(1, 0, 1, 0);
252
253 m_loadActionMenu = new QMenu(this);
254 auto action = m_loadActionMenu->menuAction();
255 action->setText("Open file");
256 action->setIcon(QIcon(imagePath("open")));
257 connect(action, &QAction::triggered, this, &This::onOpenFileClicked);
258 toolbar->addAction(action);
259
260 for (const auto& f : m_lastFiles)
261 {
262 action = new QAction(f, this);
263 connect(action, &QAction::triggered, this, &This::onOpenFileClicked);
264 m_loadActionMenu->addAction(action);
265 }
266
267 m_saveAction = toolbar->addAction(QIcon(imagePath("save")), tr("Save"), this, SLOT(onSaveFileClicked(bool)));
268 m_deleteAction = toolbar->addAction(QIcon(imagePath("delete")), tr("Clear all"), this, SLOT(onDeleteClicked(bool)));
269
270 m_saveAction->setEnabled(false);
271 m_deleteAction->setEnabled(false);
272
273
274
275 toolbar = addToolBar("ProfileToolbar");
276 toolbar->setIconSize(::profiler_gui::ICONS_SIZE);
277 toolbar->setObjectName("ProfilerGUI_ProfileToolbar");
278 toolbar->setContentsMargins(1, 0, 1, 0);
279
280 toolbar->addAction(QIcon(imagePath("list")), tr("Blocks"), this, SLOT(onEditBlocksClicked(bool)));
281 m_captureAction = toolbar->addAction(QIcon(imagePath("start")), tr("Capture"), this, SLOT(onCaptureClicked(bool)));
282 m_captureAction->setEnabled(false);
283
284 toolbar->addSeparator();
285 m_connectAction = toolbar->addAction(QIcon(imagePath("connect")), tr("Connect"), this, SLOT(onConnectClicked(bool)));
286
287 auto lbl = new QLabel("Address:", toolbar);
288 lbl->setContentsMargins(5, 0, 2, 0);
289 toolbar->addWidget(lbl);
290 m_addressEdit = new QLineEdit();
291 m_addressEdit->setToolTip("Enter IP-address or host name");
292 //QRegExp rx("^0*(2(5[0-5]|[0-4]\\d)|1?\\d{1,2})(\\.0*(2(5[0-5]|[0-4]\\d)|1?\\d{1,2})){3}$");
293 //m_addressEdit->setValidator(new QRegExpValidator(rx, m_addressEdit));
294 m_addressEdit->setText(m_lastAddress);
295 m_addressEdit->setFixedWidth((m_addressEdit->fontMetrics().width(QString("255.255.255.255")) * 3) / 2);
296 toolbar->addWidget(m_addressEdit);
297
298 lbl = new QLabel("Port:", toolbar);
299 lbl->setContentsMargins(5, 0, 2, 0);
300 toolbar->addWidget(lbl);
301 m_portEdit = new QLineEdit();
302 m_portEdit->setValidator(new QIntValidator(1, 65535, m_portEdit));
303 m_portEdit->setText(QString::number(m_lastPort));
304 m_portEdit->setFixedWidth(m_portEdit->fontMetrics().width(QString("000000")) + 10);
305 toolbar->addWidget(m_portEdit);
306
307 connect(m_addressEdit, &QLineEdit::returnPressed, [this] { onConnectClicked(true); });
308 connect(m_portEdit, &QLineEdit::returnPressed, [this] { onConnectClicked(true); });
309
310
311
312 toolbar = addToolBar("SetupToolbar");
313 toolbar->setIconSize(::profiler_gui::ICONS_SIZE);
314 toolbar->setObjectName("ProfilerGUI_SetupToolbar");
315 toolbar->setContentsMargins(1, 0, 1, 0);
316
317 toolbar->addAction(QIcon(imagePath("expand")), "Expand all", this, SLOT(onExpandAllClicked(bool)));
318 toolbar->addAction(QIcon(imagePath("collapse")), "Collapse all", this, SLOT(onCollapseAllClicked(bool)));
319
320 toolbar->addSeparator();
321 auto menu = new QMenu("Settings", this);
322 menu->setToolTipsVisible(true);
323
324 QToolButton* toolButton = new QToolButton(toolbar);
325 toolButton->setIcon(QIcon(imagePath("settings")));
326 toolButton->setMenu(menu);
327 toolButton->setPopupMode(QToolButton::InstantPopup);
328 toolbar->addWidget(toolButton);
329
330 action = menu->addAction("Statistics enabled");
331 action->setCheckable(true);
332 action->setChecked(EASY_GLOBALS.enable_statistics);
333 connect(action, &QAction::triggered, this, &This::onEnableDisableStatistics);
334 if (EASY_GLOBALS.enable_statistics)
335 {
336 auto f = action->font();
337 f.setBold(true);
338 action->setFont(f);
339 action->setIcon(QIcon(imagePath("stats")));
340 }
341 else
342 {
343 action->setText("Statistics disabled");
344 action->setIcon(QIcon(imagePath("stats-off")));
345 }
346
347
348 action = menu->addAction("Only frames on histogram");
349 action->setToolTip("Display only top-level blocks on histogram.");
350 action->setCheckable(true);
351 action->setChecked(EASY_GLOBALS.display_only_frames_on_histogram);
352 connect(action, &QAction::triggered, [this](bool _checked)
353 {
354 EASY_GLOBALS.display_only_frames_on_histogram = _checked;
355 emit EASY_GLOBALS.events.displayOnlyFramesOnHistogramChanged();
356 });
357
358
359 menu->addSeparator();
360 auto submenu = menu->addMenu("View");
361 submenu->setToolTipsVisible(true);
362 action = submenu->addAction("Draw items' borders");
363 action->setToolTip("Draw borders for blocks on diagram.\nThis reduces performance.");
364 action->setCheckable(true);
365 action->setChecked(EASY_GLOBALS.draw_graphics_items_borders);
366 connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.draw_graphics_items_borders = _checked; refreshDiagram(); });
367
368 action = submenu->addAction("Overlap narrow children");
369 action->setToolTip("Children blocks will be overlaped by narrow\nparent blocks. See also \'Blocks narrow size\'.\nThis improves performance.");
370 action->setCheckable(true);
371 action->setChecked(EASY_GLOBALS.hide_narrow_children);
372 connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.hide_narrow_children = _checked; refreshDiagram(); });
373
374 action = submenu->addAction("Hide min-size blocks");
375 action->setToolTip("Hides blocks which screen size\nis less than \'Min blocks size\'.");
376 action->setCheckable(true);
377 action->setChecked(EASY_GLOBALS.hide_minsize_blocks);
378 connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.hide_minsize_blocks = _checked; refreshDiagram(); });
379
380 action = submenu->addAction("Build hierarchy only for current thread");
381 action->setToolTip("Hierarchy tree will be built\nfor blocks from current thread only.\nThis improves performance\nand saves a lot of memory.");
382 action->setCheckable(true);
383 action->setChecked(EASY_GLOBALS.only_current_thread_hierarchy);
384 connect(action, &QAction::triggered, this, &This::onHierarchyFlagChange);
385
386 action = submenu->addAction("Add zero blocks to hierarchy");
387 action->setToolTip("Zero duration blocks will be added into hierarchy tree.\nThis reduces performance and increases memory consumption.");
388 action->setCheckable(true);
389 action->setChecked(EASY_GLOBALS.add_zero_blocks_to_hierarchy);
390 connect(action, &QAction::triggered, [this](bool _checked)
391 {
392 EASY_GLOBALS.add_zero_blocks_to_hierarchy = _checked;
393 emit EASY_GLOBALS.events.hierarchyFlagChanged(_checked);
394 });
395
396 action = submenu->addAction("Enable zero duration blocks on diagram");
397 action->setToolTip("If checked then allows diagram to paint zero duration blocks\nwith 1px width on each scale. Otherwise, such blocks will be resized\nto 250ns duration.");
398 action->setCheckable(true);
399 action->setChecked(EASY_GLOBALS.enable_zero_length);
400 connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.enable_zero_length = _checked; refreshDiagram(); });
401
402 action = submenu->addAction("Highlight similar blocks");
403 action->setToolTip("Highlight all visible blocks which are similar\nto the current selected block.\nThis reduces performance.");
404 action->setCheckable(true);
405 action->setChecked(EASY_GLOBALS.highlight_blocks_with_same_id);
406 connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.highlight_blocks_with_same_id = _checked; refreshDiagram(); });
407
408 action = submenu->addAction("Collapse blocks on tree reset");
409 action->setToolTip("This collapses all blocks on diagram\nafter hierarchy tree reset.");
410 action->setCheckable(true);
411 action->setChecked(EASY_GLOBALS.collapse_items_on_tree_close);
412 connect(action, &QAction::triggered, this, &This::onCollapseItemsAfterCloseChanged);
413
414 action = submenu->addAction("Expand all on file open");
415 action->setToolTip("If checked then all blocks on diagram\nwill be initially expanded.");
416 action->setCheckable(true);
417 action->setChecked(EASY_GLOBALS.all_items_expanded_by_default);
418 connect(action, &QAction::triggered, this, &This::onAllItemsExpandedByDefaultChange);
419
420 action = submenu->addAction("Bind diagram and tree expand");
421 action->setToolTip("Expanding/collapsing blocks at diagram expands/collapses\nblocks at hierarchy tree and wise versa.");
422 action->setCheckable(true);
423 action->setChecked(EASY_GLOBALS.bind_scene_and_tree_expand_status);
424 connect(action, &QAction::triggered, this, &This::onBindExpandStatusChange);
425
426 action = submenu->addAction("Selecting block changes current thread");
427 action->setToolTip("Automatically select thread while selecting a block.\nIf not checked then you will have to select current thread\nmanually double clicking on thread name on a diagram.");
428 action->setCheckable(true);
429 action->setChecked(EASY_GLOBALS.selecting_block_changes_thread);
430 connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.selecting_block_changes_thread = _checked; });
431
432 action = submenu->addAction("Draw event markers");
433 action->setToolTip("Display event markers under the blocks\n(even if event-blocks are not visible).\nThis slightly reduces performance.");
434 action->setCheckable(true);
435 action->setChecked(EASY_GLOBALS.enable_event_markers);
436 connect(action, &QAction::triggered, [this](bool _checked)
437 {
438 EASY_GLOBALS.enable_event_markers = _checked;
439 refreshDiagram();
440 });
441
442 action = submenu->addAction("Automatically adjust histogram height");
443 action->setToolTip("You do not need to adjust boundaries manually,\nbut this restricts you from adjusting boundaries at all (zoom mode).\nYou can still adjust boundaries in overview mode though.");
444 action->setCheckable(true);
445 action->setChecked(EASY_GLOBALS.auto_adjust_histogram_height);
446 connect(action, &QAction::triggered, [](bool _checked)
447 {
448 EASY_GLOBALS.auto_adjust_histogram_height = _checked;
449 emit EASY_GLOBALS.events.autoAdjustHistogramChanged();
450 });
451
452 action = submenu->addAction("Use decorated thread names");
453 action->setToolTip("Add \'Thread\' word into thread name if there is no one already.\nExamples: \'Render\' will change to \'Render Thread\'\n\'WorkerThread\' will not change.");
454 action->setCheckable(true);
455 action->setChecked(EASY_GLOBALS.use_decorated_thread_name);
456 connect(action, &QAction::triggered, [this](bool _checked)
457 {
458 EASY_GLOBALS.use_decorated_thread_name = _checked;
459 emit EASY_GLOBALS.events.threadNameDecorationChanged();
460 });
461
462 action = submenu->addAction("Display hex thread id");
463 action->setToolTip("Display hex thread id instead of decimal.");
464 action->setCheckable(true);
465 action->setChecked(EASY_GLOBALS.hex_thread_id);
466 connect(action, &QAction::triggered, [this](bool _checked)
467 {
468 EASY_GLOBALS.hex_thread_id = _checked;
469 emit EASY_GLOBALS.events.hexThreadIdChanged();
470 });
471
472 submenu->addSeparator();
473 auto actionGroup = new QActionGroup(this);
474 actionGroup->setExclusive(true);
475
476 action = new QAction("Chrono text at top", actionGroup);
477 action->setToolTip("Draw duration of selected interval\nat the top of the screen.");
478 action->setCheckable(true);
479 action->setData(static_cast<int>(::profiler_gui::ChronoTextPosition_Top));
480 if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Top)
481 action->setChecked(true);
482 submenu->addAction(action);
483 connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged);
484
485 action = new QAction("Chrono text at center", actionGroup);
486 action->setToolTip("Draw duration of selected interval\nat the center of the screen.");
487 action->setCheckable(true);
488 action->setData(static_cast<int>(::profiler_gui::ChronoTextPosition_Center));
489 if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Center)
490 action->setChecked(true);
491 submenu->addAction(action);
492 connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged);
493
494 action = new QAction("Chrono text at bottom", actionGroup);
495 action->setToolTip("Draw duration of selected interval\nat the bottom of the screen.");
496 action->setCheckable(true);
497 action->setData(static_cast<int>(::profiler_gui::ChronoTextPosition_Bottom));
498 if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Bottom)
499 action->setChecked(true);
500 submenu->addAction(action);
501 connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged);
502
503 submenu->addSeparator();
504 auto w = new QWidget(submenu);
505 auto l = new QHBoxLayout(w);
506 l->setContentsMargins(26, 1, 16, 1);
507 l->addWidget(new QLabel("Min blocks spacing, px", w), 0, Qt::AlignLeft);
508 auto spinbox = new QSpinBox(w);
509 spinbox->setRange(0, 400);
510 spinbox->setValue(EASY_GLOBALS.blocks_spacing);
511 spinbox->setFixedWidth(70);
512 connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onSpacingChange(int)));
513 l->addWidget(spinbox);
514 w->setLayout(l);
515 auto waction = new QWidgetAction(submenu);
516 waction->setDefaultWidget(w);
517 submenu->addAction(waction);
518
519 w = new QWidget(submenu);
520 l = new QHBoxLayout(w);
521 l->setContentsMargins(26, 1, 16, 1);
522 l->addWidget(new QLabel("Min blocks size, px", w), 0, Qt::AlignLeft);
523 spinbox = new QSpinBox(w);
524 spinbox->setRange(1, 400);
525 spinbox->setValue(EASY_GLOBALS.blocks_size_min);
526 spinbox->setFixedWidth(70);
527 connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onMinSizeChange(int)));
528 l->addWidget(spinbox);
529 w->setLayout(l);
530 waction = new QWidgetAction(submenu);
531 waction->setDefaultWidget(w);
532 submenu->addAction(waction);
533
534 w = new QWidget(submenu);
535 l = new QHBoxLayout(w);
536 l->setContentsMargins(26, 1, 16, 1);
537 l->addWidget(new QLabel("Blocks narrow size, px", w), 0, Qt::AlignLeft);
538 spinbox = new QSpinBox(w);
539 spinbox->setRange(1, 400);
540 spinbox->setValue(EASY_GLOBALS.blocks_narrow_size);
541 spinbox->setFixedWidth(70);
542 connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onNarrowSizeChange(int)));
543 l->addWidget(spinbox);
544 w->setLayout(l);
545 waction = new QWidgetAction(submenu);
546 waction->setDefaultWidget(w);
547 submenu->addAction(waction);
548
549
550
551
552 submenu = menu->addMenu("FPS Monitor");
553 w = new QWidget(submenu);
554 l = new QHBoxLayout(w);
555 l->setContentsMargins(26, 1, 16, 1);
556 l->addWidget(new QLabel("Request interval, ms", w), 0, Qt::AlignLeft);
557 spinbox = new QSpinBox(w);
558 spinbox->setRange(1, 600000);
559 spinbox->setValue(EASY_GLOBALS.fps_timer_interval);
560 spinbox->setFixedWidth(70);
561 connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onFpsIntervalChange(int)));
562 l->addWidget(spinbox);
563 w->setLayout(l);
564 waction = new QWidgetAction(submenu);
565 waction->setDefaultWidget(w);
566 submenu->addAction(waction);
567
568 w = new QWidget(submenu);
569 l = new QHBoxLayout(w);
570 l->setContentsMargins(26, 1, 16, 1);
571 l->addWidget(new QLabel("Max history size", w), 0, Qt::AlignLeft);
572 spinbox = new QSpinBox(w);
573 spinbox->setRange(2, 200);
574 spinbox->setValue(EASY_GLOBALS.max_fps_history);
575 spinbox->setFixedWidth(70);
576 connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onFpsHistoryChange(int)));
577 l->addWidget(spinbox);
578 w->setLayout(l);
579 waction = new QWidgetAction(submenu);
580 waction->setDefaultWidget(w);
581 submenu->addAction(waction);
582
583 w = new QWidget(submenu);
584 l = new QHBoxLayout(w);
585 l->setContentsMargins(26, 1, 16, 1);
586 l->addWidget(new QLabel("Line width, px", w), 0, Qt::AlignLeft);
587 spinbox = new QSpinBox(w);
588 spinbox->setRange(1, 6);
589 spinbox->setValue(EASY_GLOBALS.fps_widget_line_width);
590 spinbox->setFixedWidth(70);
591 connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onFpsMonitorLineWidthChange(int)));
592 l->addWidget(spinbox);
593 w->setLayout(l);
594 waction = new QWidgetAction(submenu);
595 waction->setDefaultWidget(w);
596 submenu->addAction(waction);
597
598
599
600
601 submenu = menu->addMenu("Units");
602 actionGroup = new QActionGroup(this);
603 actionGroup->setExclusive(true);
604 action = new QAction("Auto", actionGroup);
605 action->setCheckable(true);
606 action->setData(static_cast<int>(::profiler_gui::TimeUnits_auto));
607 if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_auto)
608 action->setChecked(true);
609 submenu->addAction(action);
610 connect(action, &QAction::triggered, this, &This::onUnitsChanged);
611
612 action = new QAction("Milliseconds", actionGroup);
613 action->setCheckable(true);
614 action->setData(static_cast<int>(::profiler_gui::TimeUnits_ms));
615 if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_ms)
616 action->setChecked(true);
617 submenu->addAction(action);
618 connect(action, &QAction::triggered, this, &This::onUnitsChanged);
619
620 action = new QAction("Microseconds", actionGroup);
621 action->setCheckable(true);
622 action->setData(static_cast<int>(::profiler_gui::TimeUnits_us));
623 if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_us)
624 action->setChecked(true);
625 submenu->addAction(action);
626 connect(action, &QAction::triggered, this, &This::onUnitsChanged);
627
628 action = new QAction("Nanoseconds", actionGroup);
629 action->setCheckable(true);
630 action->setData(static_cast<int>(::profiler_gui::TimeUnits_ns));
631 if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_ns)
632 action->setChecked(true);
633 submenu->addAction(action);
634 connect(action, &QAction::triggered, this, &This::onUnitsChanged);
635
636
637 submenu = menu->addMenu("Remote");
638 m_eventTracingEnableAction = submenu->addAction("Event tracing enabled");
639 m_eventTracingEnableAction->setCheckable(true);
640 m_eventTracingEnableAction->setEnabled(false);
641 connect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange);
642
643 m_eventTracingPriorityAction = submenu->addAction("Low priority event tracing");
644 m_eventTracingPriorityAction->setCheckable(true);
645 m_eventTracingPriorityAction->setChecked(EASY_OPTION_LOW_PRIORITY_EVENT_TRACING);
646 m_eventTracingPriorityAction->setEnabled(false);
647 connect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange);
648
649
650 submenu = menu->addMenu("Encoding");
651 actionGroup = new QActionGroup(this);
652 actionGroup->setExclusive(true);
653
654 auto default_codec_mib = QTextCodec::codecForLocale()->mibEnum();
655 {
656 QList<QAction*> actions;
657
658 for (int mib : QTextCodec::availableMibs())
659 {
660 auto codec = QTextCodec::codecForMib(mib)->name();
661
662 action = new QAction(codec, actionGroup);
663 action->setData(mib);
664 action->setCheckable(true);
665 if (mib == default_codec_mib)
666 action->setChecked(true);
667
668 actions.push_back(action);
669 connect(action, &QAction::triggered, this, &This::onEncodingChanged);
670 }
671
672 qSort(actions.begin(), actions.end(), [](QAction* lhs, QAction* rhs) {
673 return lhs->text().compare(rhs->text(), Qt::CaseInsensitive) < 0;
674 });
675
676 submenu->addActions(actions);
677 }
678
679
680
681 menu->addSeparator();
682 submenu = menu->addMenu("Theme");
683 actionGroup = new QActionGroup(this);
684 actionGroup->setExclusive(true);
685
686 for (const auto& theme : UI_themes())
687 {
688 action = new QAction(theme, actionGroup);
689 action->setCheckable(true);
690 action->setChecked(action->text() == EASY_GLOBALS.theme);
691 connect(action, &QAction::triggered, this, &EasyMainWindow::onThemeChange);
692 submenu->addAction(action);
693 }
694
695
696 auto tb_height = toolbar->height() + 4;
697 toolbar = addToolBar("FrameToolbar");
698 toolbar->setIconSize(::profiler_gui::ICONS_SIZE);
699 toolbar->setObjectName("ProfilerGUI_FrameToolbar");
700 toolbar->setContentsMargins(1, 0, 1, 0);
701 toolbar->setMinimumHeight(tb_height);
702
703 lbl = new QLabel("Expected frame time:", toolbar);
704 lbl->setContentsMargins(5, 2, 2, 2);
705 toolbar->addWidget(lbl);
706
707 m_frameTimeEdit = new QLineEdit();
708 m_frameTimeEdit->setFixedWidth(70);
709 auto val = new QDoubleValidator(m_frameTimeEdit);
710 val->setLocale(QLocale::c());
711 val->setBottom(0);
712 m_frameTimeEdit->setValidator(val);
713 m_frameTimeEdit->setText(QString::number(EASY_GLOBALS.frame_time * 1e-3));
714 connect(m_frameTimeEdit, &QLineEdit::editingFinished, this, &This::onFrameTimeEditFinish);
715 connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::expectedFrameTimeChanged, this, &This::onFrameTimeChanged);
716 toolbar->addWidget(m_frameTimeEdit);
717
718 lbl = new QLabel("ms", toolbar);
719 lbl->setContentsMargins(5, 2, 1, 1);
720 toolbar->addWidget(lbl);
721
722
723 connect(graphicsView->view(), &EasyGraphicsView::intervalChanged, treeWidget->tree(), &EasyTreeWidget::setTreeBlocks);
724 connect(&m_readerTimer, &QTimer::timeout, this, &This::onFileReaderTimeout);
725 connect(&m_listenerTimer, &QTimer::timeout, this, &This::onListenerTimerTimeout);
726 connect(&m_fpsRequestTimer, &QTimer::timeout, this, &This::onFrameTimeRequestTimeout);
727
728
729 loadGeometry();
730
731 if(QCoreApplication::arguments().size() > 1)
732 {
733 auto opened_filename = QCoreApplication::arguments().at(1);
734 loadFile(opened_filename);
735 }
736
737 connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blockStatusChanged, this, &This::onBlockStatusChange);
738 connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blocksRefreshRequired, this, &This::onGetBlockDescriptionsClicked);
739 }
740
~EasyMainWindow()741 EasyMainWindow::~EasyMainWindow()
742 {
743 }
744
745 //////////////////////////////////////////////////////////////////////////
746
dragEnterEvent(QDragEnterEvent * drag_event)747 void EasyMainWindow::dragEnterEvent(QDragEnterEvent* drag_event)
748 {
749 if (drag_event->mimeData()->hasUrls())
750 drag_event->acceptProposedAction();
751 }
752
dragMoveEvent(QDragMoveEvent * drag_event)753 void EasyMainWindow::dragMoveEvent(QDragMoveEvent* drag_event)
754 {
755 if (drag_event->mimeData()->hasUrls())
756 drag_event->acceptProposedAction();
757 }
758
dragLeaveEvent(QDragLeaveEvent * drag_event)759 void EasyMainWindow::dragLeaveEvent(QDragLeaveEvent* drag_event)
760 {
761 drag_event->accept();
762 }
763
dropEvent(QDropEvent * drop_event)764 void EasyMainWindow::dropEvent(QDropEvent* drop_event)
765 {
766 const auto& urls = drop_event->mimeData()->urls();
767 if (!urls.empty())
768 {
769 if (m_bNetworkFileRegime)
770 {
771 // Warn user about unsaved network information and suggest to save
772 auto result = QMessageBox::question(this, "Unsaved session", "You have unsaved data!\nSave before opening new file?", QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel);
773 if (result == QMessageBox::Yes)
774 {
775 onSaveFileClicked(true);
776 }
777 else if (result != QMessageBox::No)
778 {
779 // User cancelled opening new file
780 return;
781 }
782 }
783
784 loadFile(urls.front().toLocalFile());
785 }
786 }
787
788 //////////////////////////////////////////////////////////////////////////
789
onThemeChange(bool)790 void EasyMainWindow::onThemeChange(bool)
791 {
792 auto action = qobject_cast<QAction*>(sender());
793 if (action == nullptr)
794 return;
795
796 auto newTheme = action->text();
797 if (m_theme != newTheme)
798 {
799 m_theme = std::move(newTheme);
800 QMessageBox::information(this, "Theme", "You should restart the application to apply the theme.");
801 }
802 }
803
804 //////////////////////////////////////////////////////////////////////////
805
onOpenFileClicked(bool)806 void EasyMainWindow::onOpenFileClicked(bool)
807 {
808 auto action = qobject_cast<QAction*>(sender());
809 if (action == nullptr)
810 return;
811
812 QString filename;
813
814 if (action == m_loadActionMenu->menuAction())
815 filename = QFileDialog::getOpenFileName(this, "Open EasyProfiler File", m_lastFiles.empty() ? QString() : m_lastFiles.front(), "EasyProfiler File (*.prof);;All Files (*.*)");
816 else
817 filename = action->text();
818
819 if (!filename.isEmpty())
820 {
821 if (m_bNetworkFileRegime)
822 {
823 // Warn user about unsaved network information and suggest to save
824 auto result = QMessageBox::question(this, "Unsaved session", "You have unsaved data!\nSave before opening new file?", QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel);
825 if (result == QMessageBox::Yes)
826 {
827 onSaveFileClicked(true);
828 }
829 else if (result != QMessageBox::No)
830 {
831 // User cancelled opening new file
832 return;
833 }
834 }
835
836 loadFile(filename);
837 }
838 }
839
840 //////////////////////////////////////////////////////////////////////////
841
addFileToList(const QString & filename)842 void EasyMainWindow::addFileToList(const QString& filename)
843 {
844 m_lastFiles.push_front(filename);
845
846 auto action = new QAction(filename, this);
847 connect(action, &QAction::triggered, this, &This::onOpenFileClicked);
848 auto fileActions = m_loadActionMenu->actions();
849 if (fileActions.empty())
850 m_loadActionMenu->addAction(action);
851 else
852 m_loadActionMenu->insertAction(fileActions.front(), action);
853
854 if (m_lastFiles.size() > 10)
855 {
856 // Keep 10 files at the list
857 m_lastFiles.pop_back();
858 m_loadActionMenu->removeAction(fileActions.back());
859 delete fileActions.back();
860 }
861
862 m_bOpenedCacheFile = filename.contains(NETWORK_CACHE_FILE);
863
864 if (m_bOpenedCacheFile)
865 setWindowTitle(QString(EASY_DEFAULT_WINDOW_TITLE " - [%1] - UNSAVED network cache file").arg(m_lastFiles.front()));
866 else
867 setWindowTitle(QString(EASY_DEFAULT_WINDOW_TITLE " - [%1]").arg(m_lastFiles.front()));
868 }
869
loadFile(const QString & filename)870 void EasyMainWindow::loadFile(const QString& filename)
871 {
872 const auto i = filename.lastIndexOf(QChar('/'));
873 const auto j = filename.lastIndexOf(QChar('\\'));
874
875 createProgressDialog(QString("Loading %1...").arg(filename.mid(::std::max(i, j) + 1)));
876
877 m_readerTimer.start(LOADER_TIMER_INTERVAL);
878 m_reader.load(filename);
879 }
880
readStream(::std::stringstream & data)881 void EasyMainWindow::readStream(::std::stringstream& data)
882 {
883 createProgressDialog(tr("Reading from stream..."));
884 m_readerTimer.start(LOADER_TIMER_INTERVAL);
885 m_reader.load(data);
886 }
887
888 //////////////////////////////////////////////////////////////////////////
889
onSaveFileClicked(bool)890 void EasyMainWindow::onSaveFileClicked(bool)
891 {
892 if (m_serializedBlocks.empty())
893 return;
894
895 QString lastFile = m_lastFiles.empty() ? QString() : m_lastFiles.front();
896
897 const auto i = lastFile.lastIndexOf(QChar('/'));
898 const auto j = lastFile.lastIndexOf(QChar('\\'));
899 auto k = ::std::max(i, j);
900
901 QString dir;
902 if (k > 0)
903 dir = lastFile.mid(0, ++k);
904
905 if (m_bNetworkFileRegime)
906 {
907 // Current file is network cache file, use current system time as output file name
908
909 if (!dir.isEmpty())
910 dir += QDateTime::currentDateTime().toString("/yyyy-MM-dd_HH-mm-ss.prof");
911 else
912 dir = QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm-ss.prof");
913 }
914 else if (m_bOpenedCacheFile)
915 {
916 // Opened old network cache file, use it's last modification time as output file name
917
918 QFileInfo fileInfo(lastFile);
919 if (!fileInfo.exists())
920 {
921 // Can not open the file!
922
923 QMessageBox::warning(this, "Warning", "Cannot open source file.\nSaving incomplete.", QMessageBox::Close);
924
925 m_lastFiles.pop_front();
926 auto action = m_loadActionMenu->actions().front();
927 m_loadActionMenu->removeAction(action);
928 delete action;
929
930 return;
931 }
932
933 if (!dir.isEmpty())
934 dir += fileInfo.lastModified().toString("/yyyy-MM-dd_HH-mm-ss.prof");
935 else
936 dir = fileInfo.lastModified().toString("yyyy-MM-dd_HH-mm-ss.prof");
937 }
938 else
939 {
940 dir = lastFile;
941 }
942
943 auto filename = QFileDialog::getSaveFileName(this, "Save EasyProfiler File", dir, "EasyProfiler File (*.prof);;All Files (*.*)");
944 if (!filename.isEmpty())
945 {
946 // Check if the same file has been selected
947 {
948 QFileInfo fileInfo1(m_bNetworkFileRegime ? QString(NETWORK_CACHE_FILE) : lastFile), fileInfo2(filename);
949 if (fileInfo1.exists() && fileInfo2.exists() && fileInfo1 == fileInfo2)
950 {
951 // Selected the same file - do nothing
952 return;
953 }
954 }
955
956 bool inOk = false, outOk = false;
957 int8_t retry1 = -1;
958 while (++retry1 < 4)
959 {
960 ::std::ifstream inFile(m_bNetworkFileRegime ? NETWORK_CACHE_FILE : lastFile.toStdString().c_str(), ::std::fstream::binary);
961 if (!inFile.is_open())
962 {
963 ::std::this_thread::sleep_for(::std::chrono::milliseconds(500));
964 continue;
965 }
966
967 inOk = true;
968
969 int8_t retry2 = -1;
970 while (++retry2 < 4)
971 {
972 ::std::ofstream outFile(filename.toStdString(), ::std::fstream::binary);
973 if (!outFile.is_open())
974 {
975 ::std::this_thread::sleep_for(::std::chrono::milliseconds(500));
976 continue;
977 }
978
979 outFile << inFile.rdbuf();
980 outOk = true;
981 break;
982 }
983
984 break;
985 }
986
987 if (outOk)
988 {
989 if (m_bNetworkFileRegime)
990 {
991 // Remove temporary network cahche file
992 QFile::remove(QString(NETWORK_CACHE_FILE));
993 }
994 else if (m_bOpenedCacheFile)
995 {
996 // Remove old temporary network cahche file
997
998 QFile::remove(lastFile.toStdString().c_str());
999
1000 m_lastFiles.pop_front();
1001 auto action = m_loadActionMenu->actions().front();
1002 m_loadActionMenu->removeAction(action);
1003 delete action;
1004 }
1005
1006 addFileToList(filename);
1007
1008 m_bNetworkFileRegime = false;
1009 }
1010 else if (inOk)
1011 {
1012 QMessageBox::warning(this, "Warning", "Cannot open destination file.\nSaving incomplete.", QMessageBox::Close);
1013 }
1014 else
1015 {
1016 if (m_bNetworkFileRegime)
1017 QMessageBox::warning(this, "Warning", "Cannot open network cache file.\nSaving incomplete.", QMessageBox::Close);
1018 else
1019 QMessageBox::warning(this, "Warning", "Cannot open source file.\nSaving incomplete.", QMessageBox::Close);
1020 }
1021 }
1022 }
1023
1024 //////////////////////////////////////////////////////////////////////////
1025
clear()1026 void EasyMainWindow::clear()
1027 {
1028 static_cast<EasyHierarchyWidget*>(m_treeWidget->widget())->clear(true);
1029 static_cast<EasyGraphicsViewWidget*>(m_graphicsView->widget())->clear();
1030
1031 #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0
1032 static_cast<EasyDescWidget*>(m_descTreeWidget->widget())->clear();
1033 #endif
1034 if (m_dialogDescTree != nullptr)
1035 m_dialogDescTree->clear();
1036
1037 EASY_GLOBALS.selected_thread = 0;
1038 ::profiler_gui::set_max(EASY_GLOBALS.selected_block);
1039 ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id);
1040 EASY_GLOBALS.profiler_blocks.clear();
1041 EASY_GLOBALS.descriptors.clear();
1042 EASY_GLOBALS.gui_blocks.clear();
1043
1044 m_serializedBlocks.clear();
1045 m_serializedDescriptors.clear();
1046
1047 m_saveAction->setEnabled(false);
1048 m_deleteAction->setEnabled(false);
1049
1050 if (m_bNetworkFileRegime)
1051 QFile::remove(QString(NETWORK_CACHE_FILE));
1052
1053 m_bNetworkFileRegime = false;
1054 m_bOpenedCacheFile = false;
1055
1056 setWindowTitle(EASY_DEFAULT_WINDOW_TITLE);
1057 }
1058
1059 //////////////////////////////////////////////////////////////////////////
1060
refreshDiagram()1061 void EasyMainWindow::refreshDiagram()
1062 {
1063 static_cast<EasyGraphicsViewWidget*>(m_graphicsView->widget())->view()->scene()->update();
1064 }
1065
1066 //////////////////////////////////////////////////////////////////////////
1067
onDeleteClicked(bool)1068 void EasyMainWindow::onDeleteClicked(bool)
1069 {
1070 int button = QMessageBox::Yes;
1071 if (m_bNetworkFileRegime)
1072 button = QMessageBox::question(this, "Clear all profiled data", "All profiled data and network cache file\nare going to be deleted!\nContinue?", QMessageBox::Yes, QMessageBox::No);
1073
1074 if (button == QMessageBox::Yes)
1075 clear();
1076 }
1077
1078 //////////////////////////////////////////////////////////////////////////
1079
onExitClicked(bool)1080 void EasyMainWindow::onExitClicked(bool)
1081 {
1082 close();
1083 }
1084
1085 //////////////////////////////////////////////////////////////////////////
1086
onEncodingChanged(bool)1087 void EasyMainWindow::onEncodingChanged(bool)
1088 {
1089 auto action = qobject_cast<QAction*>(sender());
1090 if (action == nullptr)
1091 return;
1092
1093 const int mib = action->data().toInt();
1094 auto codec = QTextCodec::codecForMib(mib);
1095 if (codec != nullptr)
1096 QTextCodec::setCodecForLocale(codec);
1097 }
1098
onChronoTextPosChanged(bool)1099 void EasyMainWindow::onChronoTextPosChanged(bool)
1100 {
1101 auto _sender = qobject_cast<QAction*>(sender());
1102 EASY_GLOBALS.chrono_text_position = static_cast<::profiler_gui::ChronometerTextPosition>(_sender->data().toInt());
1103 refreshDiagram();
1104 }
1105
onUnitsChanged(bool)1106 void EasyMainWindow::onUnitsChanged(bool)
1107 {
1108 auto _sender = qobject_cast<QAction*>(sender());
1109 EASY_GLOBALS.time_units = static_cast<::profiler_gui::TimeUnits>(_sender->data().toInt());
1110 }
1111
onEnableDisableStatistics(bool _checked)1112 void EasyMainWindow::onEnableDisableStatistics(bool _checked)
1113 {
1114 EASY_GLOBALS.enable_statistics = _checked;
1115
1116 auto action = qobject_cast<QAction*>(sender());
1117 if (action != nullptr)
1118 {
1119 auto f = action->font();
1120 f.setBold(_checked);
1121 action->setFont(f);
1122
1123 if (_checked)
1124 {
1125 action->setText("Statistics enabled");
1126 action->setIcon(QIcon(imagePath("stats")));
1127 }
1128 else
1129 {
1130 action->setText("Statistics disabled");
1131 action->setIcon(QIcon(imagePath("stats-off")));
1132 }
1133 }
1134 }
1135
onCollapseItemsAfterCloseChanged(bool _checked)1136 void EasyMainWindow::onCollapseItemsAfterCloseChanged(bool _checked)
1137 {
1138 EASY_GLOBALS.collapse_items_on_tree_close = _checked;
1139 }
1140
onAllItemsExpandedByDefaultChange(bool _checked)1141 void EasyMainWindow::onAllItemsExpandedByDefaultChange(bool _checked)
1142 {
1143 EASY_GLOBALS.all_items_expanded_by_default = _checked;
1144 }
1145
onBindExpandStatusChange(bool _checked)1146 void EasyMainWindow::onBindExpandStatusChange(bool _checked)
1147 {
1148 EASY_GLOBALS.bind_scene_and_tree_expand_status = _checked;
1149 }
1150
onHierarchyFlagChange(bool _checked)1151 void EasyMainWindow::onHierarchyFlagChange(bool _checked)
1152 {
1153 EASY_GLOBALS.only_current_thread_hierarchy = _checked;
1154 emit EASY_GLOBALS.events.hierarchyFlagChanged(_checked);
1155 }
1156
1157 //////////////////////////////////////////////////////////////////////////
1158
onExpandAllClicked(bool)1159 void EasyMainWindow::onExpandAllClicked(bool)
1160 {
1161 for (auto& block : EASY_GLOBALS.gui_blocks)
1162 block.expanded = true;
1163
1164 emit EASY_GLOBALS.events.itemsExpandStateChanged();
1165
1166 auto tree = static_cast<EasyHierarchyWidget*>(m_treeWidget->widget())->tree();
1167 const QSignalBlocker b(tree);
1168 tree->expandAll();
1169 }
1170
onCollapseAllClicked(bool)1171 void EasyMainWindow::onCollapseAllClicked(bool)
1172 {
1173 for (auto& block : EASY_GLOBALS.gui_blocks)
1174 block.expanded = false;
1175
1176 emit EASY_GLOBALS.events.itemsExpandStateChanged();
1177
1178 auto tree = static_cast<EasyHierarchyWidget*>(m_treeWidget->widget())->tree();
1179 const QSignalBlocker b(tree);
1180 tree->collapseAll();
1181 }
1182
1183 //////////////////////////////////////////////////////////////////////////
1184
onSpacingChange(int _value)1185 void EasyMainWindow::onSpacingChange(int _value)
1186 {
1187 EASY_GLOBALS.blocks_spacing = _value;
1188 refreshDiagram();
1189 }
1190
onMinSizeChange(int _value)1191 void EasyMainWindow::onMinSizeChange(int _value)
1192 {
1193 EASY_GLOBALS.blocks_size_min = _value;
1194 refreshDiagram();
1195 }
1196
onNarrowSizeChange(int _value)1197 void EasyMainWindow::onNarrowSizeChange(int _value)
1198 {
1199 EASY_GLOBALS.blocks_narrow_size = _value;
1200 refreshDiagram();
1201 }
1202
1203 //////////////////////////////////////////////////////////////////////////
1204
onFpsIntervalChange(int _value)1205 void EasyMainWindow::onFpsIntervalChange(int _value)
1206 {
1207 EASY_GLOBALS.fps_timer_interval = _value;
1208
1209 if (m_fpsRequestTimer.isActive())
1210 m_fpsRequestTimer.stop();
1211
1212 if (EASY_GLOBALS.connected)
1213 m_fpsRequestTimer.start(_value);
1214 }
1215
onFpsHistoryChange(int _value)1216 void EasyMainWindow::onFpsHistoryChange(int _value)
1217 {
1218 EASY_GLOBALS.max_fps_history = _value;
1219 }
1220
onFpsMonitorLineWidthChange(int _value)1221 void EasyMainWindow::onFpsMonitorLineWidthChange(int _value)
1222 {
1223 EASY_GLOBALS.fps_widget_line_width = _value;
1224 }
1225
1226 //////////////////////////////////////////////////////////////////////////
1227
onEditBlocksClicked(bool)1228 void EasyMainWindow::onEditBlocksClicked(bool)
1229 {
1230 if (m_descTreeDialog != nullptr)
1231 {
1232 m_descTreeDialog->raise();
1233 return;
1234 }
1235
1236 m_descTreeDialog = new QDialog();
1237 m_descTreeDialog->setAttribute(Qt::WA_DeleteOnClose, true);
1238 m_descTreeDialog->setWindowTitle(EASY_DEFAULT_WINDOW_TITLE);
1239 m_descTreeDialog->resize(800, 600);
1240 connect(m_descTreeDialog, &QDialog::finished, this, &This::onDescTreeDialogClose);
1241
1242 auto l = new QVBoxLayout(m_descTreeDialog);
1243 m_dialogDescTree = new EasyDescWidget(m_descTreeDialog);
1244 l->addWidget(m_dialogDescTree);
1245 m_descTreeDialog->setLayout(l);
1246
1247 m_dialogDescTree->build();
1248 m_descTreeDialog->show();
1249 }
1250
onDescTreeDialogClose(int)1251 void EasyMainWindow::onDescTreeDialogClose(int)
1252 {
1253 disconnect(m_descTreeDialog, &QDialog::finished, this, &This::onDescTreeDialogClose);
1254 m_dialogDescTree = nullptr;
1255 m_descTreeDialog = nullptr;
1256 }
1257
1258 //////////////////////////////////////////////////////////////////////////
1259
closeEvent(QCloseEvent * close_event)1260 void EasyMainWindow::closeEvent(QCloseEvent* close_event)
1261 {
1262 if (m_bNetworkFileRegime)
1263 {
1264 // Warn user about unsaved network information and suggest to save
1265 if (QMessageBox::Yes == QMessageBox::question(this, "Unsaved session", "You have unsaved data!\nSave before exit?", QMessageBox::Yes, QMessageBox::No))
1266 {
1267 onSaveFileClicked(true);
1268 }
1269 }
1270
1271 saveSettingsAndGeometry();
1272
1273 if (m_descTreeDialog != nullptr)
1274 {
1275 m_descTreeDialog->reject();
1276 m_descTreeDialog = nullptr;
1277 m_dialogDescTree = nullptr;
1278 }
1279
1280 Parent::closeEvent(close_event);
1281 }
1282
1283 //////////////////////////////////////////////////////////////////////////
1284
loadSettings()1285 void EasyMainWindow::loadSettings()
1286 {
1287 QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
1288 settings.beginGroup("main");
1289
1290 auto last_files = settings.value("last_files");
1291 if (!last_files.isNull())
1292 m_lastFiles = last_files.toStringList();
1293
1294 auto last_addr = settings.value("ip_address");
1295 if (!last_addr.isNull())
1296 m_lastAddress = last_addr.toString();
1297
1298 auto last_port = settings.value("port");
1299 if (!last_port.isNull())
1300 m_lastPort = (uint16_t)last_port.toUInt();
1301
1302
1303 auto val = settings.value("chrono_text_position");
1304 if (!val.isNull())
1305 EASY_GLOBALS.chrono_text_position = static_cast<::profiler_gui::ChronometerTextPosition>(val.toInt());
1306
1307 val = settings.value("time_units");
1308 if (!val.isNull())
1309 EASY_GLOBALS.time_units = static_cast<::profiler_gui::TimeUnits>(val.toInt());
1310
1311
1312 val = settings.value("frame_time");
1313 if (!val.isNull())
1314 EASY_GLOBALS.frame_time = val.toFloat();
1315
1316 val = settings.value("blocks_spacing");
1317 if (!val.isNull())
1318 EASY_GLOBALS.blocks_spacing = val.toInt();
1319
1320 val = settings.value("blocks_size_min");
1321 if (!val.isNull())
1322 EASY_GLOBALS.blocks_size_min = val.toInt();
1323
1324 val = settings.value("blocks_narrow_size");
1325 if (!val.isNull())
1326 EASY_GLOBALS.blocks_narrow_size = val.toInt();
1327
1328
1329 auto flag = settings.value("draw_graphics_items_borders");
1330 if (!flag.isNull())
1331 EASY_GLOBALS.draw_graphics_items_borders = flag.toBool();
1332
1333 flag = settings.value("hide_narrow_children");
1334 if (!flag.isNull())
1335 EASY_GLOBALS.hide_narrow_children = flag.toBool();
1336
1337 flag = settings.value("hide_minsize_blocks");
1338 if (!flag.isNull())
1339 EASY_GLOBALS.hide_minsize_blocks = flag.toBool();
1340
1341 flag = settings.value("collapse_items_on_tree_close");
1342 if (!flag.isNull())
1343 EASY_GLOBALS.collapse_items_on_tree_close = flag.toBool();
1344
1345 flag = settings.value("all_items_expanded_by_default");
1346 if (!flag.isNull())
1347 EASY_GLOBALS.all_items_expanded_by_default = flag.toBool();
1348
1349 flag = settings.value("only_current_thread_hierarchy");
1350 if (!flag.isNull())
1351 EASY_GLOBALS.only_current_thread_hierarchy = flag.toBool();
1352
1353 flag = settings.value("enable_zero_length");
1354 if (!flag.isNull())
1355 EASY_GLOBALS.enable_zero_length = flag.toBool();
1356
1357 flag = settings.value("add_zero_blocks_to_hierarchy");
1358 if (!flag.isNull())
1359 EASY_GLOBALS.add_zero_blocks_to_hierarchy = flag.toBool();
1360
1361
1362 flag = settings.value("highlight_blocks_with_same_id");
1363 if (!flag.isNull())
1364 EASY_GLOBALS.highlight_blocks_with_same_id = flag.toBool();
1365
1366 flag = settings.value("bind_scene_and_tree_expand_status");
1367 if (!flag.isNull())
1368 EASY_GLOBALS.bind_scene_and_tree_expand_status = flag.toBool();
1369
1370 flag = settings.value("selecting_block_changes_thread");
1371 if (!flag.isNull())
1372 EASY_GLOBALS.selecting_block_changes_thread = flag.toBool();
1373
1374 flag = settings.value("enable_event_indicators");
1375 if (!flag.isNull())
1376 EASY_GLOBALS.enable_event_markers = flag.toBool();
1377
1378 flag = settings.value("auto_adjust_histogram_height");
1379 if (!flag.isNull())
1380 EASY_GLOBALS.auto_adjust_histogram_height = flag.toBool();
1381
1382 flag = settings.value("display_only_frames_on_histogram");
1383 if (!flag.isNull())
1384 EASY_GLOBALS.display_only_frames_on_histogram = flag.toBool();
1385
1386 flag = settings.value("use_decorated_thread_name");
1387 if (!flag.isNull())
1388 EASY_GLOBALS.use_decorated_thread_name = flag.toBool();
1389
1390 flag = settings.value("hex_thread_id");
1391 if (!flag.isNull())
1392 EASY_GLOBALS.hex_thread_id = flag.toBool();
1393
1394 flag = settings.value("fps_timer_interval");
1395 if (!flag.isNull())
1396 EASY_GLOBALS.fps_timer_interval = flag.toInt();
1397
1398 flag = settings.value("max_fps_history");
1399 if (!flag.isNull())
1400 EASY_GLOBALS.max_fps_history = flag.toInt();
1401
1402 flag = settings.value("fps_widget_line_width");
1403 if (!flag.isNull())
1404 EASY_GLOBALS.fps_widget_line_width = flag.toInt();
1405
1406 flag = settings.value("enable_statistics");
1407 if (!flag.isNull())
1408 EASY_GLOBALS.enable_statistics = flag.toBool();
1409
1410 QString encoding = settings.value("encoding", "UTF-8").toString();
1411 auto default_codec_mib = QTextCodec::codecForName(encoding.toStdString().c_str())->mibEnum();
1412 auto default_codec = QTextCodec::codecForMib(default_codec_mib);
1413 QTextCodec::setCodecForLocale(default_codec);
1414
1415 auto theme = settings.value("theme");
1416 if (theme.isValid())
1417 {
1418 EASY_GLOBALS.theme = m_theme = theme.toString();
1419 }
1420 else
1421 {
1422 m_theme = EASY_GLOBALS.theme;
1423 }
1424
1425 settings.endGroup();
1426 }
1427
loadGeometry()1428 void EasyMainWindow::loadGeometry()
1429 {
1430 QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
1431 settings.beginGroup("main");
1432
1433 auto geometry = settings.value("geometry").toByteArray();
1434 if (!geometry.isEmpty())
1435 restoreGeometry(geometry);
1436
1437 auto state = settings.value("windowState").toByteArray();
1438 if (!state.isEmpty())
1439 restoreState(state);
1440
1441 settings.endGroup();
1442 }
1443
saveSettingsAndGeometry()1444 void EasyMainWindow::saveSettingsAndGeometry()
1445 {
1446 QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
1447 settings.beginGroup("main");
1448
1449 settings.setValue("geometry", this->saveGeometry());
1450 settings.setValue("windowState", this->saveState());
1451 settings.setValue("last_files", m_lastFiles);
1452 settings.setValue("ip_address", m_lastAddress);
1453 settings.setValue("port", (quint32)m_lastPort);
1454 settings.setValue("chrono_text_position", static_cast<int>(EASY_GLOBALS.chrono_text_position));
1455 settings.setValue("time_units", static_cast<int>(EASY_GLOBALS.time_units));
1456 settings.setValue("frame_time", EASY_GLOBALS.frame_time);
1457 settings.setValue("blocks_spacing", EASY_GLOBALS.blocks_spacing);
1458 settings.setValue("blocks_size_min", EASY_GLOBALS.blocks_size_min);
1459 settings.setValue("blocks_narrow_size", EASY_GLOBALS.blocks_narrow_size);
1460 settings.setValue("draw_graphics_items_borders", EASY_GLOBALS.draw_graphics_items_borders);
1461 settings.setValue("hide_narrow_children", EASY_GLOBALS.hide_narrow_children);
1462 settings.setValue("hide_minsize_blocks", EASY_GLOBALS.hide_minsize_blocks);
1463 settings.setValue("collapse_items_on_tree_close", EASY_GLOBALS.collapse_items_on_tree_close);
1464 settings.setValue("all_items_expanded_by_default", EASY_GLOBALS.all_items_expanded_by_default);
1465 settings.setValue("only_current_thread_hierarchy", EASY_GLOBALS.only_current_thread_hierarchy);
1466 settings.setValue("enable_zero_length", EASY_GLOBALS.enable_zero_length);
1467 settings.setValue("add_zero_blocks_to_hierarchy", EASY_GLOBALS.add_zero_blocks_to_hierarchy);
1468 settings.setValue("highlight_blocks_with_same_id", EASY_GLOBALS.highlight_blocks_with_same_id);
1469 settings.setValue("bind_scene_and_tree_expand_status", EASY_GLOBALS.bind_scene_and_tree_expand_status);
1470 settings.setValue("selecting_block_changes_thread", EASY_GLOBALS.selecting_block_changes_thread);
1471 settings.setValue("enable_event_indicators", EASY_GLOBALS.enable_event_markers);
1472 settings.setValue("auto_adjust_histogram_height", EASY_GLOBALS.auto_adjust_histogram_height);
1473 settings.setValue("display_only_frames_on_histogram", EASY_GLOBALS.display_only_frames_on_histogram);
1474 settings.setValue("use_decorated_thread_name", EASY_GLOBALS.use_decorated_thread_name);
1475 settings.setValue("hex_thread_id", EASY_GLOBALS.hex_thread_id);
1476 settings.setValue("enable_statistics", EASY_GLOBALS.enable_statistics);
1477 settings.setValue("fps_timer_interval", EASY_GLOBALS.fps_timer_interval);
1478 settings.setValue("max_fps_history", EASY_GLOBALS.max_fps_history);
1479 settings.setValue("fps_widget_line_width", EASY_GLOBALS.fps_widget_line_width);
1480 settings.setValue("encoding", QTextCodec::codecForLocale()->name());
1481 settings.setValue("theme", m_theme);
1482
1483 settings.endGroup();
1484 }
1485
destroyProgressDialog()1486 void EasyMainWindow::destroyProgressDialog()
1487 {
1488 if (m_progress != nullptr)
1489 {
1490 m_progress->setValue(100);
1491 m_progress->deleteLater();
1492 m_progress = nullptr;
1493 }
1494 }
1495
createProgressDialog(const QString & text)1496 void EasyMainWindow::createProgressDialog(const QString& text)
1497 {
1498 destroyProgressDialog();
1499
1500 m_progress = new QProgressDialog(text, QStringLiteral("Cancel"), 0, 100, this);
1501 connect(m_progress, &QProgressDialog::canceled, this, &This::onFileReaderCancel);
1502
1503 m_progress->setFixedWidth(300);
1504 m_progress->setWindowTitle(EASY_DEFAULT_WINDOW_TITLE);
1505 m_progress->setModal(true);
1506 m_progress->setValue(0);
1507 m_progress->show();
1508 }
1509
setDisconnected(bool _showMessage)1510 void EasyMainWindow::setDisconnected(bool _showMessage)
1511 {
1512 if (m_fpsRequestTimer.isActive())
1513 m_fpsRequestTimer.stop();
1514
1515 if (_showMessage)
1516 QMessageBox::warning(this, "Warning", "Connection was lost", QMessageBox::Close);
1517
1518 EASY_GLOBALS.connected = false;
1519 m_captureAction->setEnabled(false);
1520 m_connectAction->setIcon(QIcon(imagePath("connect")));
1521 m_connectAction->setText(tr("Connect"));
1522
1523 m_eventTracingEnableAction->setEnabled(false);
1524 m_eventTracingPriorityAction->setEnabled(false);
1525
1526 m_addressEdit->setEnabled(true);
1527 m_portEdit->setEnabled(true);
1528
1529 emit EASY_GLOBALS.events.connectionChanged(false);
1530
1531 }
1532
1533 //////////////////////////////////////////////////////////////////////////
1534
onFrameTimeRequestTimeout()1535 void EasyMainWindow::onFrameTimeRequestTimeout()
1536 {
1537 if (EASY_GLOBALS.fps_enabled && EASY_GLOBALS.connected && (m_listener.regime() == LISTENER_IDLE || m_listener.regime() == LISTENER_CAPTURE))
1538 {
1539 if (m_listener.requestFrameTime())
1540 {
1541 QTimer::singleShot(100, this, &This::checkFrameTimeReady);
1542 }
1543 else if (!m_listener.connected())
1544 {
1545 m_listener.closeSocket();
1546 setDisconnected();
1547 }
1548 }
1549 }
1550
checkFrameTimeReady()1551 void EasyMainWindow::checkFrameTimeReady()
1552 {
1553 if (EASY_GLOBALS.fps_enabled && EASY_GLOBALS.connected && (m_listener.regime() == LISTENER_IDLE || m_listener.regime() == LISTENER_CAPTURE))
1554 {
1555 uint32_t maxTime = 0, avgTime = 0;
1556 if (m_listener.frameTime(maxTime, avgTime))
1557 {
1558 static_cast<EasyFrameRateViewer*>(m_fpsViewer->widget())->addPoint(maxTime, avgTime);
1559 }
1560 else if (m_fpsRequestTimer.isActive())
1561 {
1562 QTimer::singleShot(100, this, &This::checkFrameTimeReady);
1563 }
1564 }
1565 }
1566
1567 //////////////////////////////////////////////////////////////////////////
1568
onListenerTimerTimeout()1569 void EasyMainWindow::onListenerTimerTimeout()
1570 {
1571 if (!m_listener.connected())
1572 {
1573 if (m_listener.regime() == LISTENER_CAPTURE_RECEIVE)
1574 m_listener.finalizeCapture();
1575 if (m_listenerDialog)
1576 m_listenerDialog->reject();
1577 }
1578 else if (m_listener.regime() == LISTENER_CAPTURE_RECEIVE)
1579 {
1580 if (m_listener.captured())
1581 {
1582 if (m_listenerTimer.isActive())
1583 m_listenerTimer.stop();
1584
1585 m_listener.finalizeCapture();
1586
1587 m_listenerDialog->accept();
1588 m_listenerDialog = nullptr;
1589
1590 if (m_listener.size() != 0)
1591 {
1592 readStream(m_listener.data());
1593 m_listener.clearData();
1594 }
1595 }
1596 }
1597 }
1598
onListenerDialogClose(int _result)1599 void EasyMainWindow::onListenerDialogClose(int _result)
1600 {
1601 if (m_listener.regime() != LISTENER_CAPTURE_RECEIVE || !m_listener.connected())
1602 {
1603 if (m_listenerTimer.isActive())
1604 m_listenerTimer.stop();
1605 }
1606
1607 disconnect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose);
1608 m_listenerDialog = nullptr;
1609
1610 switch (m_listener.regime())
1611 {
1612 case LISTENER_CAPTURE:
1613 {
1614 m_listenerDialog = new QMessageBox(QMessageBox::Information, "Receiving data...", "This process may take some time.", QMessageBox::Cancel, this);
1615 m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true);
1616 m_listenerDialog->show();
1617
1618 m_listener.stopCapture();
1619
1620 if (m_listener.regime() != LISTENER_CAPTURE_RECEIVE)
1621 {
1622 m_listenerDialog->reject();
1623 m_listenerDialog = nullptr;
1624 }
1625 else
1626 {
1627 connect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose);
1628 m_listenerTimer.start(250);
1629 }
1630
1631 break;
1632 }
1633
1634 case LISTENER_CAPTURE_RECEIVE:
1635 {
1636 if (!m_listener.captured())
1637 {
1638 if (_result == QDialog::Accepted)
1639 {
1640 m_listenerDialog = new QMessageBox(QMessageBox::Information, "Receiving data...", "This process may take some time.", QMessageBox::Cancel, this);
1641 connect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose);
1642 m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true);
1643 m_listenerDialog->show();
1644 }
1645 else
1646 {
1647 m_listener.finalizeCapture();
1648 m_listener.clearData();
1649
1650 if (m_listener.connected())
1651 {
1652 // make reconnect to clear socket buffers
1653 const std::string address = m_listener.address();
1654 const auto port = m_listener.port();
1655
1656 profiler::net::EasyProfilerStatus reply(false, false, false);
1657 if (m_listener.reconnect(address.c_str(), port, reply))
1658 {
1659 disconnect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange);
1660 disconnect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange);
1661
1662 m_eventTracingEnableAction->setChecked(reply.isEventTracingEnabled);
1663 m_eventTracingPriorityAction->setChecked(reply.isLowPriorityEventTracing);
1664
1665 connect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange);
1666 connect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange);
1667
1668 if (reply.isProfilerEnabled)
1669 {
1670 // Connected application is already profiling.
1671 // Show capture dialog immediately
1672 onCaptureClicked(true);
1673 }
1674 }
1675 }
1676 }
1677
1678 break;
1679 }
1680
1681 if (m_listenerTimer.isActive())
1682 m_listenerTimer.stop();
1683
1684 m_listener.finalizeCapture();
1685
1686 if (m_listener.size() != 0)
1687 {
1688 readStream(m_listener.data());
1689 m_listener.clearData();
1690 }
1691
1692 break;
1693 }
1694
1695 case LISTENER_DESCRIBE:
1696 {
1697 break;
1698 }
1699
1700 default:
1701 return;
1702 }
1703
1704 if (!m_listener.connected())
1705 {
1706 m_listener.closeSocket();
1707 setDisconnected();
1708 }
1709 }
1710
1711
1712 //////////////////////////////////////////////////////////////////////////
1713
onFileReaderTimeout()1714 void EasyMainWindow::onFileReaderTimeout()
1715 {
1716 if (m_reader.done())
1717 {
1718 auto nblocks = m_reader.size();
1719 if (nblocks != 0)
1720 {
1721 static_cast<EasyHierarchyWidget*>(m_treeWidget->widget())->clear(true);
1722
1723 ::profiler::SerializedData serialized_blocks;
1724 ::profiler::SerializedData serialized_descriptors;
1725 ::profiler::descriptors_list_t descriptors;
1726 ::profiler::blocks_t blocks;
1727 ::profiler::thread_blocks_tree_t threads_map;
1728 QString filename;
1729 uint32_t descriptorsNumberInFile = 0;
1730 uint32_t version = 0;
1731 m_reader.get(serialized_blocks, serialized_descriptors, descriptors, blocks, threads_map, descriptorsNumberInFile, version, filename);
1732
1733 if (threads_map.size() > 0xff)
1734 {
1735 if (m_reader.isFile())
1736 qWarning() << "Warning: file " << filename << " contains " << threads_map.size() << " threads!";
1737 else
1738 qWarning() << "Warning: input stream contains " << threads_map.size() << " threads!";
1739 qWarning() << "Warning: Currently, maximum number of displayed threads is 255! Some threads will not be displayed.";
1740 }
1741
1742 m_bNetworkFileRegime = !m_reader.isFile();
1743 if (!m_bNetworkFileRegime)
1744 {
1745 auto index = m_lastFiles.indexOf(filename, 0);
1746 if (index == -1)
1747 {
1748 // This file is totally new. Add it to the list.
1749 addFileToList(filename);
1750 }
1751 else
1752 {
1753 if (index != 0)
1754 {
1755 // This file has been already loaded. Move it to the front.
1756 m_lastFiles.move(index, 0);
1757 auto fileActions = m_loadActionMenu->actions();
1758 auto action = fileActions.at(index);
1759 m_loadActionMenu->removeAction(action);
1760 m_loadActionMenu->insertAction(fileActions.front(), action);
1761 }
1762
1763 m_bOpenedCacheFile = filename.contains(NETWORK_CACHE_FILE);
1764
1765 if (m_bOpenedCacheFile)
1766 setWindowTitle(QString(EASY_DEFAULT_WINDOW_TITLE " - [%1] - UNSAVED network cache file").arg(filename));
1767 else
1768 setWindowTitle(QString(EASY_DEFAULT_WINDOW_TITLE " - [%1]").arg(filename));
1769 }
1770 }
1771 else
1772 {
1773 m_bOpenedCacheFile = false;
1774 setWindowTitle(EASY_DEFAULT_WINDOW_TITLE " - UNSAVED network cache");
1775 }
1776
1777 m_serializedBlocks = ::std::move(serialized_blocks);
1778 m_serializedDescriptors = ::std::move(serialized_descriptors);
1779 m_descriptorsNumberInFile = descriptorsNumberInFile;
1780 EASY_GLOBALS.selected_thread = 0;
1781 EASY_GLOBALS.version = version;
1782 ::profiler_gui::set_max(EASY_GLOBALS.selected_block);
1783 ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id);
1784 EASY_GLOBALS.profiler_blocks.swap(threads_map);
1785 EASY_GLOBALS.descriptors.swap(descriptors);
1786
1787 EASY_GLOBALS.gui_blocks.clear();
1788 EASY_GLOBALS.gui_blocks.resize(nblocks);
1789 memset(EASY_GLOBALS.gui_blocks.data(), 0, sizeof(::profiler_gui::EasyBlock) * nblocks);
1790 for (decltype(nblocks) i = 0; i < nblocks; ++i) {
1791 auto& guiblock = EASY_GLOBALS.gui_blocks[i];
1792 guiblock.tree = ::std::move(blocks[i]);
1793 #ifdef EASY_TREE_WIDGET__USE_VECTOR
1794 ::profiler_gui::set_max(guiblock.tree_item);
1795 #endif
1796 }
1797
1798 static_cast<EasyGraphicsViewWidget*>(m_graphicsView->widget())->view()->setTree(EASY_GLOBALS.profiler_blocks);
1799
1800 #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0
1801 static_cast<EasyDescWidget*>(m_descTreeWidget->widget())->build();
1802 #endif
1803 if (m_dialogDescTree != nullptr)
1804 m_dialogDescTree->build();
1805
1806 m_saveAction->setEnabled(true);
1807 m_deleteAction->setEnabled(true);
1808 }
1809 else
1810 {
1811 QMessageBox::warning(this, "Warning", QString("Cannot read profiled blocks.\n\nReason:\n%1").arg(m_reader.getError()), QMessageBox::Close);
1812
1813 if (m_reader.isFile())
1814 {
1815 auto index = m_lastFiles.indexOf(m_reader.filename(), 0);
1816 if (index >= 0)
1817 {
1818 // Remove unexisting file from list
1819 m_lastFiles.removeAt(index);
1820 auto action = m_loadActionMenu->actions().at(index);
1821 m_loadActionMenu->removeAction(action);
1822 delete action;
1823 }
1824 }
1825 }
1826
1827 m_reader.interrupt();
1828
1829 m_readerTimer.stop();
1830 destroyProgressDialog();
1831
1832 if (EASY_GLOBALS.all_items_expanded_by_default)
1833 {
1834 onExpandAllClicked(true);
1835 }
1836 }
1837 else if (m_progress != nullptr)
1838 {
1839 m_progress->setValue(m_reader.progress());
1840 }
1841 }
1842
onFileReaderCancel()1843 void EasyMainWindow::onFileReaderCancel()
1844 {
1845 m_readerTimer.stop();
1846 m_reader.interrupt();
1847 destroyProgressDialog();
1848 }
1849
1850 //////////////////////////////////////////////////////////////////////////
1851
EasyFileReader()1852 EasyFileReader::EasyFileReader()
1853 {
1854
1855 }
1856
~EasyFileReader()1857 EasyFileReader::~EasyFileReader()
1858 {
1859 interrupt();
1860 }
1861
isFile() const1862 const bool EasyFileReader::isFile() const
1863 {
1864 return m_isFile;
1865 }
1866
done() const1867 bool EasyFileReader::done() const
1868 {
1869 return m_bDone.load(::std::memory_order_acquire);
1870 }
1871
progress() const1872 int EasyFileReader::progress() const
1873 {
1874 return m_progress.load(::std::memory_order_acquire);
1875 }
1876
size() const1877 unsigned int EasyFileReader::size() const
1878 {
1879 return m_size.load(::std::memory_order_acquire);
1880 }
1881
filename() const1882 const QString& EasyFileReader::filename() const
1883 {
1884 return m_filename;
1885 }
1886
load(const QString & _filename)1887 void EasyFileReader::load(const QString& _filename)
1888 {
1889 interrupt();
1890
1891 m_isFile = true;
1892 m_filename = _filename;
1893 m_thread = ::std::thread([this](bool _enableStatistics) {
1894 m_size.store(fillTreesFromFile(m_progress, m_filename.toStdString().c_str(), m_serializedBlocks, m_serializedDescriptors,
1895 m_descriptors, m_blocks, m_blocksTree, m_descriptorsNumberInFile, m_version, _enableStatistics, m_errorMessage), ::std::memory_order_release);
1896 m_progress.store(100, ::std::memory_order_release);
1897 m_bDone.store(true, ::std::memory_order_release);
1898 }, EASY_GLOBALS.enable_statistics);
1899 }
1900
load(::std::stringstream & _stream)1901 void EasyFileReader::load(::std::stringstream& _stream)
1902 {
1903 interrupt();
1904
1905 m_isFile = false;
1906 m_filename.clear();
1907
1908 #if defined(__GNUC__) && __GNUC__ < 5 && !defined(__llvm__)
1909 // gcc 4 has a known bug which has been solved in gcc 5:
1910 // std::stringstream has no swap() method :(
1911 // have to copy all contents... Use gcc 5 or higher!
1912 #pragma message "Warning: in gcc 4 and lower std::stringstream has no swap()! Memory consumption may increase! Better use gcc 5 or higher instead."
1913 m_stream.str(_stream.str());
1914 #else
1915 m_stream.swap(_stream);
1916 #endif
1917
1918 m_thread = ::std::thread([this](bool _enableStatistics) {
1919 ::std::ofstream cache_file(NETWORK_CACHE_FILE, ::std::fstream::binary);
1920 if (cache_file.is_open()) {
1921 cache_file << m_stream.str();
1922 cache_file.close();
1923 }
1924 m_size.store(fillTreesFromStream(m_progress, m_stream, m_serializedBlocks, m_serializedDescriptors, m_descriptors,
1925 m_blocks, m_blocksTree, m_descriptorsNumberInFile, m_version, _enableStatistics, m_errorMessage), ::std::memory_order_release);
1926 m_progress.store(100, ::std::memory_order_release);
1927 m_bDone.store(true, ::std::memory_order_release);
1928 }, EASY_GLOBALS.enable_statistics);
1929 }
1930
interrupt()1931 void EasyFileReader::interrupt()
1932 {
1933 m_progress.store(-100, ::std::memory_order_release);
1934 if (m_thread.joinable())
1935 m_thread.join();
1936
1937 m_bDone.store(false, ::std::memory_order_release);
1938 m_progress.store(0, ::std::memory_order_release);
1939 m_size.store(0, ::std::memory_order_release);
1940 m_serializedBlocks.clear();
1941 m_serializedDescriptors.clear();
1942 m_descriptors.clear();
1943 m_blocks.clear();
1944 m_blocksTree.clear();
1945 m_descriptorsNumberInFile = 0;
1946 m_version = 0;
1947
1948 clear_stream(m_stream);
1949 clear_stream(m_errorMessage);
1950 }
1951
get(::profiler::SerializedData & _serializedBlocks,::profiler::SerializedData & _serializedDescriptors,::profiler::descriptors_list_t & _descriptors,::profiler::blocks_t & _blocks,::profiler::thread_blocks_tree_t & _tree,uint32_t & _descriptorsNumberInFile,uint32_t & _version,QString & _filename)1952 void EasyFileReader::get(::profiler::SerializedData& _serializedBlocks, ::profiler::SerializedData& _serializedDescriptors,
1953 ::profiler::descriptors_list_t& _descriptors, ::profiler::blocks_t& _blocks,
1954 ::profiler::thread_blocks_tree_t& _tree, uint32_t& _descriptorsNumberInFile, uint32_t& _version, QString& _filename)
1955 {
1956 if (done())
1957 {
1958 m_serializedBlocks.swap(_serializedBlocks);
1959 m_serializedDescriptors.swap(_serializedDescriptors);
1960 ::profiler::descriptors_list_t(::std::move(m_descriptors)).swap(_descriptors);
1961 m_blocks.swap(_blocks);
1962 m_blocksTree.swap(_tree);
1963 m_filename.swap(_filename);
1964 _descriptorsNumberInFile = m_descriptorsNumberInFile;
1965 _version = m_version;
1966 }
1967 }
1968
getError()1969 QString EasyFileReader::getError()
1970 {
1971 return QString(m_errorMessage.str().c_str());
1972 }
1973
1974 //////////////////////////////////////////////////////////////////////////
1975
onEventTracingPriorityChange(bool _checked)1976 void EasyMainWindow::onEventTracingPriorityChange(bool _checked)
1977 {
1978 if (EASY_GLOBALS.connected)
1979 m_listener.send(profiler::net::BoolMessage(profiler::net::MessageType::Change_Event_Tracing_Priority, _checked));
1980 }
1981
onEventTracingEnableChange(bool _checked)1982 void EasyMainWindow::onEventTracingEnableChange(bool _checked)
1983 {
1984 if (EASY_GLOBALS.connected)
1985 m_listener.send(profiler::net::BoolMessage(profiler::net::MessageType::Change_Event_Tracing_Status, _checked));
1986 }
1987
1988 //////////////////////////////////////////////////////////////////////////
1989
onFrameTimeEditFinish()1990 void EasyMainWindow::onFrameTimeEditFinish()
1991 {
1992 auto text = m_frameTimeEdit->text();
1993 if (text.contains(QChar(',')))
1994 {
1995 text.remove(QChar('.')).replace(QChar(','), QChar('.'));
1996 m_frameTimeEdit->setText(text);
1997 }
1998
1999 EASY_GLOBALS.frame_time = text.toFloat() * 1e3f;
2000
2001 disconnect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::expectedFrameTimeChanged,
2002 this, &This::onFrameTimeChanged);
2003
2004 emit EASY_GLOBALS.events.expectedFrameTimeChanged();
2005
2006 connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::expectedFrameTimeChanged,
2007 this, &This::onFrameTimeChanged);
2008 }
2009
onFrameTimeChanged()2010 void EasyMainWindow::onFrameTimeChanged()
2011 {
2012 m_frameTimeEdit->setText(QString::number(EASY_GLOBALS.frame_time * 1e-3));
2013 }
2014
2015 //////////////////////////////////////////////////////////////////////////
2016
onConnectClicked(bool)2017 void EasyMainWindow::onConnectClicked(bool)
2018 {
2019 if (EASY_GLOBALS.connected)
2020 {
2021 // Disconnect if already connected
2022 m_listener.disconnect();
2023 setDisconnected(false);
2024 return;
2025 }
2026
2027 QString address = m_addressEdit->text();
2028 const decltype(m_lastPort) port = m_portEdit->text().toUShort();
2029
2030 const bool isSameAddress = (EASY_GLOBALS.connected && m_listener.port() == port &&
2031 address.toStdString() == m_listener.address());
2032
2033 profiler::net::EasyProfilerStatus reply(false, false, false);
2034 if (!m_listener.connect(address.toStdString().c_str(), port, reply))
2035 {
2036 QMessageBox::warning(this, "Warning", QString("Cannot connect to %1").arg(address), QMessageBox::Close);
2037 if (EASY_GLOBALS.connected)
2038 {
2039 m_listener.closeSocket();
2040 setDisconnected(false);
2041 }
2042
2043 if (!isSameAddress)
2044 {
2045 m_lastAddress = ::std::move(address);
2046 m_lastPort = port;
2047 }
2048
2049 return;
2050 }
2051
2052 m_lastAddress = ::std::move(address);
2053 m_lastPort = port;
2054
2055 qInfo() << "Connected successfully";
2056 EASY_GLOBALS.connected = true;
2057 m_captureAction->setEnabled(true);
2058 m_connectAction->setIcon(QIcon(imagePath("connected")));
2059 m_connectAction->setText(tr("Disconnect"));
2060
2061 if (m_fpsViewer->isVisible())
2062 static_cast<EasyFrameRateViewer*>(m_fpsViewer->widget())->clear();
2063
2064 if (!m_fpsRequestTimer.isActive())
2065 m_fpsRequestTimer.start(EASY_GLOBALS.fps_timer_interval);
2066
2067 disconnect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange);
2068 disconnect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange);
2069
2070 m_eventTracingEnableAction->setEnabled(true);
2071 m_eventTracingPriorityAction->setEnabled(true);
2072
2073 m_eventTracingEnableAction->setChecked(reply.isEventTracingEnabled);
2074 m_eventTracingPriorityAction->setChecked(reply.isLowPriorityEventTracing);
2075
2076 connect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange);
2077 connect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange);
2078
2079 m_addressEdit->setEnabled(false);
2080 m_portEdit->setEnabled(false);
2081
2082 emit EASY_GLOBALS.events.connectionChanged(true);
2083
2084 if (reply.isProfilerEnabled)
2085 {
2086 // Connected application is already profiling.
2087 // Show capture dialog immediately
2088 onCaptureClicked(true);
2089 }
2090 }
2091
onCaptureClicked(bool)2092 void EasyMainWindow::onCaptureClicked(bool)
2093 {
2094 if (!EASY_GLOBALS.connected)
2095 {
2096 QMessageBox::warning(this, "Warning", "No connection with profiling app", QMessageBox::Close);
2097 return;
2098 }
2099
2100 if (m_listener.regime() != LISTENER_IDLE)
2101 {
2102 if (m_listener.regime() == LISTENER_CAPTURE || m_listener.regime() == LISTENER_CAPTURE_RECEIVE)
2103 QMessageBox::warning(this, "Warning", "Already capturing frames.\nFinish old capturing session first.", QMessageBox::Close);
2104 else
2105 QMessageBox::warning(this, "Warning", "Capturing blocks description.\nFinish old capturing session first.", QMessageBox::Close);
2106 return;
2107 }
2108
2109 if (!m_listener.startCapture())
2110 {
2111 // Connection lost. Try to restore connection.
2112
2113 profiler::net::EasyProfilerStatus reply(false, false, false);
2114 if (!m_listener.connect(m_lastAddress.toStdString().c_str(), m_lastPort, reply))
2115 {
2116 m_listener.closeSocket();
2117 setDisconnected();
2118 return;
2119 }
2120
2121 if (!m_listener.startCapture())
2122 {
2123 m_listener.closeSocket();
2124 setDisconnected();
2125 return;
2126 }
2127 }
2128
2129 m_listenerTimer.start(250);
2130
2131 m_listenerDialog = new QMessageBox(QMessageBox::Information, "Capturing frames...", "Close this dialog to stop capturing.", QMessageBox::NoButton, this);
2132
2133 auto button = new QToolButton(m_listenerDialog);
2134 button->setAutoRaise(true);
2135 button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
2136 button->setIconSize(::profiler_gui::ICONS_SIZE);
2137 button->setIcon(QIcon(imagePath("stop")));
2138 button->setText("Stop");
2139 m_listenerDialog->addButton(button, QMessageBox::AcceptRole);
2140
2141 m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true);
2142 connect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose);
2143 m_listenerDialog->show();
2144 }
2145
onGetBlockDescriptionsClicked(bool)2146 void EasyMainWindow::onGetBlockDescriptionsClicked(bool)
2147 {
2148 if (!EASY_GLOBALS.connected)
2149 {
2150 QMessageBox::warning(this, "Warning", "No connection with profiling app", QMessageBox::Close);
2151 return;
2152 }
2153
2154 if (m_listener.regime() != LISTENER_IDLE)
2155 {
2156 if (m_listener.regime() == LISTENER_DESCRIBE)
2157 QMessageBox::warning(this, "Warning", "Already capturing blocks description.\nFinish old capturing session first.", QMessageBox::Close);
2158 else
2159 QMessageBox::warning(this, "Warning", "Already capturing frames.\nFinish old capturing session first.", QMessageBox::Close);
2160 return;
2161 }
2162
2163 m_listenerDialog = new QMessageBox(QMessageBox::Information, "Waiting for blocks...", "This may take some time.", QMessageBox::NoButton, this);
2164 m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true);
2165 m_listenerDialog->show();
2166
2167 m_listener.requestBlocksDescription();
2168
2169 m_listenerDialog->reject();
2170 m_listenerDialog = nullptr;
2171
2172 if (m_listener.size() != 0)
2173 {
2174 // Read descriptions from stream
2175
2176 decltype(EASY_GLOBALS.descriptors) descriptors;
2177 decltype(m_serializedDescriptors) serializedDescriptors;
2178 ::std::stringstream errorMessage;
2179 if (readDescriptionsFromStream(m_listener.data(), serializedDescriptors, descriptors, errorMessage))
2180 {
2181 // Merge old and new descriptions
2182
2183 bool cancel = false;
2184 const bool doFlush = m_descriptorsNumberInFile > descriptors.size();
2185 if (doFlush && !m_serializedBlocks.empty())
2186 {
2187 auto button = QMessageBox::question(this, "Information",
2188 QString("New blocks description number = %1\nis less than the old one = %2.\nTo avoid possible conflicts\nall profiled data will be deleted.\nContinue?")
2189 .arg(descriptors.size())
2190 .arg(m_descriptorsNumberInFile),
2191 QMessageBox::Yes, QMessageBox::No);
2192
2193 if (button == QMessageBox::Yes)
2194 clear(); // Clear all contents because new descriptors list conflicts with old one
2195 else
2196 cancel = true;
2197 }
2198
2199 if (!cancel)
2200 {
2201 if (!doFlush && m_descriptorsNumberInFile < EASY_GLOBALS.descriptors.size())
2202 {
2203 // There are dynamically added descriptors, add them to the new list too
2204
2205 auto newnumber = static_cast<decltype(m_descriptorsNumberInFile)>(descriptors.size());
2206 auto size = static_cast<decltype(m_descriptorsNumberInFile)>(EASY_GLOBALS.descriptors.size());
2207 auto diff = newnumber - size;
2208 decltype(newnumber) failnumber = 0;
2209
2210 descriptors.reserve(descriptors.size() + EASY_GLOBALS.descriptors.size() - m_descriptorsNumberInFile);
2211 for (auto i = m_descriptorsNumberInFile; i < size; ++i)
2212 {
2213 auto id = EASY_GLOBALS.descriptors[i]->id();
2214 if (id < newnumber)
2215 descriptors.push_back(descriptors[id]);
2216 else
2217 ++failnumber;
2218 }
2219
2220 if (failnumber != 0)
2221 {
2222 // There are some errors...
2223
2224 // revert changes
2225 descriptors.resize(newnumber);
2226
2227 // clear all profiled data to avoid conflicts
2228 auto button = QMessageBox::question(this, "Information",
2229 "There are errors while merging block descriptions lists.\nTo avoid possible conflicts\nall profiled data will be deleted.\nContinue?",
2230 QMessageBox::Yes, QMessageBox::No);
2231
2232 if (button == QMessageBox::Yes)
2233 clear(); // Clear all contents because new descriptors list conflicts with old one
2234 else
2235 cancel = true;
2236 }
2237
2238 if (!cancel && diff != 0)
2239 {
2240 for (auto& b : EASY_GLOBALS.gui_blocks)
2241 {
2242 if (b.tree.node->id() >= m_descriptorsNumberInFile)
2243 b.tree.node->setId(b.tree.node->id() + diff);
2244 }
2245
2246 m_descriptorsNumberInFile = newnumber;
2247 }
2248 }
2249
2250 if (!cancel)
2251 {
2252 EASY_GLOBALS.descriptors.swap(descriptors);
2253 m_serializedDescriptors.swap(serializedDescriptors);
2254 m_descriptorsNumberInFile = static_cast<uint32_t>(EASY_GLOBALS.descriptors.size());
2255
2256 if (m_descTreeDialog != nullptr)
2257 {
2258 #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0
2259 static_cast<EasyDescWidget*>(m_descTreeWidget->widget())->build();
2260 #endif
2261 m_dialogDescTree->build();
2262 m_descTreeDialog->raise();
2263 }
2264 else
2265 {
2266 onEditBlocksClicked(true);
2267 }
2268 }
2269 }
2270 }
2271 else
2272 {
2273 QMessageBox::warning(this, "Warning", QString("Cannot read blocks description from stream.\n\nReason:\n%1").arg(errorMessage.str().c_str()), QMessageBox::Close);
2274 }
2275
2276 m_listener.clearData();
2277 }
2278
2279 if (!m_listener.connected())
2280 {
2281 m_listener.closeSocket();
2282 setDisconnected();
2283 }
2284 }
2285
2286 //////////////////////////////////////////////////////////////////////////
2287
onBlockStatusChange(::profiler::block_id_t _id,::profiler::EasyBlockStatus _status)2288 void EasyMainWindow::onBlockStatusChange(::profiler::block_id_t _id, ::profiler::EasyBlockStatus _status)
2289 {
2290 if (EASY_GLOBALS.connected)
2291 m_listener.send(profiler::net::BlockStatusMessage(_id, static_cast<uint8_t>(_status)));
2292 }
2293
2294 //////////////////////////////////////////////////////////////////////////
2295
EasySocketListener()2296 EasySocketListener::EasySocketListener() : m_receivedSize(0), m_port(0), m_regime(LISTENER_IDLE)
2297 {
2298 m_bInterrupt = ATOMIC_VAR_INIT(false);
2299 m_bConnected = ATOMIC_VAR_INIT(false);
2300 m_bStopReceive = ATOMIC_VAR_INIT(false);
2301 m_bFrameTimeReady = ATOMIC_VAR_INIT(false);
2302 m_bCaptureReady = ATOMIC_VAR_INIT(false);
2303 m_frameMax = ATOMIC_VAR_INIT(0);
2304 m_frameAvg = ATOMIC_VAR_INIT(0);
2305 }
2306
~EasySocketListener()2307 EasySocketListener::~EasySocketListener()
2308 {
2309 m_bInterrupt.store(true, ::std::memory_order_release);
2310 if (m_thread.joinable())
2311 m_thread.join();
2312 }
2313
connected() const2314 bool EasySocketListener::connected() const
2315 {
2316 return m_bConnected.load(::std::memory_order_acquire);
2317 }
2318
captured() const2319 bool EasySocketListener::captured() const
2320 {
2321 return m_bCaptureReady.load(::std::memory_order_acquire);
2322 }
2323
regime() const2324 EasyListenerRegime EasySocketListener::regime() const
2325 {
2326 return m_regime;
2327 }
2328
size() const2329 uint64_t EasySocketListener::size() const
2330 {
2331 return m_receivedSize;
2332 }
2333
data()2334 ::std::stringstream& EasySocketListener::data()
2335 {
2336 return m_receivedData;
2337 }
2338
address() const2339 const ::std::string& EasySocketListener::address() const
2340 {
2341 return m_address;
2342 }
2343
port() const2344 uint16_t EasySocketListener::port() const
2345 {
2346 return m_port;
2347 }
2348
clearData()2349 void EasySocketListener::clearData()
2350 {
2351 clear_stream(m_receivedData);
2352 m_receivedSize = 0;
2353 }
2354
disconnect()2355 void EasySocketListener::disconnect()
2356 {
2357 if (connected())
2358 {
2359 m_bInterrupt.store(true, ::std::memory_order_release);
2360 if (m_thread.joinable())
2361 m_thread.join();
2362
2363 m_bConnected.store(false, ::std::memory_order_release);
2364 m_bInterrupt.store(false, ::std::memory_order_release);
2365 m_bCaptureReady.store(false, ::std::memory_order_release);
2366 m_bStopReceive.store(false, ::std::memory_order_release);
2367 }
2368
2369 m_address.clear();
2370 m_port = 0;
2371
2372 closeSocket();
2373 }
2374
closeSocket()2375 void EasySocketListener::closeSocket()
2376 {
2377 m_easySocket.flush();
2378 m_easySocket.init();
2379 }
2380
connect(const char * _ipaddress,uint16_t _port,profiler::net::EasyProfilerStatus & _reply,bool _disconnectFirst)2381 bool EasySocketListener::connect(const char* _ipaddress, uint16_t _port, profiler::net::EasyProfilerStatus& _reply, bool _disconnectFirst)
2382 {
2383 if (connected())
2384 {
2385 m_bInterrupt.store(true, ::std::memory_order_release);
2386 if (m_thread.joinable())
2387 m_thread.join();
2388
2389 m_bConnected.store(false, ::std::memory_order_release);
2390 m_bInterrupt.store(false, ::std::memory_order_release);
2391 m_bCaptureReady.store(false, ::std::memory_order_release);
2392 m_bStopReceive.store(false, ::std::memory_order_release);
2393 }
2394
2395 m_address.clear();
2396 m_port = 0;
2397
2398 if (_disconnectFirst)
2399 closeSocket();
2400
2401 int res = m_easySocket.setAddress(_ipaddress, _port);
2402 res = m_easySocket.connect();
2403
2404 const bool isConnected = res == 0;
2405 if (isConnected)
2406 {
2407 static const size_t buffer_size = sizeof(profiler::net::EasyProfilerStatus) << 1;
2408 char buffer[buffer_size] = {};
2409 int bytes = 0;
2410
2411 while (true)
2412 {
2413 bytes = m_easySocket.receive(buffer, buffer_size);
2414
2415 if (bytes == -1)
2416 {
2417 if (m_easySocket.isDisconnected())
2418 return false;
2419 bytes = 0;
2420 continue;
2421 }
2422
2423 break;
2424 }
2425
2426 if (bytes == 0)
2427 {
2428 m_address = _ipaddress;
2429 m_port = _port;
2430 m_bConnected.store(isConnected, ::std::memory_order_release);
2431 return isConnected;
2432 }
2433
2434 size_t seek = bytes;
2435 while (seek < sizeof(profiler::net::EasyProfilerStatus))
2436 {
2437 bytes = m_easySocket.receive(buffer + seek, buffer_size - seek);
2438
2439 if (bytes == -1)
2440 {
2441 if (m_easySocket.isDisconnected())
2442 return false;
2443 break;
2444 }
2445
2446 seek += bytes;
2447 }
2448
2449 auto message = reinterpret_cast<const ::profiler::net::EasyProfilerStatus*>(buffer);
2450 if (message->isEasyNetMessage() && message->type == profiler::net::MessageType::Connection_Accepted)
2451 _reply = *message;
2452
2453 m_address = _ipaddress;
2454 m_port = _port;
2455 }
2456
2457 m_bConnected.store(isConnected, ::std::memory_order_release);
2458 return isConnected;
2459 }
2460
reconnect(const char * _ipaddress,uint16_t _port,::profiler::net::EasyProfilerStatus & _reply)2461 bool EasySocketListener::reconnect(const char* _ipaddress, uint16_t _port, ::profiler::net::EasyProfilerStatus& _reply)
2462 {
2463 return connect(_ipaddress, _port, _reply, true);
2464 }
2465
startCapture()2466 bool EasySocketListener::startCapture()
2467 {
2468 //if (m_thread.joinable())
2469 //{
2470 // m_bInterrupt.store(true, ::std::memory_order_release);
2471 // m_thread.join();
2472 // m_bInterrupt.store(false, ::std::memory_order_release);
2473 //}
2474
2475 clearData();
2476
2477 profiler::net::Message request(profiler::net::MessageType::Request_Start_Capture);
2478 m_easySocket.send(&request, sizeof(request));
2479
2480 if (m_easySocket.isDisconnected()) {
2481 m_bConnected.store(false, ::std::memory_order_release);
2482 return false;
2483 }
2484
2485 m_regime = LISTENER_CAPTURE;
2486 m_bCaptureReady.store(false, ::std::memory_order_release);
2487 //m_thread = ::std::thread(&EasySocketListener::listenCapture, this);
2488
2489 return true;
2490 }
2491
stopCapture()2492 void EasySocketListener::stopCapture()
2493 {
2494 //if (!m_thread.joinable() || m_regime != LISTENER_CAPTURE)
2495 // return;
2496
2497 if (m_regime != LISTENER_CAPTURE)
2498 return;
2499
2500 //m_bStopReceive.store(true, ::std::memory_order_release);
2501 profiler::net::Message request(profiler::net::MessageType::Request_Stop_Capture);
2502 m_easySocket.send(&request, sizeof(request));
2503
2504 //m_thread.join();
2505
2506 if (m_easySocket.isDisconnected()) {
2507 m_bConnected.store(false, ::std::memory_order_release);
2508 m_bStopReceive.store(false, ::std::memory_order_release);
2509 m_regime = LISTENER_IDLE;
2510 m_bCaptureReady.store(true, ::std::memory_order_release);
2511 return;
2512 }
2513
2514 m_regime = LISTENER_CAPTURE_RECEIVE;
2515 if (m_thread.joinable())
2516 {
2517 m_bInterrupt.store(true, ::std::memory_order_release);
2518 m_thread.join();
2519 m_bInterrupt.store(false, ::std::memory_order_release);
2520 }
2521
2522 m_thread = ::std::thread(&EasySocketListener::listenCapture, this);
2523
2524 //m_regime = LISTENER_IDLE;
2525 //m_bStopReceive.store(false, ::std::memory_order_release);
2526 }
2527
finalizeCapture()2528 void EasySocketListener::finalizeCapture()
2529 {
2530 if (m_thread.joinable())
2531 {
2532 m_bInterrupt.store(true, ::std::memory_order_release);
2533 m_thread.join();
2534 m_bInterrupt.store(false, ::std::memory_order_release);
2535 }
2536
2537 m_regime = LISTENER_IDLE;
2538 m_bCaptureReady.store(false, ::std::memory_order_release);
2539 m_bStopReceive.store(false, ::std::memory_order_release);
2540 }
2541
requestBlocksDescription()2542 void EasySocketListener::requestBlocksDescription()
2543 {
2544 if (m_thread.joinable())
2545 {
2546 m_bInterrupt.store(true, ::std::memory_order_release);
2547 m_thread.join();
2548 m_bInterrupt.store(false, ::std::memory_order_release);
2549 }
2550
2551 clearData();
2552
2553 profiler::net::Message request(profiler::net::MessageType::Request_Blocks_Description);
2554 m_easySocket.send(&request, sizeof(request));
2555
2556 if(m_easySocket.isDisconnected() ){
2557 m_bConnected.store(false, ::std::memory_order_release);
2558 }
2559
2560 m_regime = LISTENER_DESCRIBE;
2561 listenDescription();
2562 m_regime = LISTENER_IDLE;
2563 }
2564
frameTime(uint32_t & _maxTime,uint32_t & _avgTime)2565 bool EasySocketListener::frameTime(uint32_t& _maxTime, uint32_t& _avgTime)
2566 {
2567 if (m_bFrameTimeReady.exchange(false, ::std::memory_order_acquire))
2568 {
2569 _maxTime = m_frameMax.load(::std::memory_order_acquire);
2570 _avgTime = m_frameAvg.load(::std::memory_order_acquire);
2571 return true;
2572 }
2573
2574 return false;
2575 }
2576
requestFrameTime()2577 bool EasySocketListener::requestFrameTime()
2578 {
2579 if (m_regime != LISTENER_IDLE && m_regime != LISTENER_CAPTURE)
2580 return false;
2581
2582 if (m_thread.joinable())
2583 {
2584 m_bInterrupt.store(true, ::std::memory_order_release);
2585 m_thread.join();
2586 m_bInterrupt.store(false, ::std::memory_order_release);
2587 }
2588
2589 profiler::net::Message request(profiler::net::MessageType::Request_MainThread_FPS);
2590 m_easySocket.send(&request, sizeof(request));
2591
2592 if (m_easySocket.isDisconnected())
2593 {
2594 m_bConnected.store(false, ::std::memory_order_release);
2595 return false;
2596 }
2597
2598 m_bFrameTimeReady.store(false, ::std::memory_order_release);
2599 m_thread = ::std::thread(&EasySocketListener::listenFrameTime, this);
2600
2601 return true;
2602 }
2603
2604 //////////////////////////////////////////////////////////////////////////
2605
listenCapture()2606 void EasySocketListener::listenCapture()
2607 {
2608 EASY_STATIC_CONSTEXPR int buffer_size = 8 * 1024 * 1024;
2609
2610 char* buffer = new char[buffer_size];
2611 int seek = 0, bytes = 0;
2612 auto timeBegin = ::std::chrono::system_clock::now();
2613
2614 bool isListen = true, disconnected = false;
2615 while (isListen && !m_bInterrupt.load(::std::memory_order_acquire))
2616 {
2617 if (m_bStopReceive.load(::std::memory_order_acquire))
2618 {
2619 profiler::net::Message request(profiler::net::MessageType::Request_Stop_Capture);
2620 m_easySocket.send(&request, sizeof(request));
2621 m_bStopReceive.store(false, ::std::memory_order_release);
2622 }
2623
2624 if ((bytes - seek) == 0)
2625 {
2626 bytes = m_easySocket.receive(buffer, buffer_size);
2627
2628 if (bytes == -1)
2629 {
2630 if (m_easySocket.isDisconnected())
2631 {
2632 m_bConnected.store(false, ::std::memory_order_release);
2633 isListen = false;
2634 disconnected = true;
2635 }
2636
2637 seek = 0;
2638 bytes = 0;
2639
2640 continue;
2641 }
2642
2643 seek = 0;
2644 }
2645
2646 if (bytes == 0)
2647 {
2648 isListen = false;
2649 break;
2650 }
2651
2652 char* buf = buffer + seek;
2653
2654 if (bytes > 0)
2655 {
2656 auto message = reinterpret_cast<const ::profiler::net::Message*>(buf);
2657 if (!message->isEasyNetMessage())
2658 continue;
2659
2660 switch (message->type)
2661 {
2662 case profiler::net::MessageType::Connection_Accepted:
2663 {
2664 qInfo() << "Receive MessageType::Connection_Accepted";
2665 //m_easySocket.send(&request, sizeof(request));
2666 seek += sizeof(profiler::net::Message);
2667 break;
2668 }
2669
2670 case profiler::net::MessageType::Reply_Capturing_Started:
2671 {
2672 qInfo() << "Receive MessageType::Reply_Capturing_Started";
2673 seek += sizeof(profiler::net::Message);
2674 break;
2675 }
2676
2677 case profiler::net::MessageType::Reply_Blocks_End:
2678 {
2679 qInfo() << "Receive MessageType::Reply_Blocks_End";
2680 seek += sizeof(profiler::net::Message);
2681
2682 const auto dt = ::std::chrono::duration_cast<std::chrono::milliseconds>(::std::chrono::system_clock::now() - timeBegin);
2683 const auto bytesNumber = m_receivedData.str().size();
2684 qInfo() << "recieved " << bytesNumber << " bytes, " << dt.count() << " ms, average speed = " << double(bytesNumber) * 1e3 / double(dt.count()) / 1024. << " kBytes/sec";
2685
2686 seek = 0;
2687 bytes = 0;
2688
2689 isListen = false;
2690
2691 break;
2692 }
2693
2694 case profiler::net::MessageType::Reply_Blocks:
2695 {
2696 qInfo() << "Receive MessageType::Reply_Blocks";
2697
2698 seek += sizeof(profiler::net::DataMessage);
2699 auto dm = (profiler::net::DataMessage*)message;
2700 timeBegin = std::chrono::system_clock::now();
2701
2702 int neededSize = dm->size;
2703
2704
2705 buf = buffer + seek;
2706 auto bytesNumber = ::std::min((int)dm->size, bytes - seek);
2707 m_receivedSize += bytesNumber;
2708 m_receivedData.write(buf, bytesNumber);
2709 neededSize -= bytesNumber;
2710
2711 if (neededSize == 0)
2712 seek += bytesNumber;
2713 else
2714 {
2715 seek = 0;
2716 bytes = 0;
2717 }
2718
2719
2720 int loaded = 0;
2721 while (neededSize > 0)
2722 {
2723 bytes = m_easySocket.receive(buffer, buffer_size);
2724
2725 if (bytes == -1)
2726 {
2727 if (m_easySocket.isDisconnected())
2728 {
2729 m_bConnected.store(false, ::std::memory_order_release);
2730 isListen = false;
2731 disconnected = true;
2732 neededSize = 0;
2733 }
2734
2735 break;
2736 }
2737
2738 buf = buffer;
2739 int toWrite = ::std::min(bytes, neededSize);
2740 m_receivedSize += toWrite;
2741 m_receivedData.write(buf, toWrite);
2742 neededSize -= toWrite;
2743 loaded += toWrite;
2744 seek = toWrite;
2745 }
2746
2747 if (m_bStopReceive.load(::std::memory_order_acquire))
2748 {
2749 profiler::net::Message request(profiler::net::MessageType::Request_Stop_Capture);
2750 m_easySocket.send(&request, sizeof(request));
2751 m_bStopReceive.store(false, ::std::memory_order_release);
2752 }
2753
2754 break;
2755 }
2756
2757 default:
2758 //qInfo() << "Receive unknown " << message->type;
2759 break;
2760 }
2761 }
2762 }
2763
2764 if (disconnected)
2765 clearData();
2766
2767 delete [] buffer;
2768
2769 m_bCaptureReady.store(true, ::std::memory_order_release);
2770 }
2771
listenDescription()2772 void EasySocketListener::listenDescription()
2773 {
2774 EASY_STATIC_CONSTEXPR int buffer_size = 8 * 1024 * 1024;
2775
2776 char* buffer = new char[buffer_size];
2777 int seek = 0, bytes = 0;
2778
2779 bool isListen = true, disconnected = false;
2780 while (isListen && !m_bInterrupt.load(::std::memory_order_acquire))
2781 {
2782 if ((bytes - seek) == 0)
2783 {
2784 bytes = m_easySocket.receive(buffer, buffer_size);
2785
2786 if (bytes == -1)
2787 {
2788 if (m_easySocket.isDisconnected())
2789 {
2790 m_bConnected.store(false, ::std::memory_order_release);
2791 isListen = false;
2792 disconnected = true;
2793 }
2794
2795 seek = 0;
2796 bytes = 0;
2797
2798 continue;
2799 }
2800
2801 seek = 0;
2802 }
2803
2804 if (bytes == 0)
2805 {
2806 isListen = false;
2807 break;
2808 }
2809
2810 char* buf = buffer + seek;
2811
2812 if (bytes > 0)
2813 {
2814 auto message = reinterpret_cast<const ::profiler::net::Message*>(buf);
2815 if (!message->isEasyNetMessage())
2816 continue;
2817
2818 switch (message->type)
2819 {
2820 case profiler::net::MessageType::Connection_Accepted:
2821 {
2822 qInfo() << "Receive MessageType::Connection_Accepted";
2823 seek += sizeof(profiler::net::Message);
2824 break;
2825 }
2826
2827 case profiler::net::MessageType::Reply_Blocks_Description_End:
2828 {
2829 qInfo() << "Receive MessageType::Reply_Blocks_Description_End";
2830 seek += sizeof(profiler::net::Message);
2831
2832 seek = 0;
2833 bytes = 0;
2834
2835 isListen = false;
2836
2837 break;
2838 }
2839
2840 case profiler::net::MessageType::Reply_Blocks_Description:
2841 {
2842 qInfo() << "Receive MessageType::Reply_Blocks_Description";
2843
2844 seek += sizeof(profiler::net::DataMessage);
2845 auto dm = (profiler::net::DataMessage*)message;
2846 int neededSize = dm->size;
2847
2848 buf = buffer + seek;
2849 auto bytesNumber = ::std::min((int)dm->size, bytes - seek);
2850 m_receivedSize += bytesNumber;
2851 m_receivedData.write(buf, bytesNumber);
2852 neededSize -= bytesNumber;
2853
2854 if (neededSize == 0)
2855 seek += bytesNumber;
2856 else{
2857 seek = 0;
2858 bytes = 0;
2859 }
2860
2861 int loaded = 0;
2862 while (neededSize > 0)
2863 {
2864 bytes = m_easySocket.receive(buffer, buffer_size);
2865
2866 if (bytes == -1)
2867 {
2868 if (m_easySocket.isDisconnected())
2869 {
2870 m_bConnected.store(false, ::std::memory_order_release);
2871 isListen = false;
2872 disconnected = true;
2873 neededSize = 0;
2874 }
2875
2876 break;
2877 }
2878
2879 buf = buffer;
2880 int toWrite = ::std::min(bytes, neededSize);
2881 m_receivedSize += toWrite;
2882 m_receivedData.write(buf, toWrite);
2883 neededSize -= toWrite;
2884 loaded += toWrite;
2885 seek = toWrite;
2886 }
2887
2888 break;
2889 }
2890
2891 default:
2892 break;
2893 }
2894 }
2895 }
2896
2897 if (disconnected)
2898 clearData();
2899
2900 delete[] buffer;
2901 }
2902
listenFrameTime()2903 void EasySocketListener::listenFrameTime()
2904 {
2905 EASY_STATIC_CONSTEXPR size_t buffer_size = sizeof(::profiler::net::TimestampMessage) << 2;
2906
2907 char buffer[buffer_size] = {};
2908 int seek = 0, bytes = 0;
2909
2910 bool isListen = true;
2911 while (isListen && !m_bInterrupt.load(::std::memory_order_acquire))
2912 {
2913 if ((bytes - seek) == 0)
2914 {
2915 bytes = m_easySocket.receive(buffer, buffer_size);
2916
2917 if (bytes == -1)
2918 {
2919 if (m_easySocket.isDisconnected())
2920 {
2921 m_bConnected.store(false, ::std::memory_order_release);
2922 isListen = false;
2923 }
2924
2925 seek = 0;
2926 bytes = 0;
2927
2928 continue;
2929 }
2930
2931 seek = 0;
2932 }
2933
2934 if (bytes == 0)
2935 {
2936 isListen = false;
2937 break;
2938 }
2939
2940 char* buf = buffer + seek;
2941
2942 if (bytes > 0)
2943 {
2944 auto message = reinterpret_cast<const ::profiler::net::Message*>(buf);
2945 if (!message->isEasyNetMessage())
2946 continue;
2947
2948 switch (message->type)
2949 {
2950 case profiler::net::MessageType::Connection_Accepted:
2951 case profiler::net::MessageType::Reply_Capturing_Started:
2952 {
2953 seek += sizeof(profiler::net::Message);
2954 break;
2955 }
2956
2957 case profiler::net::MessageType::Reply_MainThread_FPS:
2958 {
2959 //qInfo() << "Receive MessageType::Reply_MainThread_FPS";
2960
2961 seek += sizeof(profiler::net::TimestampMessage);
2962 if (seek <= buffer_size)
2963 {
2964 auto timestampMessage = (profiler::net::TimestampMessage*)message;
2965 m_frameMax.store(timestampMessage->maxValue, ::std::memory_order_release);
2966 m_frameAvg.store(timestampMessage->avgValue, ::std::memory_order_release);
2967 m_bFrameTimeReady.store(true, ::std::memory_order_release);
2968 }
2969
2970 isListen = false;
2971 break;
2972 }
2973
2974 default:
2975 break;
2976 }
2977 }
2978 }
2979 }
2980
2981 //////////////////////////////////////////////////////////////////////////
2982
2983