/************************************************************************ * file name : main_window.cpp * ----------------- : * creation time : 2016/06/26 * author : Victor Zarubkin * email : v.s.zarubkin@gmail.com * ----------------- : * description : The file contains implementation of MainWindow for easy_profiler GUI. * ----------------- : * change log : * 2016/06/26 Victor Zarubkin: Initial commit. * : * : * 2016/06/27 Victor Zarubkin: Passing blocks number to EasyTreeWidget::setTree(). * : * : * 2016/06/29 Victor Zarubkin: Added menu with tests. * : * : * 2016/06/30 Sergey Yagovtsev: Open file by command line argument * : * : * * ----------------- : * license : Lightweight profiler library for c++ * : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin * : * : * : Licensed under the Apache License, Version 2.0 (the "License"); * : you may not use this file except in compliance with the License. * : You may obtain a copy of the License at * : * : http://www.apache.org/licenses/LICENSE-2.0 * : * : Unless required by applicable law or agreed to in writing, software * : distributed under the License is distributed on an "AS IS" BASIS, * : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * : See the License for the specific language governing permissions and * : limitations under the License. * : * : * : GNU General Public License Usage * : Alternatively, this file may be used under the terms of the GNU * : General Public License as published by the Free Software Foundation, * : either version 3 of the License, or (at your option) any later version. * : * : This program is distributed in the hope that it will be useful, * : but WITHOUT ANY WARRANTY; without even the implied warranty of * : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the * : GNU General Public License for more details. * : * : You should have received a copy of the GNU General Public License * : along with this program.If not, see . ************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "main_window.h" #include "blocks_tree_widget.h" #include "blocks_graphics_view.h" #include "descriptors_tree_widget.h" #include "globals.h" #include "easy/easy_net.h" #ifdef max #undef max #endif #ifdef min #undef min #endif ////////////////////////////////////////////////////////////////////////// const int LOADER_TIMER_INTERVAL = 40; const auto NETWORK_CACHE_FILE = "easy_profiler_stream.cache"; ////////////////////////////////////////////////////////////////////////// inline void clear_stream(std::stringstream& _stream) { #if defined(__GNUC__) && __GNUC__ < 5 // gcc 4 has a known bug which has been solved in gcc 5: // std::stringstream has no swap() method :( _stream.str(std::string()); #else std::stringstream().swap(_stream); #endif } ////////////////////////////////////////////////////////////////////////// EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("localhost"), m_lastPort(::profiler::DEFAULT_PORT) { { QIcon icon(":/logo"); if (!icon.isNull()) QApplication::setWindowIcon(icon); } setObjectName("ProfilerGUI_MainWindow"); setWindowTitle("EasyProfiler"); setDockNestingEnabled(true); setAcceptDrops(true); resize(800, 600); setStatusBar(new QStatusBar()); m_graphicsView = new QDockWidget("Diagram", this); m_graphicsView->setObjectName("ProfilerGUI_Diagram"); m_graphicsView->setMinimumHeight(50); m_graphicsView->setAllowedAreas(Qt::AllDockWidgetAreas); auto graphicsView = new EasyGraphicsViewWidget(this); m_graphicsView->setWidget(graphicsView); m_treeWidget = new QDockWidget("Hierarchy", this); m_treeWidget->setObjectName("ProfilerGUI_Hierarchy"); m_treeWidget->setMinimumHeight(50); m_treeWidget->setAllowedAreas(Qt::AllDockWidgetAreas); auto treeWidget = new EasyHierarchyWidget(this); m_treeWidget->setWidget(treeWidget); addDockWidget(Qt::TopDockWidgetArea, m_graphicsView); addDockWidget(Qt::BottomDockWidgetArea, m_treeWidget); #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 auto descTree = new EasyDescWidget(); m_descTreeWidget = new QDockWidget("Blocks"); m_descTreeWidget->setObjectName("ProfilerGUI_Blocks"); m_descTreeWidget->setMinimumHeight(50); m_descTreeWidget->setAllowedAreas(Qt::AllDockWidgetAreas); m_descTreeWidget->setWidget(descTree); addDockWidget(Qt::BottomDockWidgetArea, m_descTreeWidget); #endif loadSettings(); auto toolbar = addToolBar("FileToolbar"); toolbar->setObjectName("ProfilerGUI_FileToolbar"); toolbar->setContentsMargins(1, 0, 1, 0); m_loadActionMenu = new QMenu(this); auto action = m_loadActionMenu->menuAction(); action->setText("Open file"); action->setIcon(QIcon(":/Open")); connect(action, &QAction::triggered, this, &This::onOpenFileClicked); toolbar->addAction(action); for (const auto& f : m_lastFiles) { action = new QAction(f, this); connect(action, &QAction::triggered, this, &This::onOpenFileClicked); m_loadActionMenu->addAction(action); } m_saveAction = toolbar->addAction(QIcon(":/Save"), tr("Save"), this, SLOT(onSaveFileClicked(bool))); m_deleteAction = toolbar->addAction(QIcon(":/Delete"), tr("Clear all"), this, SLOT(onDeleteClicked(bool))); m_saveAction->setEnabled(false); m_deleteAction->setEnabled(false); toolbar = addToolBar("ProfileToolbar"); toolbar->setObjectName("ProfilerGUI_ProfileToolbar"); toolbar->setContentsMargins(1, 0, 1, 0); toolbar->addAction(QIcon(":/List"), tr("Blocks"), this, SLOT(onEditBlocksClicked(bool))); m_captureAction = toolbar->addAction(QIcon(":/Start"), tr("Capture"), this, SLOT(onCaptureClicked(bool))); m_captureAction->setEnabled(false); toolbar->addSeparator(); m_connectAction = toolbar->addAction(QIcon(":/Connection"), tr("Connect"), this, SLOT(onConnectClicked(bool))); auto lbl = new QLabel("Address:", toolbar); lbl->setContentsMargins(5, 0, 2, 0); toolbar->addWidget(lbl); m_addressEdit = new QLineEdit(); m_addressEdit->setToolTip("Enter IP-address or host name"); //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}$"); //m_addressEdit->setValidator(new QRegExpValidator(rx, m_addressEdit)); m_addressEdit->setText(m_lastAddress); m_addressEdit->setFixedWidth((m_addressEdit->fontMetrics().width(QString("255.255.255.255")) * 3) / 2); toolbar->addWidget(m_addressEdit); lbl = new QLabel("Port:", toolbar); lbl->setContentsMargins(5, 0, 2, 0); toolbar->addWidget(lbl); m_portEdit = new QLineEdit(); m_portEdit->setValidator(new QIntValidator(1, 65535, m_portEdit)); m_portEdit->setText(QString::number(m_lastPort)); m_portEdit->setFixedWidth(m_portEdit->fontMetrics().width(QString("000000")) + 10); toolbar->addWidget(m_portEdit); connect(m_addressEdit, &QLineEdit::returnPressed, [this](){ onConnectClicked(true); }); connect(m_portEdit, &QLineEdit::returnPressed, [this](){ onConnectClicked(true); }); toolbar = addToolBar("SetupToolbar"); toolbar->setObjectName("ProfilerGUI_SetupToolbar"); toolbar->setContentsMargins(1, 0, 1, 0); toolbar->addAction(QIcon(":/Expand"), "Expand all", this, SLOT(onExpandAllClicked(bool))); toolbar->addAction(QIcon(":/Collapse"), "Collapse all", this, SLOT(onCollapseAllClicked(bool))); toolbar->addSeparator(); auto menu = new QMenu("Settings", this); QToolButton* toolButton = new QToolButton(toolbar); toolButton->setIcon(QIcon(":/Settings")); toolButton->setMenu(menu); toolButton->setPopupMode(QToolButton::InstantPopup); toolbar->addWidget(toolButton); action = menu->addAction("Statistics enabled"); action->setCheckable(true); action->setChecked(EASY_GLOBALS.enable_statistics); connect(action, &QAction::triggered, this, &This::onEnableDisableStatistics); if (EASY_GLOBALS.enable_statistics) { auto f = action->font(); f.setBold(true); action->setFont(f); SET_ICON(action, ":/Stats"); } else { action->setText("Statistics disabled"); SET_ICON(action, ":/Stats-off"); } menu->addSeparator(); auto submenu = menu->addMenu("View"); submenu->setToolTipsVisible(true); action = submenu->addAction("Draw items' borders"); action->setToolTip("Draw borders for blocks on diagram.\nThis reduces performance."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.draw_graphics_items_borders); connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.draw_graphics_items_borders = _checked; refreshDiagram(); }); action = submenu->addAction("Overlap narrow children"); action->setToolTip("Children blocks will be overlaped by narrow\nparent blocks. See also \'Blocks narrow size\'.\nThis improves performance."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.hide_narrow_children); connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.hide_narrow_children = _checked; refreshDiagram(); }); action = submenu->addAction("Hide min-size blocks"); action->setToolTip("Hides blocks which screen size\nis less than \'Min blocks size\'."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.hide_minsize_blocks); connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.hide_minsize_blocks = _checked; refreshDiagram(); }); action = submenu->addAction("Build hierarchy only for current thread"); action->setToolTip("Hierarchy tree will be built\nfor blocks from current thread only.\nThis improves performance\nand saves a lot of memory."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.only_current_thread_hierarchy); connect(action, &QAction::triggered, this, &This::onHierarchyFlagChange); action = submenu->addAction("Add zero blocks to hierarchy"); action->setToolTip("Zero duration blocks will be added into hierarchy tree.\nThis reduces performance and increases memory consumption."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.add_zero_blocks_to_hierarchy); connect(action, &QAction::triggered, [this](bool _checked) { EASY_GLOBALS.add_zero_blocks_to_hierarchy = _checked; emit EASY_GLOBALS.events.hierarchyFlagChanged(_checked); }); action = submenu->addAction("Enable zero duration blocks on diagram"); 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."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.enable_zero_length); connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.enable_zero_length = _checked; refreshDiagram(); }); action = submenu->addAction("Highlight similar blocks"); action->setToolTip("Highlight all visible blocks which are similar\nto the current selected block.\nThis reduces performance."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.highlight_blocks_with_same_id); connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.highlight_blocks_with_same_id = _checked; refreshDiagram(); }); action = submenu->addAction("Collapse blocks on tree reset"); action->setToolTip("This collapses all blocks on diagram\nafter hierarchy tree reset."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.collapse_items_on_tree_close); connect(action, &QAction::triggered, this, &This::onCollapseItemsAfterCloseChanged); action = submenu->addAction("Expand all on file open"); action->setToolTip("If checked then all blocks on diagram\nwill be initially expanded."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.all_items_expanded_by_default); connect(action, &QAction::triggered, this, &This::onAllItemsExpandedByDefaultChange); action = submenu->addAction("Bind diagram and tree expand"); action->setToolTip("Expanding/collapsing blocks at diagram expands/collapses\nblocks at hierarchy tree and wise versa."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.bind_scene_and_tree_expand_status); connect(action, &QAction::triggered, this, &This::onBindExpandStatusChange); action = submenu->addAction("Selecting block changes current thread"); 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."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.selecting_block_changes_thread); connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.selecting_block_changes_thread = _checked; }); action = submenu->addAction("Draw event indicators"); action->setToolTip("Display event indicators under the blocks\n(even if event-blocks are not visible).\nThis slightly reduces performance."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.enable_event_indicators); connect(action, &QAction::triggered, this, &This::onEventIndicatorsChange); action = submenu->addAction("Use decorated thread names"); 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."); action->setCheckable(true); action->setChecked(EASY_GLOBALS.use_decorated_thread_name); connect(action, &QAction::triggered, [this](bool _checked) { EASY_GLOBALS.use_decorated_thread_name = _checked; emit EASY_GLOBALS.events.threadNameDecorationChanged(); }); submenu->addSeparator(); auto actionGroup = new QActionGroup(this); actionGroup->setExclusive(true); action = new QAction("Chrono text at top", actionGroup); action->setToolTip("Draw duration of selected interval\nat the top of the screen."); action->setCheckable(true); action->setData(static_cast(::profiler_gui::ChronoTextPosition_Top)); if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Top) action->setChecked(true); submenu->addAction(action); connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged); action = new QAction("Chrono text at center", actionGroup); action->setToolTip("Draw duration of selected interval\nat the center of the screen."); action->setCheckable(true); action->setData(static_cast(::profiler_gui::ChronoTextPosition_Center)); if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Center) action->setChecked(true); submenu->addAction(action); connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged); action = new QAction("Chrono text at bottom", actionGroup); action->setToolTip("Draw duration of selected interval\nat the bottom of the screen."); action->setCheckable(true); action->setData(static_cast(::profiler_gui::ChronoTextPosition_Bottom)); if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Bottom) action->setChecked(true); submenu->addAction(action); connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged); submenu->addSeparator(); auto w = new QWidget(submenu); auto l = new QHBoxLayout(w); l->setContentsMargins(33, 1, 1, 1); l->addWidget(new QLabel("Min blocks spacing, px", w), 0, Qt::AlignLeft); auto spinbox = new QSpinBox(w); spinbox->setMinimum(0); spinbox->setValue(EASY_GLOBALS.blocks_spacing); spinbox->setFixedWidth(50); connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onSpacingChange(int))); l->addWidget(spinbox); w->setLayout(l); auto waction = new QWidgetAction(submenu); waction->setDefaultWidget(w); submenu->addAction(waction); w = new QWidget(submenu); l = new QHBoxLayout(w); l->setContentsMargins(33, 1, 1, 1); l->addWidget(new QLabel("Min blocks size, px", w), 0, Qt::AlignLeft); spinbox = new QSpinBox(w); spinbox->setMinimum(1); spinbox->setValue(EASY_GLOBALS.blocks_size_min); spinbox->setFixedWidth(50); connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onMinSizeChange(int))); l->addWidget(spinbox); w->setLayout(l); waction = new QWidgetAction(submenu); waction->setDefaultWidget(w); submenu->addAction(waction); w = new QWidget(submenu); l = new QHBoxLayout(w); l->setContentsMargins(33, 1, 1, 1); l->addWidget(new QLabel("Blocks narrow size, px", w), 0, Qt::AlignLeft); spinbox = new QSpinBox(w); spinbox->setMinimum(1); spinbox->setValue(EASY_GLOBALS.blocks_narrow_size); spinbox->setFixedWidth(50); connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onNarrowSizeChange(int))); l->addWidget(spinbox); w->setLayout(l); waction = new QWidgetAction(submenu); waction->setDefaultWidget(w); submenu->addAction(waction); submenu = menu->addMenu("Units"); actionGroup = new QActionGroup(this); actionGroup->setExclusive(true); action = new QAction("Auto", actionGroup); action->setCheckable(true); action->setData(static_cast(::profiler_gui::TimeUnits_auto)); if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_auto) action->setChecked(true); submenu->addAction(action); connect(action, &QAction::triggered, this, &This::onUnitsChanged); action = new QAction("Milliseconds", actionGroup); action->setCheckable(true); action->setData(static_cast(::profiler_gui::TimeUnits_ms)); if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_ms) action->setChecked(true); submenu->addAction(action); connect(action, &QAction::triggered, this, &This::onUnitsChanged); action = new QAction("Microseconds", actionGroup); action->setCheckable(true); action->setData(static_cast(::profiler_gui::TimeUnits_us)); if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_us) action->setChecked(true); submenu->addAction(action); connect(action, &QAction::triggered, this, &This::onUnitsChanged); action = new QAction("Nanoseconds", actionGroup); action->setCheckable(true); action->setData(static_cast(::profiler_gui::TimeUnits_ns)); if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_ns) action->setChecked(true); submenu->addAction(action); connect(action, &QAction::triggered, this, &This::onUnitsChanged); submenu = menu->addMenu("Remote"); m_eventTracingEnableAction = submenu->addAction("Event tracing enabled"); m_eventTracingEnableAction->setCheckable(true); m_eventTracingEnableAction->setEnabled(false); connect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange); m_eventTracingPriorityAction = submenu->addAction("Low priority event tracing"); m_eventTracingPriorityAction->setCheckable(true); m_eventTracingPriorityAction->setChecked(EASY_OPTION_LOW_PRIORITY_EVENT_TRACING); m_eventTracingPriorityAction->setEnabled(false); connect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange); submenu = menu->addMenu("Encoding"); actionGroup = new QActionGroup(this); actionGroup->setExclusive(true); auto default_codec_mib = QTextCodec::codecForLocale()->mibEnum(); foreach(int mib, QTextCodec::availableMibs()) { auto codec = QTextCodec::codecForMib(mib)->name(); action = new QAction(codec, actionGroup); action->setCheckable(true); if (mib == default_codec_mib) action->setChecked(true); submenu->addAction(action); connect(action, &QAction::triggered, this, &This::onEncodingChanged); } auto tb_height = toolbar->height() + 4; toolbar = addToolBar("FrameToolbar"); toolbar->setObjectName("ProfilerGUI_FrameToolbar"); toolbar->setContentsMargins(1, 0, 1, 0); toolbar->setMinimumHeight(tb_height); lbl = new QLabel("Frame time:", toolbar); lbl->setContentsMargins(5, 2, 2, 2); toolbar->addWidget(lbl); m_frameTimeEdit = new QLineEdit(); m_frameTimeEdit->setFixedWidth(70); auto val = new QDoubleValidator(m_frameTimeEdit); val->setLocale(QLocale::c()); val->setBottom(0); m_frameTimeEdit->setValidator(val); m_frameTimeEdit->setText(QString::number(EASY_GLOBALS.frame_time * 1e-3)); connect(m_frameTimeEdit, &QLineEdit::editingFinished, this, &This::onFrameTimeEditFinish); toolbar->addWidget(m_frameTimeEdit); lbl = new QLabel("ms", toolbar); lbl->setContentsMargins(5, 2, 1, 1); toolbar->addWidget(lbl); connect(graphicsView->view(), &EasyGraphicsView::intervalChanged, treeWidget->tree(), &EasyTreeWidget::setTreeBlocks); connect(&m_readerTimer, &QTimer::timeout, this, &This::onFileReaderTimeout); connect(&m_listenerTimer, &QTimer::timeout, this, &This::onListenerTimerTimeout); m_progress = new QProgressDialog("Loading file...", "Cancel", 0, 100, this); m_progress->setFixedWidth(300); m_progress->setWindowTitle("EasyProfiler"); m_progress->setModal(true); m_progress->setValue(100); //m_progress->hide(); connect(m_progress, &QProgressDialog::canceled, this, &This::onFileReaderCancel); loadGeometry(); if(QCoreApplication::arguments().size() > 1) { auto opened_filename = QCoreApplication::arguments().at(1); loadFile(opened_filename); } connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blockStatusChanged, this, &This::onBlockStatusChange); connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blocksRefreshRequired, this, &This::onGetBlockDescriptionsClicked); } EasyMainWindow::~EasyMainWindow() { delete m_progress; } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::dragEnterEvent(QDragEnterEvent* drag_event) { if (drag_event->mimeData()->hasUrls()) drag_event->acceptProposedAction(); } void EasyMainWindow::dragMoveEvent(QDragMoveEvent* drag_event) { if (drag_event->mimeData()->hasUrls()) drag_event->acceptProposedAction(); } void EasyMainWindow::dragLeaveEvent(QDragLeaveEvent* drag_event) { drag_event->accept(); } void EasyMainWindow::dropEvent(QDropEvent* drop_event) { const auto& urls = drop_event->mimeData()->urls(); if (!urls.empty()) loadFile(urls.front().toLocalFile()); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onOpenFileClicked(bool) { auto action = qobject_cast(sender()); if (action == nullptr) return; if (action == m_loadActionMenu->menuAction()) { auto filename = QFileDialog::getOpenFileName(this, "Open profiler log", m_lastFiles.empty() ? QString() : m_lastFiles.front(), "Profiler Log File (*.prof);;All Files (*.*)"); if (!filename.isEmpty()) loadFile(filename); } else { loadFile(action->text()); } } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::addFileToList(const QString& filename) { m_lastFiles.push_front(filename); auto action = new QAction(filename, this); connect(action, &QAction::triggered, this, &This::onOpenFileClicked); auto fileActions = m_loadActionMenu->actions(); if (fileActions.empty()) m_loadActionMenu->addAction(action); else m_loadActionMenu->insertAction(fileActions.front(), action); if (m_lastFiles.size() > 10) { // Keep 10 files at the list m_lastFiles.pop_back(); m_loadActionMenu->removeAction(fileActions.back()); delete fileActions.back(); } } void EasyMainWindow::loadFile(const QString& filename) { const auto i = filename.lastIndexOf(QChar('/')); const auto j = filename.lastIndexOf(QChar('\\')); m_progress->setLabelText(QString("Loading %1...").arg(filename.mid(::std::max(i, j) + 1))); m_progress->setValue(0); m_progress->show(); m_readerTimer.start(LOADER_TIMER_INTERVAL); m_reader.load(filename); } void EasyMainWindow::readStream(::std::stringstream& data) { m_progress->setLabelText(tr("Reading from stream...")); m_progress->setValue(0); m_progress->show(); m_readerTimer.start(LOADER_TIMER_INTERVAL); m_reader.load(data); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onSaveFileClicked(bool) { if (m_serializedBlocks.empty()) return; QString lastFile = m_lastFiles.empty() ? QString() : m_lastFiles.front(); const auto i = lastFile.lastIndexOf(QChar('/')); const auto j = lastFile.lastIndexOf(QChar('\\')); auto k = ::std::max(i, j); QString dir; if (k > 0) dir = lastFile.mid(0, ++k); auto filename = QFileDialog::getSaveFileName(this, "Save profiler log", dir, "Profiler Log File (*.prof);;All Files (*.*)"); if (!filename.isEmpty()) { bool inOk = false, outOk = false; int8_t retry1 = -1; while (++retry1 < 4) { ::std::ifstream inFile(m_bNetworkFileRegime ? NETWORK_CACHE_FILE : lastFile.toStdString().c_str(), ::std::fstream::binary); if (!inFile.is_open()) { ::std::this_thread::sleep_for(::std::chrono::milliseconds(500)); continue; } inOk = true; int8_t retry2 = -1; while (++retry2 < 4) { ::std::ofstream outFile(filename.toStdString(), ::std::fstream::binary); if (!outFile.is_open()) { ::std::this_thread::sleep_for(::std::chrono::milliseconds(500)); continue; } outFile << inFile.rdbuf(); outOk = true; break; } break; } if (outOk) { if (m_bNetworkFileRegime) QFile::remove(QString(NETWORK_CACHE_FILE)); addFileToList(filename); m_bNetworkFileRegime = false; } else if (inOk) { QMessageBox::warning(this, "Warning", "Can not open destination file.\nSaving incomplete.", QMessageBox::Close); } else { if (m_bNetworkFileRegime) QMessageBox::warning(this, "Warning", "Can not open network cache file.\nSaving incomplete.", QMessageBox::Close); else QMessageBox::warning(this, "Warning", "Can not open source file.\nSaving incomplete.", QMessageBox::Close); } } } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::clear() { static_cast(m_treeWidget->widget())->clear(true); static_cast(m_graphicsView->widget())->clear(); #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 static_cast(m_descTreeWidget->widget())->clear(); #endif if (m_dialogDescTree != nullptr) m_dialogDescTree->clear(); EASY_GLOBALS.selected_thread = 0; ::profiler_gui::set_max(EASY_GLOBALS.selected_block); ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id); EASY_GLOBALS.profiler_blocks.clear(); EASY_GLOBALS.descriptors.clear(); EASY_GLOBALS.gui_blocks.clear(); m_serializedBlocks.clear(); m_serializedDescriptors.clear(); m_saveAction->setEnabled(false); m_deleteAction->setEnabled(false); m_bNetworkFileRegime = false; } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::refreshDiagram() { static_cast(m_graphicsView->widget())->view()->scene()->update(); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onDeleteClicked(bool) { auto button = QMessageBox::question(this, "Clear all profiled data", "All profiled data is going to be deleted!\nContinue?", QMessageBox::Yes, QMessageBox::No); if (button == QMessageBox::Yes) clear(); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onExitClicked(bool) { close(); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onEncodingChanged(bool) { auto _sender = qobject_cast(sender()); auto name = _sender->text(); QTextCodec *codec = QTextCodec::codecForName(name.toStdString().c_str()); QTextCodec::setCodecForLocale(codec); } void EasyMainWindow::onChronoTextPosChanged(bool) { auto _sender = qobject_cast(sender()); EASY_GLOBALS.chrono_text_position = static_cast<::profiler_gui::ChronometerTextPosition>(_sender->data().toInt()); refreshDiagram(); } void EasyMainWindow::onUnitsChanged(bool) { auto _sender = qobject_cast(sender()); EASY_GLOBALS.time_units = static_cast<::profiler_gui::TimeUnits>(_sender->data().toInt()); } void EasyMainWindow::onEventIndicatorsChange(bool _checked) { EASY_GLOBALS.enable_event_indicators = _checked; refreshDiagram(); } void EasyMainWindow::onEnableDisableStatistics(bool _checked) { EASY_GLOBALS.enable_statistics = _checked; auto action = qobject_cast(sender()); if (action != nullptr) { auto f = action->font(); f.setBold(_checked); action->setFont(f); if (_checked) { action->setText("Statistics enabled"); SET_ICON(action, ":/Stats"); } else { action->setText("Statistics disabled"); SET_ICON(action, ":/Stats-off"); } } } void EasyMainWindow::onCollapseItemsAfterCloseChanged(bool _checked) { EASY_GLOBALS.collapse_items_on_tree_close = _checked; } void EasyMainWindow::onAllItemsExpandedByDefaultChange(bool _checked) { EASY_GLOBALS.all_items_expanded_by_default = _checked; } void EasyMainWindow::onBindExpandStatusChange(bool _checked) { EASY_GLOBALS.bind_scene_and_tree_expand_status = _checked; } void EasyMainWindow::onHierarchyFlagChange(bool _checked) { EASY_GLOBALS.only_current_thread_hierarchy = _checked; emit EASY_GLOBALS.events.hierarchyFlagChanged(_checked); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onExpandAllClicked(bool) { for (auto& block : EASY_GLOBALS.gui_blocks) block.expanded = true; emit EASY_GLOBALS.events.itemsExpandStateChanged(); auto tree = static_cast(m_treeWidget->widget())->tree(); const QSignalBlocker b(tree); tree->expandAll(); } void EasyMainWindow::onCollapseAllClicked(bool) { for (auto& block : EASY_GLOBALS.gui_blocks) block.expanded = false; emit EASY_GLOBALS.events.itemsExpandStateChanged(); auto tree = static_cast(m_treeWidget->widget())->tree(); const QSignalBlocker b(tree); tree->collapseAll(); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onSpacingChange(int _value) { EASY_GLOBALS.blocks_spacing = _value; refreshDiagram(); } void EasyMainWindow::onMinSizeChange(int _value) { EASY_GLOBALS.blocks_size_min = _value; refreshDiagram(); } void EasyMainWindow::onNarrowSizeChange(int _value) { EASY_GLOBALS.blocks_narrow_size = _value; refreshDiagram(); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onEditBlocksClicked(bool) { if (m_descTreeDialog != nullptr) { m_descTreeDialog->raise(); return; } m_descTreeDialog = new QDialog(); m_descTreeDialog->setAttribute(Qt::WA_DeleteOnClose, true); m_descTreeDialog->setWindowTitle("EasyProfiler"); m_descTreeDialog->resize(800, 600); connect(m_descTreeDialog, &QDialog::finished, this, &This::onDescTreeDialogClose); auto l = new QVBoxLayout(m_descTreeDialog); m_dialogDescTree = new EasyDescWidget(m_descTreeDialog); l->addWidget(m_dialogDescTree); m_descTreeDialog->setLayout(l); m_dialogDescTree->build(); m_descTreeDialog->show(); } void EasyMainWindow::onDescTreeDialogClose(int) { disconnect(m_descTreeDialog, &QDialog::finished, this, &This::onDescTreeDialogClose); m_dialogDescTree = nullptr; m_descTreeDialog = nullptr; } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::closeEvent(QCloseEvent* close_event) { saveSettingsAndGeometry(); if (m_descTreeDialog != nullptr) { m_descTreeDialog->reject(); m_descTreeDialog = nullptr; m_dialogDescTree = nullptr; } Parent::closeEvent(close_event); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::loadSettings() { QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME); settings.beginGroup("main"); auto last_files = settings.value("last_files"); if (!last_files.isNull()) m_lastFiles = last_files.toStringList(); auto last_addr = settings.value("ip_address"); if (!last_addr.isNull()) m_lastAddress = last_addr.toString(); auto last_port = settings.value("port"); if (!last_port.isNull()) m_lastPort = (uint16_t)last_port.toUInt(); auto val = settings.value("chrono_text_position"); if (!val.isNull()) EASY_GLOBALS.chrono_text_position = static_cast<::profiler_gui::ChronometerTextPosition>(val.toInt()); val = settings.value("time_units"); if (!val.isNull()) EASY_GLOBALS.time_units = static_cast<::profiler_gui::TimeUnits>(val.toInt()); val = settings.value("frame_time"); if (!val.isNull()) EASY_GLOBALS.frame_time = val.toFloat(); val = settings.value("blocks_spacing"); if (!val.isNull()) EASY_GLOBALS.blocks_spacing = val.toInt(); val = settings.value("blocks_size_min"); if (!val.isNull()) EASY_GLOBALS.blocks_size_min = val.toInt(); val = settings.value("blocks_narrow_size"); if (!val.isNull()) EASY_GLOBALS.blocks_narrow_size = val.toInt(); auto flag = settings.value("draw_graphics_items_borders"); if (!flag.isNull()) EASY_GLOBALS.draw_graphics_items_borders = flag.toBool(); flag = settings.value("hide_narrow_children"); if (!flag.isNull()) EASY_GLOBALS.hide_narrow_children = flag.toBool(); flag = settings.value("hide_minsize_blocks"); if (!flag.isNull()) EASY_GLOBALS.hide_minsize_blocks = flag.toBool(); flag = settings.value("collapse_items_on_tree_close"); if (!flag.isNull()) EASY_GLOBALS.collapse_items_on_tree_close = flag.toBool(); flag = settings.value("all_items_expanded_by_default"); if (!flag.isNull()) EASY_GLOBALS.all_items_expanded_by_default = flag.toBool(); flag = settings.value("only_current_thread_hierarchy"); if (!flag.isNull()) EASY_GLOBALS.only_current_thread_hierarchy = flag.toBool(); flag = settings.value("enable_zero_length"); if (!flag.isNull()) EASY_GLOBALS.enable_zero_length = flag.toBool(); flag = settings.value("add_zero_blocks_to_hierarchy"); if (!flag.isNull()) EASY_GLOBALS.add_zero_blocks_to_hierarchy = flag.toBool(); flag = settings.value("highlight_blocks_with_same_id"); if (!flag.isNull()) EASY_GLOBALS.highlight_blocks_with_same_id = flag.toBool(); flag = settings.value("bind_scene_and_tree_expand_status"); if (!flag.isNull()) EASY_GLOBALS.bind_scene_and_tree_expand_status = flag.toBool(); flag = settings.value("selecting_block_changes_thread"); if (!flag.isNull()) EASY_GLOBALS.selecting_block_changes_thread = flag.toBool(); flag = settings.value("enable_event_indicators"); if (!flag.isNull()) EASY_GLOBALS.enable_event_indicators = flag.toBool(); flag = settings.value("use_decorated_thread_name"); if (!flag.isNull()) EASY_GLOBALS.use_decorated_thread_name = flag.toBool(); flag = settings.value("enable_statistics"); if (!flag.isNull()) EASY_GLOBALS.enable_statistics = flag.toBool(); QString encoding = settings.value("encoding", "UTF-8").toString(); auto default_codec_mib = QTextCodec::codecForName(encoding.toStdString().c_str())->mibEnum(); auto default_codec = QTextCodec::codecForMib(default_codec_mib); QTextCodec::setCodecForLocale(default_codec); settings.endGroup(); } void EasyMainWindow::loadGeometry() { QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME); settings.beginGroup("main"); auto geometry = settings.value("geometry").toByteArray(); if (!geometry.isEmpty()) restoreGeometry(geometry); auto state = settings.value("windowState").toByteArray(); if (!state.isEmpty()) restoreState(state); settings.endGroup(); } void EasyMainWindow::saveSettingsAndGeometry() { QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME); settings.beginGroup("main"); settings.setValue("geometry", this->saveGeometry()); settings.setValue("windowState", this->saveState()); settings.setValue("last_files", m_lastFiles); settings.setValue("ip_address", m_lastAddress); settings.setValue("port", (quint32)m_lastPort); settings.setValue("chrono_text_position", static_cast(EASY_GLOBALS.chrono_text_position)); settings.setValue("time_units", static_cast(EASY_GLOBALS.time_units)); settings.setValue("frame_time", EASY_GLOBALS.frame_time); settings.setValue("blocks_spacing", EASY_GLOBALS.blocks_spacing); settings.setValue("blocks_size_min", EASY_GLOBALS.blocks_size_min); settings.setValue("blocks_narrow_size", EASY_GLOBALS.blocks_narrow_size); settings.setValue("draw_graphics_items_borders", EASY_GLOBALS.draw_graphics_items_borders); settings.setValue("hide_narrow_children", EASY_GLOBALS.hide_narrow_children); settings.setValue("hide_minsize_blocks", EASY_GLOBALS.hide_minsize_blocks); settings.setValue("collapse_items_on_tree_close", EASY_GLOBALS.collapse_items_on_tree_close); settings.setValue("all_items_expanded_by_default", EASY_GLOBALS.all_items_expanded_by_default); settings.setValue("only_current_thread_hierarchy", EASY_GLOBALS.only_current_thread_hierarchy); settings.setValue("enable_zero_length", EASY_GLOBALS.enable_zero_length); settings.setValue("add_zero_blocks_to_hierarchy", EASY_GLOBALS.add_zero_blocks_to_hierarchy); settings.setValue("highlight_blocks_with_same_id", EASY_GLOBALS.highlight_blocks_with_same_id); settings.setValue("bind_scene_and_tree_expand_status", EASY_GLOBALS.bind_scene_and_tree_expand_status); settings.setValue("selecting_block_changes_thread", EASY_GLOBALS.selecting_block_changes_thread); settings.setValue("enable_event_indicators", EASY_GLOBALS.enable_event_indicators); settings.setValue("use_decorated_thread_name", EASY_GLOBALS.use_decorated_thread_name); settings.setValue("enable_statistics", EASY_GLOBALS.enable_statistics); settings.setValue("encoding", QTextCodec::codecForLocale()->name()); settings.endGroup(); } void EasyMainWindow::setDisconnected(bool _showMessage) { if (_showMessage) QMessageBox::warning(this, "Warning", "Application was disconnected", QMessageBox::Close); EASY_GLOBALS.connected = false; m_captureAction->setEnabled(false); SET_ICON(m_connectAction, ":/Connection"); m_eventTracingEnableAction->setEnabled(false); m_eventTracingPriorityAction->setEnabled(false); emit EASY_GLOBALS.events.connectionChanged(false); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onListenerTimerTimeout() { if (!m_listener.connected()) m_listenerDialog->reject(); } void EasyMainWindow::onListenerDialogClose(int) { m_listenerTimer.stop(); disconnect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose); m_listenerDialog = nullptr; switch (m_listener.regime()) { case LISTENER_CAPTURE: { m_listenerDialog = new QMessageBox(QMessageBox::Information, "Receiving data...", "This process may take some time.", QMessageBox::NoButton, this); m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true); m_listenerDialog->show(); m_listener.stopCapture(); m_listenerDialog->reject(); m_listenerDialog = nullptr; if (m_listener.size() != 0) { readStream(m_listener.data()); m_listener.clearData(); } break; } case LISTENER_DESCRIBE: { break; } default: return; } if (!m_listener.connected()) { setDisconnected(); } } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onFileReaderTimeout() { if (m_reader.done()) { auto nblocks = m_reader.size(); if (nblocks != 0) { static_cast(m_treeWidget->widget())->clear(true); ::profiler::SerializedData serialized_blocks; ::profiler::SerializedData serialized_descriptors; ::profiler::descriptors_list_t descriptors; ::profiler::blocks_t blocks; ::profiler::thread_blocks_tree_t threads_map; QString filename; uint32_t descriptorsNumberInFile = 0; m_reader.get(serialized_blocks, serialized_descriptors, descriptors, blocks, threads_map, descriptorsNumberInFile, filename); if (threads_map.size() > 0xff) { if (m_reader.isFile()) qWarning() << "Warning: file " << filename << " contains " << threads_map.size() << " threads!"; else qWarning() << "Warning: input stream contains " << threads_map.size() << " threads!"; qWarning() << "Warning: Currently, maximum number of displayed threads is 255! Some threads will not be displayed."; } m_bNetworkFileRegime = !m_reader.isFile(); if (!m_bNetworkFileRegime) { auto index = m_lastFiles.indexOf(filename, 0); if (index == -1) { // This file is totally new. Add it to the list. addFileToList(filename); } else if (index != 0) { // This file has been already loaded. Move it to the front. m_lastFiles.move(index, 0); auto fileActions = m_loadActionMenu->actions(); auto action = fileActions.at(index); m_loadActionMenu->removeAction(action); m_loadActionMenu->insertAction(fileActions.front(), action); } } m_serializedBlocks = ::std::move(serialized_blocks); m_serializedDescriptors = ::std::move(serialized_descriptors); m_descriptorsNumberInFile = descriptorsNumberInFile; EASY_GLOBALS.selected_thread = 0; ::profiler_gui::set_max(EASY_GLOBALS.selected_block); ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id); EASY_GLOBALS.profiler_blocks.swap(threads_map); EASY_GLOBALS.descriptors.swap(descriptors); EASY_GLOBALS.gui_blocks.clear(); EASY_GLOBALS.gui_blocks.resize(nblocks); memset(EASY_GLOBALS.gui_blocks.data(), 0, sizeof(::profiler_gui::EasyBlock) * nblocks); for (decltype(nblocks) i = 0; i < nblocks; ++i) { auto& guiblock = EASY_GLOBALS.gui_blocks[i]; guiblock.tree = ::std::move(blocks[i]); #ifdef EASY_TREE_WIDGET__USE_VECTOR ::profiler_gui::set_max(guiblock.tree_item); #endif } static_cast(m_graphicsView->widget())->view()->setTree(EASY_GLOBALS.profiler_blocks); #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 static_cast(m_descTreeWidget->widget())->build(); #endif if (m_dialogDescTree != nullptr) m_dialogDescTree->build(); m_saveAction->setEnabled(true); m_deleteAction->setEnabled(true); } else { QMessageBox::warning(this, "Warning", QString("Can not read profiled blocks.\n\nReason:\n%1").arg(m_reader.getError()), QMessageBox::Close); if (m_reader.isFile()) { auto index = m_lastFiles.indexOf(m_reader.filename(), 0); if (index >= 0) { // Remove unexisting file from list m_lastFiles.removeAt(index); auto action = m_loadActionMenu->actions().at(index); m_loadActionMenu->removeAction(action); delete action; } } } m_reader.interrupt(); m_readerTimer.stop(); m_progress->setValue(100); //m_progress->hide(); if (EASY_GLOBALS.all_items_expanded_by_default) { onExpandAllClicked(true); } } else { m_progress->setValue(m_reader.progress()); } } void EasyMainWindow::onFileReaderCancel() { m_readerTimer.stop(); m_reader.interrupt(); m_progress->setValue(100); //m_progress->hide(); } ////////////////////////////////////////////////////////////////////////// EasyFileReader::EasyFileReader() { } EasyFileReader::~EasyFileReader() { interrupt(); } const bool EasyFileReader::isFile() const { return m_isFile; } bool EasyFileReader::done() const { return m_bDone.load(::std::memory_order_acquire); } int EasyFileReader::progress() const { return m_progress.load(::std::memory_order_acquire); } unsigned int EasyFileReader::size() const { return m_size.load(::std::memory_order_acquire); } const QString& EasyFileReader::filename() const { return m_filename; } void EasyFileReader::load(const QString& _filename) { interrupt(); m_isFile = true; m_filename = _filename; m_thread = ::std::move(::std::thread([this](bool _enableStatistics) { m_size.store(fillTreesFromFile(m_progress, m_filename.toStdString().c_str(), m_serializedBlocks, m_serializedDescriptors, m_descriptors, m_blocks, m_blocksTree, m_descriptorsNumberInFile, _enableStatistics, m_errorMessage), ::std::memory_order_release); m_progress.store(100, ::std::memory_order_release); m_bDone.store(true, ::std::memory_order_release); }, EASY_GLOBALS.enable_statistics)); } void EasyFileReader::load(::std::stringstream& _stream) { interrupt(); m_isFile = false; m_filename.clear(); #if defined(__GNUC__) && __GNUC__ < 5 // gcc 4 has a known bug which has been solved in gcc 5: // std::stringstream has no swap() method :( // have to copy all contents... Use gcc 5 or higher! #pragma message "Warning: in gcc 4 and lower std::stringstream has no swap()! Memory consumption may increase! Better use gcc 5 or higher instead." m_stream.str(_stream.str()); #else m_stream.swap(_stream); #endif m_thread = ::std::move(::std::thread([this](bool _enableStatistics) { ::std::ofstream cache_file(NETWORK_CACHE_FILE, ::std::fstream::binary); if (cache_file.is_open()) { cache_file << m_stream.str(); cache_file.close(); } m_size.store(fillTreesFromStream(m_progress, m_stream, m_serializedBlocks, m_serializedDescriptors, m_descriptors, m_blocks, m_blocksTree, m_descriptorsNumberInFile, _enableStatistics, m_errorMessage), ::std::memory_order_release); m_progress.store(100, ::std::memory_order_release); m_bDone.store(true, ::std::memory_order_release); }, EASY_GLOBALS.enable_statistics)); } void EasyFileReader::interrupt() { m_progress.store(-100, ::std::memory_order_release); if (m_thread.joinable()) m_thread.join(); m_bDone.store(false, ::std::memory_order_release); m_progress.store(0, ::std::memory_order_release); m_size.store(0, ::std::memory_order_release); m_serializedBlocks.clear(); m_serializedDescriptors.clear(); m_descriptors.clear(); m_blocks.clear(); m_blocksTree.clear(); m_descriptorsNumberInFile = 0; clear_stream(m_stream); clear_stream(m_errorMessage); } void EasyFileReader::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, QString& _filename) { if (done()) { m_serializedBlocks.swap(_serializedBlocks); m_serializedDescriptors.swap(_serializedDescriptors); ::profiler::descriptors_list_t(::std::move(m_descriptors)).swap(_descriptors); m_blocks.swap(_blocks); m_blocksTree.swap(_tree); m_filename.swap(_filename); _descriptorsNumberInFile = m_descriptorsNumberInFile; } } QString EasyFileReader::getError() { return QString(m_errorMessage.str().c_str()); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onEventTracingPriorityChange(bool _checked) { if (EASY_GLOBALS.connected) m_listener.send(profiler::net::BoolMessage(profiler::net::MESSAGE_TYPE_EVENT_TRACING_PRIORITY, _checked)); } void EasyMainWindow::onEventTracingEnableChange(bool _checked) { if (EASY_GLOBALS.connected) m_listener.send(profiler::net::BoolMessage(profiler::net::MESSAGE_TYPE_EVENT_TRACING_STATUS, _checked)); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onFrameTimeEditFinish() { auto text = m_frameTimeEdit->text(); if (text.contains(QChar(','))) { text.remove(QChar('.')).replace(QChar(','), QChar('.')); m_frameTimeEdit->setText(text); } EASY_GLOBALS.frame_time = text.toFloat() * 1e3f; emit EASY_GLOBALS.events.timelineMarkerChanged(); } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onConnectClicked(bool) { auto text = m_addressEdit->text(); // auto parts = text.split(QChar('.')); // if (parts.size() != 4) // { // QMessageBox::warning(this, "Warning", "Invalid IP-Address", QMessageBox::Close); // // if (EASY_GLOBALS.connected) // { // // Restore last values // m_addressEdit->setText(m_lastAddress); // m_portEdit->setText(QString::number(m_lastPort)); // } // // return; // } // // for (auto& part : parts) // { // int i = 0; // for (; i < part.size(); ++i) // { // if (part[i] != QChar('0')) // break; // } // // if (i < part.size()) // part = part.mid(i); // else // part = "0"; // } QString& address = text;// parts.join(QChar('.')); const decltype(m_lastPort) port = m_portEdit->text().toUShort(); //m_addressEdit->setText(address); const bool isReconnecting = (EASY_GLOBALS.connected && m_listener.port() == port && address.toStdString() == m_listener.address()); if (EASY_GLOBALS.connected) { if (QMessageBox::question(this, isReconnecting ? "Reconnect" : "New connection", QString("Current connection will be broken\n\n%1") .arg(isReconnecting ? "Re-connect?" : "Establish new connection?"), QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) { if (!isReconnecting) { // Restore last values m_addressEdit->setText(m_lastAddress); m_portEdit->setText(QString::number(m_lastPort)); } return; } } profiler::net::EasyProfilerStatus reply(false, false, false); if (!m_listener.connect(address.toStdString().c_str(), port, reply)) { if (EASY_GLOBALS.connected && !isReconnecting) { if (QMessageBox::warning(this, "Warning", QString("Cannot connect to %1\n\nRestore previous connection?").arg(address), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { if (!m_listener.connect(m_lastAddress.toStdString().c_str(), m_lastPort, reply)) { QMessageBox::warning(this, "Warning", "Cannot restore previous connection", QMessageBox::Close); setDisconnected(false); m_lastAddress = ::std::move(address); m_lastPort = port; } else { m_addressEdit->setText(m_lastAddress); m_portEdit->setText(QString::number(m_lastPort)); // QMessageBox::information(this, "Information", "Previous connection restored", QMessageBox::Close); } } else { setDisconnected(false); m_lastAddress = ::std::move(address); m_lastPort = port; } } else { QMessageBox::warning(this, "Warning", QString("Cannot connect to %1").arg(address), QMessageBox::Close); if (EASY_GLOBALS.connected) setDisconnected(false); if (!isReconnecting) { m_lastAddress = ::std::move(address); m_lastPort = port; } } return; } m_lastAddress = ::std::move(address); m_lastPort = port; qInfo() << "Connected successfully"; EASY_GLOBALS.connected = true; m_captureAction->setEnabled(true); SET_ICON(m_connectAction, ":/Connection-on"); disconnect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange); disconnect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange); m_eventTracingEnableAction->setEnabled(true); m_eventTracingPriorityAction->setEnabled(true); m_eventTracingEnableAction->setChecked(reply.isEventTracingEnabled); m_eventTracingPriorityAction->setChecked(reply.isLowPriorityEventTracing); connect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange); connect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange); emit EASY_GLOBALS.events.connectionChanged(true); } void EasyMainWindow::onCaptureClicked(bool) { if (!EASY_GLOBALS.connected) { QMessageBox::warning(this, "Warning", "No connection with profiling app", QMessageBox::Close); return; } if (m_listener.regime() != LISTENER_IDLE) { if (m_listener.regime() == LISTENER_CAPTURE) QMessageBox::warning(this, "Warning", "Already capturing frames.\nFinish old capturing session first.", QMessageBox::Close); else QMessageBox::warning(this, "Warning", "Capturing blocks description.\nFinish old capturing session first.", QMessageBox::Close); return; } if (!m_listener.startCapture()) { // Connection lost. Try to restore connection. profiler::net::EasyProfilerStatus reply(false, false, false); if (!m_listener.connect(m_lastAddress.toStdString().c_str(), m_lastPort, reply)) { setDisconnected(); return; } if (!m_listener.startCapture()) { setDisconnected(); return; } } m_listenerTimer.start(250); m_listenerDialog = new QMessageBox(QMessageBox::Information, "Capturing frames...", "Close this dialog to stop capturing.", QMessageBox::NoButton, this); m_listenerDialog->addButton("Stop", QMessageBox::AcceptRole); m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true); connect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose); m_listenerDialog->show(); } void EasyMainWindow::onGetBlockDescriptionsClicked(bool) { if (!EASY_GLOBALS.connected) { QMessageBox::warning(this, "Warning", "No connection with profiling app", QMessageBox::Close); return; } if (m_listener.regime() != LISTENER_IDLE) { if (m_listener.regime() == LISTENER_DESCRIBE) QMessageBox::warning(this, "Warning", "Already capturing blocks description.\nFinish old capturing session first.", QMessageBox::Close); else QMessageBox::warning(this, "Warning", "Capturing capturing frames.\nFinish old capturing session first.", QMessageBox::Close); return; } m_listenerDialog = new QMessageBox(QMessageBox::Information, "Waiting for blocks...", "This may take some time.", QMessageBox::NoButton, this); m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true); m_listenerDialog->show(); m_listener.requestBlocksDescription(); m_listenerDialog->reject(); m_listenerDialog = nullptr; if (m_listener.size() != 0) { // Read descriptions from stream decltype(EASY_GLOBALS.descriptors) descriptors; decltype(m_serializedDescriptors) serializedDescriptors; ::std::stringstream errorMessage; if (readDescriptionsFromStream(m_listener.data(), serializedDescriptors, descriptors, errorMessage)) { // Merge old and new descriptions bool cancel = false; const bool doFlush = m_descriptorsNumberInFile > descriptors.size(); if (doFlush && !m_serializedBlocks.empty()) { auto button = QMessageBox::question(this, "Information", QString("New blocks description number = %1\nis less than the old one = %2.\nTo avoid possible conflicts\nall profiled data will be deleted.\nContinue?") .arg(descriptors.size()) .arg(m_descriptorsNumberInFile), QMessageBox::Yes, QMessageBox::No); if (button == QMessageBox::Yes) clear(); // Clear all contents because new descriptors list conflicts with old one else cancel = true; } if (!cancel) { if (!doFlush && m_descriptorsNumberInFile < EASY_GLOBALS.descriptors.size()) { // There are dynamically added descriptors, add them to the new list too auto newnumber = static_cast(descriptors.size()); auto size = static_cast(EASY_GLOBALS.descriptors.size()); auto diff = newnumber - size; decltype(newnumber) failnumber = 0; descriptors.reserve(descriptors.size() + EASY_GLOBALS.descriptors.size() - m_descriptorsNumberInFile); for (auto i = m_descriptorsNumberInFile; i < size; ++i) { auto id = EASY_GLOBALS.descriptors[i]->id(); if (id < newnumber) descriptors.push_back(descriptors[id]); else ++failnumber; } if (failnumber != 0) { // There are some errors... // revert changes descriptors.resize(newnumber); // clear all profiled data to avoid conflicts auto button = QMessageBox::question(this, "Information", "There are errors while merging block descriptions lists.\nTo avoid possible conflicts\nall profiled data will be deleted.\nContinue?", QMessageBox::Yes, QMessageBox::No); if (button == QMessageBox::Yes) clear(); // Clear all contents because new descriptors list conflicts with old one else cancel = true; } if (!cancel && diff != 0) { for (auto& b : EASY_GLOBALS.gui_blocks) { if (b.tree.node->id() >= m_descriptorsNumberInFile) b.tree.node->setId(b.tree.node->id() + diff); } m_descriptorsNumberInFile = newnumber; } } if (!cancel) { EASY_GLOBALS.descriptors.swap(descriptors); m_serializedDescriptors.swap(serializedDescriptors); m_descriptorsNumberInFile = static_cast(EASY_GLOBALS.descriptors.size()); if (m_descTreeDialog != nullptr) { #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 static_cast(m_descTreeWidget->widget())->build(); #endif m_dialogDescTree->build(); m_descTreeDialog->raise(); } else { onEditBlocksClicked(true); } } } } else { QMessageBox::warning(this, "Warning", QString("Can not read blocks description from stream.\n\nReason:\n%1").arg(errorMessage.str().c_str()), QMessageBox::Close); } m_listener.clearData(); } if (!m_listener.connected()) { setDisconnected(); } } ////////////////////////////////////////////////////////////////////////// void EasyMainWindow::onBlockStatusChange(::profiler::block_id_t _id, ::profiler::EasyBlockStatus _status) { if (EASY_GLOBALS.connected) m_listener.send(profiler::net::BlockStatusMessage(_id, static_cast(_status))); } ////////////////////////////////////////////////////////////////////////// EasySocketListener::EasySocketListener() : m_receivedSize(0), m_port(0), m_regime(LISTENER_IDLE) { m_bInterrupt = ATOMIC_VAR_INIT(false); m_bConnected = ATOMIC_VAR_INIT(false); m_bStopReceive = ATOMIC_VAR_INIT(false); } EasySocketListener::~EasySocketListener() { m_bInterrupt.store(true, ::std::memory_order_release); if (m_thread.joinable()) m_thread.join(); } bool EasySocketListener::connected() const { return m_bConnected.load(::std::memory_order_acquire); } EasyListenerRegime EasySocketListener::regime() const { return m_regime; } uint64_t EasySocketListener::size() const { return m_receivedSize; } ::std::stringstream& EasySocketListener::data() { return m_receivedData; } const ::std::string& EasySocketListener::address() const { return m_address; } uint16_t EasySocketListener::port() const { return m_port; } void EasySocketListener::clearData() { clear_stream(m_receivedData); m_receivedSize = 0; } bool EasySocketListener::connect(const char* _ipaddress, uint16_t _port, profiler::net::EasyProfilerStatus& _reply) { if (connected()) { m_bInterrupt.store(true, ::std::memory_order_release); if (m_thread.joinable()) m_thread.join(); m_bConnected.store(false, ::std::memory_order_release); m_bInterrupt.store(false, ::std::memory_order_release); } m_address.clear(); m_port = 0; m_easySocket.flush(); m_easySocket.init(); int res = m_easySocket.setAddress(_ipaddress, _port); res = m_easySocket.connect(); const bool isConnected = res == 0; if (isConnected) { static const size_t buffer_size = sizeof(profiler::net::EasyProfilerStatus) << 1; char buffer[buffer_size] = {}; int bytes = 0; while (true) { bytes = m_easySocket.receive(buffer, buffer_size); if (bytes == -1) { if (m_easySocket.isDisconnected()) return false; bytes = 0; continue; } break; } if (bytes == 0) { m_address = _ipaddress; m_port = _port; m_bConnected.store(isConnected, ::std::memory_order_release); return isConnected; } size_t seek = bytes; while (seek < sizeof(profiler::net::EasyProfilerStatus)) { bytes = m_easySocket.receive(buffer + seek, buffer_size - seek); if (bytes == -1) { if (m_easySocket.isDisconnected()) return false; break; } seek += bytes; } auto message = reinterpret_cast(buffer); if (message->isEasyNetMessage() && message->type == profiler::net::MESSAGE_TYPE_ACCEPTED_CONNECTION) _reply = *message; m_address = _ipaddress; m_port = _port; } m_bConnected.store(isConnected, ::std::memory_order_release); return isConnected; } bool EasySocketListener::startCapture() { clearData(); profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_START_CAPTURE); m_easySocket.send(&request, sizeof(request)); if (m_easySocket.isDisconnected()) { m_bConnected.store(false, ::std::memory_order_release); return false; } m_regime = LISTENER_CAPTURE; m_thread = ::std::move(::std::thread(&EasySocketListener::listenCapture, this)); return true; } void EasySocketListener::stopCapture() { if (!m_thread.joinable() || m_regime != LISTENER_CAPTURE) return; m_bStopReceive.store(true, ::std::memory_order_release); //profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_STOP_CAPTURE); //m_easySocket.send(&request, sizeof(request)); m_thread.join(); if (m_easySocket.isDisconnected()) m_bConnected.store(false, ::std::memory_order_release); m_regime = LISTENER_IDLE; m_bStopReceive.store(false, ::std::memory_order_release); } void EasySocketListener::requestBlocksDescription() { clearData(); profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_BLOCKS_DESCRIPTION); m_easySocket.send(&request, sizeof(request)); if(m_easySocket.isDisconnected() ){ m_bConnected.store(false, ::std::memory_order_release); } m_regime = LISTENER_DESCRIBE; listenDescription(); m_regime = LISTENER_IDLE; } ////////////////////////////////////////////////////////////////////////// void EasySocketListener::listenCapture() { // TODO: Merge functions listenCapture() and listenDescription() static const int buffer_size = 8 * 1024 * 1024; char* buffer = new char[buffer_size]; int seek = 0, bytes = 0; auto timeBegin = ::std::chrono::system_clock::now(); bool isListen = true, disconnected = false; while (isListen && !m_bInterrupt.load(::std::memory_order_acquire)) { if (m_bStopReceive.load(::std::memory_order_acquire)) { profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_STOP_CAPTURE); m_easySocket.send(&request, sizeof(request)); m_bStopReceive.store(false, ::std::memory_order_release); } if ((bytes - seek) == 0) { bytes = m_easySocket.receive(buffer, buffer_size); if (bytes == -1) { if (m_easySocket.isDisconnected()) { m_bConnected.store(false, ::std::memory_order_release); isListen = false; disconnected = true; } seek = 0; bytes = 0; continue; } seek = 0; } if (bytes == 0) { isListen = false; break; } char* buf = buffer + seek; if (bytes > 0) { auto message = reinterpret_cast(buf); if (!message->isEasyNetMessage()) continue; switch (message->type) { case profiler::net::MESSAGE_TYPE_ACCEPTED_CONNECTION: { qInfo() << "Receive MESSAGE_TYPE_ACCEPTED_CONNECTION"; //m_easySocket.send(&request, sizeof(request)); seek += sizeof(profiler::net::Message); break; } case profiler::net::MESSAGE_TYPE_REPLY_START_CAPTURING: { qInfo() << "Receive MESSAGE_TYPE_REPLY_START_CAPTURING"; seek += sizeof(profiler::net::Message); break; } case profiler::net::MESSAGE_TYPE_REPLY_BLOCKS_END: { qInfo() << "Receive MESSAGE_TYPE_REPLY_BLOCKS_END"; seek += sizeof(profiler::net::Message); const auto dt = ::std::chrono::duration_cast(::std::chrono::system_clock::now() - timeBegin); const auto bytesNumber = m_receivedData.str().size(); qInfo() << "recieved " << bytesNumber << " bytes, " << dt.count() << " ms, average speed = " << double(bytesNumber) * 1e3 / double(dt.count()) / 1024. << " kBytes/sec"; seek = 0; bytes = 0; isListen = false; break; } case profiler::net::MESSAGE_TYPE_REPLY_BLOCKS: { qInfo() << "Receive MESSAGE_TYPE_REPLY_BLOCKS"; seek += sizeof(profiler::net::DataMessage); profiler::net::DataMessage* dm = (profiler::net::DataMessage*)message; timeBegin = std::chrono::system_clock::now(); int neededSize = dm->size; buf = buffer + seek; auto bytesNumber = ::std::min((int)dm->size, bytes - seek); m_receivedSize += bytesNumber; m_receivedData.write(buf, bytesNumber); neededSize -= bytesNumber; if (neededSize == 0) seek += bytesNumber; else { seek = 0; bytes = 0; } int loaded = 0; while (neededSize > 0) { bytes = m_easySocket.receive(buffer, buffer_size); if (bytes == -1) { if (m_easySocket.isDisconnected()) { m_bConnected.store(false, ::std::memory_order_release); isListen = false; disconnected = true; neededSize = 0; } break; } buf = buffer; int toWrite = ::std::min(bytes, neededSize); m_receivedSize += toWrite; m_receivedData.write(buf, toWrite); neededSize -= toWrite; loaded += toWrite; seek = toWrite; } if (m_bStopReceive.load(::std::memory_order_acquire)) { profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_STOP_CAPTURE); m_easySocket.send(&request, sizeof(request)); m_bStopReceive.store(false, ::std::memory_order_release); } break; } default: //qInfo() << "Receive unknown " << message->type; break; } } } if (disconnected) clearData(); delete [] buffer; } void EasySocketListener::listenDescription() { // TODO: Merge functions listenDescription() and listenCapture() static const int buffer_size = 8 * 1024 * 1024; char* buffer = new char[buffer_size]; int seek = 0, bytes = 0; bool isListen = true, disconnected = false; while (isListen && !m_bInterrupt.load(::std::memory_order_acquire)) { if ((bytes - seek) == 0) { bytes = m_easySocket.receive(buffer, buffer_size); if (bytes == -1) { if (m_easySocket.isDisconnected()) { m_bConnected.store(false, ::std::memory_order_release); isListen = false; disconnected = true; } seek = 0; bytes = 0; continue; } seek = 0; } if (bytes == 0) { isListen = false; break; } char* buf = buffer + seek; if (bytes > 0) { auto message = reinterpret_cast(buf); if (!message->isEasyNetMessage()) continue; switch (message->type) { case profiler::net::MESSAGE_TYPE_ACCEPTED_CONNECTION: { qInfo() << "Receive MESSAGE_TYPE_ACCEPTED_CONNECTION"; seek += sizeof(profiler::net::Message); break; } case profiler::net::MESSAGE_TYPE_REPLY_BLOCKS_DESCRIPTION_END: { qInfo() << "Receive MESSAGE_TYPE_REPLY_BLOCKS_DESCRIPTION_END"; seek += sizeof(profiler::net::Message); seek = 0; bytes = 0; isListen = false; break; } case profiler::net::MESSAGE_TYPE_REPLY_BLOCKS_DESCRIPTION: { qInfo() << "Receive MESSAGE_TYPE_REPLY_BLOCKS"; seek += sizeof(profiler::net::DataMessage); profiler::net::DataMessage* dm = (profiler::net::DataMessage*)message; int neededSize = dm->size; buf = buffer + seek; auto bytesNumber = ::std::min((int)dm->size, bytes - seek); m_receivedSize += bytesNumber; m_receivedData.write(buf, bytesNumber); neededSize -= bytesNumber; if (neededSize == 0) seek += bytesNumber; else{ seek = 0; bytes = 0; } int loaded = 0; while (neededSize > 0) { bytes = m_easySocket.receive(buffer, buffer_size); if (bytes == -1) { if (m_easySocket.isDisconnected()) { m_bConnected.store(false, ::std::memory_order_release); isListen = false; disconnected = true; neededSize = 0; } break; } buf = buffer; int toWrite = ::std::min(bytes, neededSize); m_receivedSize += toWrite; m_receivedData.write(buf, toWrite); neededSize -= toWrite; loaded += toWrite; seek = toWrite; } break; } default: break; } } } if (disconnected) clearData(); delete[] buffer; } //////////////////////////////////////////////////////////////////////////