| // SPDX-License-Identifier: LGPL-2.1 |
| |
| /* |
| * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com> |
| */ |
| |
| /** |
| * @file KsMainWindow.cpp |
| * @brief KernelShark GUI main window. |
| */ |
| |
| // C |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <pwd.h> |
| |
| // C++11 |
| #include <thread> |
| |
| // Qt |
| #include <QMenu> |
| #include <QFileDialog> |
| #include <QMenuBar> |
| #include <QLabel> |
| #include <QLocalSocket> |
| |
| // KernelShark |
| #include "libkshark.h" |
| #include "libkshark-tepdata.h" |
| #include "KsCmakeDef.hpp" |
| #include "KsMainWindow.hpp" |
| #include "KsAdvFilteringDialog.hpp" |
| |
| using namespace KsWidgetsLib; |
| |
| /** Create KernelShark Main window. */ |
| KsMainWindow::KsMainWindow(QWidget *parent) |
| : QMainWindow(parent), |
| _splitter(Qt::Vertical, this), |
| _data(this), |
| _view(this), |
| _graph(this), |
| _mState(this), |
| _plugins(this), |
| _capture(this), |
| _captureLocalServer(this), |
| _openAction("Open Trace File", this), |
| _appendAction("Append Trace File", this), |
| _restoreSessionAction("Restore Last Session", this), |
| _importSessionAction("Import Session", this), |
| _exportSessionAction("Export Session", this), |
| _quitAction("Quit", this), |
| _graphFilterSyncCBox(nullptr), |
| _listFilterSyncCBox(nullptr), |
| _showEventsAction("Show events", this), |
| _showTasksAction("Show tasks", this), |
| _showCPUsAction("Show CPUs", this), |
| _advanceFilterAction("TEP Advance Filtering", this), |
| _clearAllFilters("Clear all filters", this), |
| _cpuSelectAction("CPUs", this), |
| _taskSelectAction("Tasks", this), |
| _managePluginsAction("Manage Plotting plugins", this), |
| _addPluginsAction("Add plugins", this), |
| _captureAction("Record", this), |
| _addOffcetAction("Add Time Offset", this), |
| _colorAction(this), |
| _colSlider(this), |
| _colorPhaseSlider(Qt::Horizontal, this), |
| _fullScreenModeAction("Full Screen Mode", this), |
| _aboutAction("About", this), |
| _contentsAction("Contents", this), |
| _bugReportAction("Report a bug", this), |
| _deselectShortcut(this), |
| _settings(_getCacheDir() + "/setting.ini", QSettings::IniFormat), |
| _workInProgress(this), |
| _updateSessionSize(true) |
| { |
| setWindowTitle("Kernel Shark"); |
| _createActions(); |
| _createMenus(); |
| _initCapture(); |
| _plugins.registerPluginMenues(); |
| |
| if (geteuid() == 0) |
| _rootWarning(); |
| |
| _splitter.addWidget(&_graph); |
| _splitter.addWidget(&_view); |
| setCentralWidget(&_splitter); |
| |
| /* |
| * Add Status bar. First of all remove the bottom margins of the table |
| * so that the Status bar can nicely stick to it. |
| */ |
| QMargins m = _view.layout()->contentsMargins(); |
| m.setBottom(0); |
| _view.layout()->setContentsMargins(m); |
| |
| /* Now create the Status bar and the "Work In Progress" Widget. */ |
| QStatusBar *sb = statusBar(); |
| sb->setFixedHeight(1.2 * FONT_HEIGHT); |
| _workInProgress.addToStatusBar(sb); |
| |
| _graph.setWipPtr(&_workInProgress); |
| _graph.glPtr()->setWipPtr(&_workInProgress); |
| _view.setWipPtr(&_workInProgress); |
| |
| connect(&_splitter, &QSplitter::splitterMoved, |
| this, &KsMainWindow::_splitterMoved); |
| |
| _view.setMarkerSM(&_mState); |
| connect(&_mState, &KsDualMarkerSM::markSwitchForView, |
| &_view, &KsTraceViewer::markSwitch); |
| |
| _graph.setMarkerSM(&_mState); |
| |
| connect(&_mState, &KsDualMarkerSM::updateGraph, |
| &_graph, &KsTraceGraph::markEntry); |
| |
| connect(&_mState, &KsDualMarkerSM::updateView, |
| &_view, &KsTraceViewer::showRow); |
| |
| connect(&_view, &KsTraceViewer::select, |
| &_graph, &KsTraceGraph::markEntry); |
| |
| connect(&_view, &KsTraceViewer::addTaskPlot, |
| &_graph, &KsTraceGraph::addTaskPlot); |
| |
| connect(_graph.glPtr(), &KsGLWidget::updateView, |
| &_view, &KsTraceViewer::showRow); |
| |
| connect(&_graph, &KsTraceGraph::deselect, |
| this, &KsMainWindow::_deselectActive); |
| |
| connect(&_view, &KsTraceViewer::deselect, |
| this, &KsMainWindow::_deselectActive); |
| |
| connect(&_data, &KsDataStore::updateWidgets, |
| &_view, &KsTraceViewer::update); |
| |
| connect(&_data, &KsDataStore::updateWidgets, |
| &_graph, &KsTraceGraph::update); |
| |
| connect(&_plugins, &KsPluginManager::dataReload, |
| &_data, &KsDataStore::reload); |
| |
| _deselectShortcut.setKey(Qt::CTRL | Qt::Key_D); |
| connect(&_deselectShortcut, &QShortcut::activated, |
| this, &KsMainWindow::_deselectActive); |
| |
| connect(&_mState, &KsDualMarkerSM::deselectA, |
| this, &KsMainWindow::_deselectA); |
| |
| connect(&_mState, &KsDualMarkerSM::deselectB, |
| this, &KsMainWindow::_deselectB); |
| |
| _lastDataFilePath = _settings.value("dataPath").toString(); |
| _lastConfFilePath = _settings.value("confPath").toString(); |
| _lastPluginFilePath = _settings.value("pluginPath").toString(); |
| |
| _resizeEmpty(); |
| } |
| |
| /** Destroy KernelShark Main window. */ |
| KsMainWindow::~KsMainWindow() |
| { |
| kshark_context *kshark_ctx(nullptr); |
| QString file = lastSessionFile(); |
| |
| if (!file.isEmpty()) { |
| QByteArray fileBA = file.toLocal8Bit(); |
| |
| _updateSession(); |
| kshark_save_config_file(fileBA.data(), |
| _session.getConfDocPtr()); |
| } |
| |
| _settings.setValue("dataPath", _lastDataFilePath); |
| _settings.setValue("confPath", _lastConfFilePath); |
| _settings.setValue("pluginPath", _lastPluginFilePath); |
| |
| _data.clear(); |
| _plugins.deletePluginDialogs(); |
| |
| /* |
| * Do not show error messages if the "capture" process is still |
| * running (Capture dialog is not closed). |
| */ |
| if (_capture.state() != QProcess::NotRunning) { |
| disconnect(_captureErrorConnection); |
| _capture.close(); |
| _capture.waitForFinished(); |
| } |
| |
| /** |
| * The list of shapes generated by the plugins may contain objects |
| * defined inside the plugins. Make sure to delete these objects now, |
| * because after closing the plugins, the destructors of the |
| * plugins-defined objects will no longer be available. |
| */ |
| _graph.glPtr()->freePluginShapes(); |
| |
| if (kshark_instance(&kshark_ctx)) |
| kshark_free(kshark_ctx); |
| } |
| |
| /** Set the list ot CPU cores to be plotted. */ |
| void KsMainWindow::setCPUPlots(int sd, QVector<int> cpus) |
| { |
| kshark_context *kshark_ctx(nullptr); |
| kshark_data_stream *stream; |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| stream = kshark_get_data_stream(kshark_ctx, sd); |
| if (!stream) |
| return; |
| |
| int nCPUs = stream->n_cpus; |
| auto lamCPUCheck = [=] (int cpu) { |
| if (cpu >= nCPUs) { |
| qWarning() << "Warning: No CPU" << cpu << "found in the data."; |
| return true; |
| } |
| |
| return false; |
| }; |
| |
| cpus.erase(std::remove_if(cpus.begin(), cpus.end(), lamCPUCheck), |
| cpus.end()); |
| |
| _graph.cpuReDraw(sd, cpus); |
| } |
| |
| /** Set the list ot tasks (pids) to be plotted. */ |
| void KsMainWindow::setTaskPlots(int sd, QVector<int> pids) |
| { |
| QVector<int> allPids = KsUtils::getPidList(sd); |
| auto lamPidCheck = [=] (int pid) { |
| int i = allPids.indexOf(pid); |
| if (i < 0) { |
| qWarning() << "Warning: No Pid" << pid << "found in the data."; |
| return true; |
| } |
| |
| return false; |
| }; |
| |
| pids.erase(std::remove_if(pids.begin(), pids.end(), lamPidCheck), |
| pids.end()); |
| |
| _graph.taskReDraw(sd, pids); |
| } |
| |
| /** |
| * Reimplemented event handler used to update the geometry of the window on |
| * resize events. |
| */ |
| void KsMainWindow::resizeEvent(QResizeEvent* event) |
| { |
| QMainWindow::resizeEvent(event); |
| |
| if (_updateSessionSize) { |
| _session.saveMainWindowSize(*this); |
| _session.saveSplitterSize(_splitter); |
| } |
| } |
| |
| void KsMainWindow::_createActions() |
| { |
| /* File menu */ |
| _openAction.setIcon(QIcon::fromTheme("document-open")); |
| _openAction.setShortcut(tr("Ctrl+O")); |
| _openAction.setStatusTip("Open an existing data file"); |
| |
| connect(&_openAction, &QAction::triggered, |
| this, &KsMainWindow::_open); |
| |
| _appendAction.setIcon(QIcon::fromTheme("document-open")); |
| _appendAction.setShortcut(tr("Ctrl+A")); |
| _appendAction.setStatusTip("Append an existing data file"); |
| |
| connect(&_appendAction, &QAction::triggered, |
| this, &KsMainWindow::_append); |
| |
| _restoreSessionAction.setIcon(QIcon::fromTheme("document-open-recent")); |
| connect(&_restoreSessionAction, &QAction::triggered, |
| this, &KsMainWindow::_restoreSession); |
| |
| _importSessionAction.setIcon(QIcon::fromTheme("document-send")); |
| _importSessionAction.setStatusTip("Load a session"); |
| |
| connect(&_importSessionAction, &QAction::triggered, |
| this, &KsMainWindow::_importSession); |
| |
| _exportSessionAction.setIcon(QIcon::fromTheme("document-revert")); |
| _exportSessionAction.setStatusTip("Export this session"); |
| |
| connect(&_exportSessionAction, &QAction::triggered, |
| this, &KsMainWindow::_exportSession); |
| |
| _quitAction.setIcon(QIcon::fromTheme("window-close")); |
| _quitAction.setShortcut(tr("Ctrl+Q")); |
| _quitAction.setStatusTip("Exit KernelShark"); |
| |
| connect(&_quitAction, &QAction::triggered, |
| this, &KsMainWindow::close); |
| |
| /* Filter menu */ |
| connect(&_showEventsAction, &QAction::triggered, |
| this, &KsMainWindow::_showEvents); |
| |
| connect(&_showTasksAction, &QAction::triggered, |
| this, &KsMainWindow::_showTasks); |
| |
| connect(&_showCPUsAction, &QAction::triggered, |
| this, &KsMainWindow::_showCPUs); |
| |
| connect(&_advanceFilterAction, &QAction::triggered, |
| this, &KsMainWindow::_advancedFiltering); |
| |
| connect(&_clearAllFilters, &QAction::triggered, |
| this, &KsMainWindow::_clearFilters); |
| |
| /* Plot menu */ |
| connect(&_cpuSelectAction, &QAction::triggered, |
| this, &KsMainWindow::_cpuSelect); |
| |
| connect(&_taskSelectAction, &QAction::triggered, |
| this, &KsMainWindow::_taskSelect); |
| |
| /* Tools menu */ |
| _managePluginsAction.setShortcut(tr("Ctrl+P")); |
| _managePluginsAction.setIcon(QIcon::fromTheme("preferences-system")); |
| _managePluginsAction.setStatusTip("Manage plugins"); |
| |
| connect(&_managePluginsAction, &QAction::triggered, |
| this, &KsMainWindow::_pluginSelect); |
| |
| _addPluginsAction.setIcon(QIcon::fromTheme("applications-engineering")); |
| _addPluginsAction.setStatusTip("Add plugins"); |
| |
| connect(&_addPluginsAction, &QAction::triggered, |
| this, &KsMainWindow::_pluginAdd); |
| |
| _captureAction.setIcon(QIcon::fromTheme("media-record")); |
| _captureAction.setShortcut(tr("Ctrl+R")); |
| _captureAction.setStatusTip("Capture trace data"); |
| |
| connect(&_captureAction, &QAction::triggered, |
| this, &KsMainWindow::_record); |
| |
| connect(&_addOffcetAction, &QAction::triggered, |
| this, &KsMainWindow::_offset); |
| |
| _colorPhaseSlider.setMinimum(20); |
| _colorPhaseSlider.setMaximum(180); |
| _colorPhaseSlider.setValue(KsPlot::Color::rainbowFrequency() * 100); |
| _colorPhaseSlider.setFixedWidth(FONT_WIDTH * 15); |
| |
| connect(&_colorPhaseSlider, &QSlider::valueChanged, |
| this, &KsMainWindow::_setColorPhase); |
| |
| /* |
| * Updating the colors of the table can be slow. Do this only when |
| * the slider is released. |
| */ |
| connect(&_colorPhaseSlider, &QSlider::sliderReleased, |
| &_view, &KsTraceViewer::loadColors); |
| |
| _colSlider.setLayout(new QHBoxLayout); |
| _colSlider.layout()->addWidget(new QLabel("Color scheme", this)); |
| _colSlider.layout()->addWidget(&_colorPhaseSlider); |
| _colorAction.setDefaultWidget(&_colSlider); |
| |
| _fullScreenModeAction.setIcon(QIcon::fromTheme("view-fullscreen")); |
| _fullScreenModeAction.setShortcut(tr("Ctrl+Shift+F")); |
| _fullScreenModeAction.setStatusTip("Full Screen Mode"); |
| |
| connect(&_fullScreenModeAction, &QAction::triggered, |
| this, &KsMainWindow::_changeScreenMode); |
| |
| /* Help menu */ |
| _aboutAction.setIcon(QIcon::fromTheme("help-about")); |
| |
| connect(&_aboutAction, &QAction::triggered, |
| this, &KsMainWindow::_aboutInfo); |
| |
| _contentsAction.setIcon(QIcon::fromTheme("help-contents")); |
| connect(&_contentsAction, &QAction::triggered, |
| this, &KsMainWindow::_contents); |
| |
| connect(&_bugReportAction, &QAction::triggered, |
| this, &KsMainWindow::_bugReport); |
| } |
| |
| void KsMainWindow::_createMenus() |
| { |
| QMenu *file, *sessions, *filter, *plots, *tools, *help; |
| kshark_context *kshark_ctx(nullptr); |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| /* File menu */ |
| file = menuBar()->addMenu("File"); |
| file->addAction(&_openAction); |
| file->addAction(&_appendAction); |
| |
| sessions = file->addMenu("Sessions"); |
| sessions->setIcon(QIcon::fromTheme("document-properties")); |
| sessions->addAction(&_restoreSessionAction); |
| sessions->addAction(&_importSessionAction); |
| sessions->addAction(&_exportSessionAction); |
| file->addAction(&_quitAction); |
| |
| /* |
| * Enable the "Append Trace File" menu only in the case of multiple |
| * data streams. |
| */ |
| auto lamEnableAppendAction = [this] () { |
| kshark_context *kshark_ctx(nullptr); |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| if (kshark_ctx->n_streams > 0) |
| _appendAction.setEnabled(true); |
| else |
| _appendAction.setEnabled(false); |
| }; |
| |
| connect(file, &QMenu::aboutToShow, lamEnableAppendAction); |
| |
| /* Filter menu */ |
| filter = menuBar()->addMenu("Filter"); |
| |
| connect(filter, &QMenu::aboutToShow, |
| this, &KsMainWindow::_updateFilterMenu); |
| |
| /* |
| * Set the default filter mask. Filter will apply to both View and |
| * Graph. |
| */ |
| kshark_ctx->filter_mask = |
| KS_TEXT_VIEW_FILTER_MASK | KS_GRAPH_VIEW_FILTER_MASK; |
| |
| kshark_ctx->filter_mask |= KS_EVENT_VIEW_FILTER_MASK; |
| |
| _graphFilterSyncCBox = |
| KsUtils::addCheckBoxToMenu(filter, "Apply filters to Graph"); |
| _graphFilterSyncCBox->setChecked(true); |
| |
| connect(_graphFilterSyncCBox, &QCheckBox::stateChanged, |
| this, &KsMainWindow::_graphFilterSync); |
| |
| _listFilterSyncCBox = |
| KsUtils::addCheckBoxToMenu(filter, "Apply filters to List"); |
| _listFilterSyncCBox->setChecked(true); |
| |
| connect(_listFilterSyncCBox, &QCheckBox::stateChanged, |
| this, &KsMainWindow::_listFilterSync); |
| |
| filter->addAction(&_showEventsAction); |
| filter->addAction(&_showTasksAction); |
| filter->addAction(&_showCPUsAction); |
| filter->addAction(&_advanceFilterAction); |
| filter->addAction(&_clearAllFilters); |
| |
| /* |
| * Enable the "TEP Advance Filtering" menu only in the case when TEP |
| * data is loaded. |
| */ |
| auto lamEnableAdvFilterAction = [this] () { |
| kshark_context *kshark_ctx(nullptr); |
| QVector<int> streamIds; |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| _advanceFilterAction.setEnabled(false); |
| streamIds = KsUtils::getStreamIdList(kshark_ctx); |
| for (auto const &sd: streamIds) { |
| if (kshark_is_tep(kshark_ctx->stream[sd])) { |
| _advanceFilterAction.setEnabled(true); |
| break; |
| } |
| } |
| }; |
| |
| connect(filter, &QMenu::aboutToShow, lamEnableAdvFilterAction); |
| |
| /* Plot menu */ |
| plots = menuBar()->addMenu("Plots"); |
| plots->addAction(&_cpuSelectAction); |
| plots->addAction(&_taskSelectAction); |
| |
| /* Tools menu */ |
| tools = menuBar()->addMenu("Tools"); |
| tools->addAction(&_colorAction); |
| tools->addAction(&_fullScreenModeAction); |
| tools->addSeparator(); |
| tools->addAction(&_captureAction); |
| tools->addAction(&_managePluginsAction); |
| tools->addAction(&_addPluginsAction); |
| tools->addAction(&_addOffcetAction); |
| |
| /* |
| * Enable the "Add Time Offset" menu only in the case of multiple |
| * data streams. |
| */ |
| auto lamEnableOffcetAction = [this] () { |
| kshark_context *kshark_ctx(nullptr); |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| if (kshark_ctx->n_streams > 1) |
| _addOffcetAction.setEnabled(true); |
| else |
| _addOffcetAction.setEnabled(false); |
| }; |
| |
| connect(tools, &QMenu::aboutToShow, lamEnableOffcetAction); |
| |
| /* Help menu */ |
| help = menuBar()->addMenu("Help"); |
| help->addAction(&_aboutAction); |
| help->addAction(&_contentsAction); |
| help->addAction(&_bugReportAction); |
| } |
| |
| /** |
| * @brief Add a plugin configuration/control menu. |
| * |
| * @param place: String describing the place to add the menu. For example |
| * "Tools/Plot Latency". |
| * @param func: Function that will launch of plugin control menus. |
| */ |
| void KsMainWindow::addPluginMenu(QString place, pluginActionFunc func) |
| { |
| QStringList dialogPath = place.split("/"); |
| QAction *pluginAction; |
| |
| auto lamAddMenu = [this, func] () { |
| func(this); |
| }; |
| |
| for (auto &m: menuBar()->findChildren<QMenu*>()) { |
| if(dialogPath[0] == m->menuAction()->text()) { |
| pluginAction = new QAction(dialogPath[1], this); |
| m->addAction(pluginAction); |
| |
| connect(pluginAction, &QAction::triggered, |
| lamAddMenu); |
| } |
| } |
| } |
| |
| /** Select the kshark_entry having given index with a given maker. */ |
| void KsMainWindow::markEntry(ssize_t row, DualMarkerState st) |
| { |
| if (row >= 0) { |
| _mState.setState(st); |
| _graph.markEntry(row); |
| _view.showRow(row, true); |
| } |
| } |
| |
| /** Select given kshark_entry with a given maker. */ |
| void KsMainWindow::markEntry(const kshark_entry *e, DualMarkerState st) |
| { |
| ssize_t row; |
| |
| if (!e) { |
| _mState.getMarker(st).reset(); |
| return; |
| } |
| |
| row = kshark_find_entry_by_time(e->ts, _data.rows(), |
| 0, _data.size() - 1); |
| |
| markEntry(row, st); |
| } |
| |
| |
| void KsMainWindow::_open() |
| { |
| QString fileName; |
| |
| fileName = KsUtils::getFile(this, "Open File", |
| "trace-cmd files (*.dat);;All files (*)", |
| _lastDataFilePath); |
| |
| if (!fileName.isEmpty()) |
| loadDataFile(fileName); |
| } |
| |
| void KsMainWindow::_append() |
| { |
| QString fileName = KsUtils::getFile(this, |
| "Append File", |
| "trace-cmd files (*.dat);;Text files (*.txt);;All files (*)", |
| _lastDataFilePath); |
| |
| if (!fileName.isEmpty()) |
| appendDataFile(fileName); |
| } |
| |
| QString KsMainWindow::_getCacheDir() |
| { |
| QString dir; |
| |
| auto lamMakePath = [&] (bool ask) { |
| if (ask) { |
| QMessageBox::StandardButton reply; |
| QString err("KernelShark cache directory not found!\n"); |
| QString question = |
| QString("Do you want to create %1").arg(dir); |
| |
| reply = QMessageBox::question(this, "KernelShark", |
| err + question, |
| QMessageBox::Yes | |
| QMessageBox::No); |
| |
| if (reply == QMessageBox::No) { |
| dir.clear(); |
| return; |
| } |
| } |
| |
| QDir().mkpath(dir); |
| }; |
| |
| auto lamRootHome = [] () { |
| struct passwd *pwd = getpwuid(0); |
| |
| return pwd ? QString(pwd->pw_dir) : QString("/root"); |
| }; |
| |
| dir = getenv("KS_USER_CACHE_DIR"); |
| if (!dir.isEmpty()) { |
| if (!QDir(dir).exists()) |
| lamMakePath(true); |
| } else { |
| auto appCachePath = QStandardPaths::GenericCacheLocation; |
| dir = QStandardPaths::writableLocation(appCachePath); |
| dir += "/kernelshark"; |
| |
| if (geteuid() == 0) |
| dir.replace(QDir::homePath(), lamRootHome()); |
| |
| if (!QDir(dir).exists()) |
| lamMakePath(false); |
| } |
| |
| return dir; |
| } |
| |
| /** Get the description file of the last session. */ |
| QString KsMainWindow::lastSessionFile() |
| { |
| QString file; |
| |
| file = _getCacheDir(); |
| if (!file.isEmpty()) |
| file += "/lastsession.json"; |
| |
| return file; |
| } |
| |
| void KsMainWindow::_restoreSession() |
| { |
| loadSession(lastSessionFile()); |
| } |
| |
| void KsMainWindow::_importSession() |
| { |
| QString fileName; |
| |
| fileName = KsUtils::getFile(this, "Import Session", |
| "Kernel Shark Config files (*.json);;", |
| _lastConfFilePath); |
| |
| if (fileName.isEmpty()) |
| return; |
| |
| loadSession(fileName); |
| } |
| |
| void KsMainWindow::_updateSession() |
| { |
| kshark_context *kshark_ctx(nullptr); |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| _session.saveVisModel(_graph.glPtr()->model()->histo()); |
| _session.saveDataStreams(kshark_ctx); |
| _session.saveGraphs(kshark_ctx, _graph); |
| _session.saveDualMarker(&_mState); |
| _session.saveTable(_view); |
| _session.saveColorScheme(); |
| _session.saveUserPlugins(_plugins); |
| } |
| |
| void KsMainWindow::_exportSession() |
| { |
| QString fileName; |
| |
| fileName = KsUtils::getSaveFile(this, "Export Filter", |
| "Kernel Shark Config files (*.json);;", |
| ".json", |
| _lastConfFilePath); |
| if (fileName.isEmpty()) |
| return; |
| |
| _updateSession(); |
| _session.exportToFile(fileName); |
| } |
| |
| void KsMainWindow::_filterSyncCBoxUpdate(kshark_context *kshark_ctx) |
| { |
| if (kshark_ctx->filter_mask & KS_TEXT_VIEW_FILTER_MASK) |
| _listFilterSyncCBox->setChecked(true); |
| else |
| _listFilterSyncCBox->setChecked(false); |
| |
| if (kshark_ctx->filter_mask & |
| (KS_GRAPH_VIEW_FILTER_MASK | KS_EVENT_VIEW_FILTER_MASK)) |
| _graphFilterSyncCBox->setChecked(true); |
| else |
| _graphFilterSyncCBox->setChecked(false); |
| } |
| |
| void KsMainWindow::_updateFilterMenu() |
| { |
| kshark_context *kshark_ctx(nullptr); |
| |
| if (kshark_instance(&kshark_ctx)) |
| _filterSyncCBoxUpdate(kshark_ctx); |
| } |
| |
| void KsMainWindow::_listFilterSync(int state) |
| { |
| KsUtils::listFilterSync(state); |
| _data.update(); |
| } |
| |
| void KsMainWindow::_graphFilterSync(int state) |
| { |
| KsUtils::graphFilterSync(state); |
| _data.update(); |
| } |
| |
| void KsMainWindow::_presetCBWidget(kshark_hash_id *showFilter, |
| kshark_hash_id *hideFilter, |
| KsCheckBoxWidget *cbw) |
| { |
| if (!kshark_this_filter_is_set(showFilter) && |
| !kshark_this_filter_is_set(hideFilter)) { |
| /* |
| * No filter is set currently. All CheckBoxes of the Widget |
| * will be checked. |
| */ |
| cbw->setDefault(true); |
| } else { |
| QVector<int> ids = cbw->getIds(); |
| QVector<int> status; |
| int n = ids.count(); |
| bool show, hide; |
| |
| if (kshark_this_filter_is_set(showFilter)) { |
| /* |
| * The "show only" filter is set. The default status |
| * of all CheckBoxes will be "unchecked". |
| */ |
| status = QVector<int>(n, false); |
| for (int i = 0; i < n; ++i) { |
| show = !!kshark_hash_id_find(showFilter, |
| ids[i]); |
| |
| hide = !!kshark_hash_id_find(hideFilter, |
| ids[i]); |
| |
| if (show && !hide) { |
| /* |
| * Both "show" and "hide" define this |
| * Id as visible. Set the status of |
| * its CheckBoxes to "checked". |
| */ |
| status[i] = true; |
| } |
| } |
| } else { |
| /* |
| * Only the "do not show" filter is set. The default |
| * status of all CheckBoxes will be "checked". |
| */ |
| status = QVector<int>(n, true); |
| for (int i = 0; i < n; ++i) { |
| hide = !!kshark_hash_id_find(hideFilter, |
| ids[i]); |
| |
| if (hide) |
| status[i] = false; |
| } |
| } |
| |
| cbw->set(status); |
| } |
| } |
| |
| void KsMainWindow::_applyFilter(int sd, QVector<int> all, QVector<int> show, |
| std::function<void(int, QVector<int>)> posFilter, |
| std::function<void(int, QVector<int>)> negFilter) |
| { |
| if (show.count() != 0 && show.count() < all.count() / 2) { |
| posFilter(sd, show); |
| } else { |
| /* |
| * It is more efficiant to apply negative (do not show) filter. |
| */ |
| QVector<int> diff; |
| |
| /* |
| * The Ids may not be sorted, because in the widgets the items |
| * are shown sorted by name. Get those Ids sorted first. |
| */ |
| std::sort(all.begin(), all.end()); |
| std::sort(show.begin(), show.end()); |
| |
| /* |
| * The IDs of the "do not show" filter are given by the |
| * difference between "all" Ids and the Ids of the "show only" |
| * filter. |
| */ |
| std::set_difference(all.begin(), all.end(), |
| show.begin(), show.end(), |
| std::inserter(diff, diff.begin())); |
| |
| negFilter(sd, diff); |
| } |
| } |
| |
| /* Quiet warnings over documenting simple structures */ |
| //! @cond Doxygen_Suppress |
| |
| #define LAMBDA_FILTER(method) [=] (int sd, QVector<int> vec) {method(sd, vec);} |
| |
| //! @endcond |
| |
| void KsMainWindow::_showEvents() |
| { |
| kshark_context *kshark_ctx(nullptr); |
| QVector<KsCheckBoxWidget *> cbws; |
| KsCheckBoxWidget *events_cbw; |
| KsCheckBoxDialog *dialog; |
| kshark_data_stream *stream; |
| QVector<int> streamIds; |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| streamIds = KsUtils::getStreamIdList(kshark_ctx); |
| for (auto const &sd: streamIds) { |
| stream = kshark_ctx->stream[sd]; |
| events_cbw = new KsEventsCheckBoxWidget(stream, this); |
| cbws.append(events_cbw); |
| _presetCBWidget(stream->show_event_filter, |
| stream->hide_event_filter, |
| events_cbw); |
| } |
| |
| dialog = new KsCheckBoxDialog(cbws, this); |
| |
| auto lamFilter = [=] (int sd, QVector<int> show) { |
| QVector<int> all = KsUtils::getEventIdList(sd); |
| _applyFilter(sd, all, show, |
| LAMBDA_FILTER(_data.applyPosEventFilter), |
| LAMBDA_FILTER(_data.applyNegEventFilter)); |
| }; |
| |
| connect(dialog, &KsCheckBoxDialog::apply, lamFilter); |
| |
| dialog->show(); |
| } |
| |
| void KsMainWindow::_showTasks() |
| { |
| kshark_context *kshark_ctx(nullptr); |
| QVector<KsCheckBoxWidget *> cbws; |
| kshark_data_stream *stream; |
| KsCheckBoxWidget *tasks_cbw; |
| KsCheckBoxDialog *dialog; |
| QVector<int> streamIds; |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| streamIds = KsUtils::getStreamIdList(kshark_ctx); |
| for (auto const &sd: streamIds) { |
| stream = kshark_ctx->stream[sd]; |
| tasks_cbw = new KsTasksCheckBoxWidget(stream, true, this); |
| cbws.append(tasks_cbw); |
| _presetCBWidget(stream->show_task_filter, |
| stream->hide_task_filter, |
| tasks_cbw); |
| } |
| |
| dialog = new KsCheckBoxDialog(cbws, this); |
| |
| auto lamFilter = [=] (int sd, QVector<int> show) { |
| QVector<int> all = KsUtils::getPidList(sd); |
| _applyFilter(sd, all, show, |
| LAMBDA_FILTER(_data.applyPosTaskFilter), |
| LAMBDA_FILTER(_data.applyNegTaskFilter)); |
| }; |
| |
| connect(dialog, &KsCheckBoxDialog::apply, lamFilter); |
| |
| dialog->show(); |
| } |
| |
| void KsMainWindow::_showCPUs() |
| { |
| kshark_context *kshark_ctx(nullptr); |
| QVector<KsCheckBoxWidget *> cbws; |
| kshark_data_stream *stream; |
| KsCheckBoxWidget *cpus_cbw; |
| KsCheckBoxDialog *dialog; |
| QVector<int> streamIds; |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| streamIds = KsUtils::getStreamIdList(kshark_ctx); |
| for (auto const &sd: streamIds) { |
| stream = kshark_ctx->stream[sd]; |
| cpus_cbw = new KsCPUCheckBoxWidget(stream, this); |
| cbws.append(cpus_cbw); |
| _presetCBWidget(stream->show_task_filter, |
| stream->hide_task_filter, |
| cpus_cbw); |
| } |
| |
| dialog = new KsCheckBoxDialog(cbws, this); |
| |
| auto lamFilter = [=] (int sd, QVector<int> show) { |
| QVector<int> all = KsUtils::getCPUList(sd); |
| _applyFilter(sd, all, show, |
| LAMBDA_FILTER(_data.applyPosCPUFilter), |
| LAMBDA_FILTER(_data.applyNegCPUFilter)); |
| }; |
| |
| connect(dialog, &KsCheckBoxDialog::apply, lamFilter); |
| |
| dialog->show(); |
| } |
| |
| void KsMainWindow::_advancedFiltering() |
| { |
| KsAdvFilteringDialog *dialog; |
| |
| dialog = new KsAdvFilteringDialog(this); |
| connect(dialog, &KsAdvFilteringDialog::dataReload, |
| &_data, &KsDataStore::reload); |
| |
| dialog->show(); |
| } |
| |
| void KsMainWindow::_clearFilters() |
| { |
| _data.clearAllFilters(); |
| } |
| |
| void KsMainWindow::_cpuSelect() |
| { |
| kshark_context *kshark_ctx(nullptr); |
| QVector<KsCheckBoxWidget *> cbws; |
| kshark_data_stream *stream; |
| KsCheckBoxWidget *cpus_cbd; |
| KsCheckBoxDialog *dialog; |
| QVector<int> streamIds; |
| int nCPUs; |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| streamIds = KsUtils::getStreamIdList(kshark_ctx); |
| for (auto const &sd: streamIds) { |
| stream = kshark_ctx->stream[sd]; |
| cpus_cbd = new KsCPUCheckBoxWidget(stream, this); |
| cbws.append(cpus_cbd); |
| |
| nCPUs = stream->n_cpus; |
| if (nCPUs == _graph.glPtr()->cpuGraphCount(sd)) { |
| cpus_cbd->setDefault(true); |
| } else { |
| QVector<int> v(nCPUs, false); |
| for (auto const &cpu: _graph.glPtr()->_streamPlots[sd]._cpuList) |
| v[cpu] = true; |
| |
| cpus_cbd->set(v); |
| } |
| } |
| |
| dialog = new KsCheckBoxDialog(cbws, this); |
| |
| connect(dialog, &KsCheckBoxDialog::apply, |
| &_graph, &KsTraceGraph::cpuReDraw); |
| |
| dialog->show(); |
| } |
| |
| void KsMainWindow::_taskSelect() |
| { |
| kshark_context *kshark_ctx(nullptr); |
| QVector<KsCheckBoxWidget *> cbws; |
| QVector<int> streamIds, pids; |
| kshark_data_stream *stream; |
| KsCheckBoxWidget *tasks_cbd; |
| KsCheckBoxDialog *dialog; |
| int nPids; |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| streamIds = KsUtils::getStreamIdList(kshark_ctx); |
| for (auto const &sd: streamIds) { |
| stream = kshark_ctx->stream[sd]; |
| tasks_cbd = new KsTasksCheckBoxWidget(stream, true, this); |
| cbws.append(tasks_cbd); |
| |
| pids = KsUtils::getPidList(sd); |
| nPids = pids.count(); |
| if (nPids == _graph.glPtr()->taskGraphCount(sd)) { |
| tasks_cbd->setDefault(true); |
| } else { |
| QVector<int> v(nPids, false); |
| for (int i = 0; i < nPids; ++i) { |
| QVector<int> plots = |
| _graph.glPtr()->_streamPlots[sd]._taskList; |
| for (auto const &pid: plots) { |
| if (pids[i] == pid) { |
| v[i] = true; |
| break; |
| } |
| } |
| } |
| |
| tasks_cbd->set(v); |
| } |
| } |
| |
| dialog = new KsCheckBoxDialog(cbws, this); |
| |
| connect(dialog, &KsCheckBoxDialog::apply, |
| &_graph, &KsTraceGraph::taskReDraw); |
| |
| dialog->show(); |
| } |
| |
| void KsMainWindow::_pluginSelect() |
| { |
| QVector<int> streamIds, enabledPlugins, failedPlugins; |
| kshark_context *kshark_ctx(nullptr); |
| QVector<KsCheckBoxWidget *> cbws; |
| KsPluginCheckBoxWidget *plugin_cbw; |
| KsCheckBoxDialog *dialog; |
| QStringList pluginList; |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| if (kshark_ctx->n_streams == 0) { |
| QString err("Data has to be loaded first."); |
| QMessageBox msgBox; |
| msgBox.critical(nullptr, "Error", err); |
| |
| return; |
| } |
| |
| streamIds = KsUtils::getStreamIdList(kshark_ctx); |
| for (auto const &sd: streamIds) { |
| pluginList = _plugins.getStreamPluginList(sd); |
| enabledPlugins = _plugins.getActivePlugins(sd); |
| failedPlugins = |
| _plugins.getPluginsByStatus(sd, KSHARK_PLUGIN_FAILED); |
| |
| plugin_cbw = new KsPluginCheckBoxWidget(sd, pluginList, this); |
| plugin_cbw->set(enabledPlugins); |
| plugin_cbw->setActive(failedPlugins, false); |
| |
| cbws.append(plugin_cbw); |
| } |
| |
| dialog = new KsPluginsCheckBoxDialog(cbws, &_data, this); |
| dialog->applyStatus(); |
| |
| connect(dialog, &KsCheckBoxDialog::apply, |
| this, &KsMainWindow::_pluginUpdate); |
| |
| dialog->show(); |
| |
| _graph.update(&_data); |
| } |
| |
| void KsMainWindow::_pluginUpdate(int sd, QVector<int> pluginStates) |
| { |
| kshark_context *kshark_ctx(nullptr); |
| QVector<int> streamIds; |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| _plugins.updatePlugins(sd, pluginStates); |
| streamIds = KsUtils::getStreamIdList(kshark_ctx); |
| if (streamIds.size() && streamIds.last() == sd) { |
| /* This is the last stream. Reload the data. */ |
| if (_data.size()) |
| _data.reload(); |
| } |
| } |
| |
| void KsMainWindow::_pluginAdd() |
| { |
| kshark_context *kshark_ctx(nullptr); |
| QStringList fileNames; |
| QVector<int> streams; |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| fileNames = KsUtils::getFiles(this, |
| "Add KernelShark plugins", |
| "KernelShark Plugins (*.so);;", |
| _lastPluginFilePath); |
| |
| if (!fileNames.isEmpty()) { |
| if (kshark_ctx->n_streams > 1) { |
| KsDStreamCheckBoxWidget *stream_cbw; |
| QVector<KsCheckBoxWidget *> cbws; |
| KsCheckBoxDialog *dialog; |
| |
| stream_cbw = new KsDStreamCheckBoxWidget(); |
| cbws.append(stream_cbw); |
| dialog = new KsCheckBoxDialog(cbws, this); |
| |
| auto lamStreams = [&streams] (int, QVector<int> s) { |
| streams = s; |
| }; |
| |
| connect(dialog, &KsCheckBoxDialog::apply, lamStreams); |
| dialog->exec(); |
| } |
| |
| _graph.startOfWork(KsDataWork::UpdatePlugins); |
| |
| _plugins.addPlugins(fileNames, streams); |
| if (_data.size()) |
| _data.reload(); |
| |
| _graph.endOfWork(KsDataWork::UpdatePlugins); |
| } |
| } |
| |
| void KsMainWindow::_record() |
| { |
| bool canDoAsRoot(false); |
| |
| #ifdef DO_AS_ROOT |
| canDoAsRoot = true; |
| #endif |
| |
| if (geteuid() && !canDoAsRoot) { |
| QString message; |
| |
| message = "Record is currently not supported."; |
| message += " Install \"pkexec\" and then do:<br>"; |
| message += " cd build <br> sudo ./cmake_uninstall.sh <br>"; |
| message += " ./cmake_clean.sh <br> cmake .. <br> make <br>"; |
| message += " sudo make install"; |
| |
| _error(message, "recordCantStart", false); |
| return; |
| } |
| |
| _capture.start(); |
| } |
| |
| void KsMainWindow::_offset() |
| { |
| KsTimeOffsetDialog *dialog = new KsTimeOffsetDialog(this); |
| |
| auto lamApplyOffset = [&] (int sd, double ms) { |
| _data.setClockOffset(sd, ms * 1000); |
| _graph.update(&_data); |
| }; |
| |
| connect(dialog, &KsTimeOffsetDialog::apply, lamApplyOffset); |
| } |
| |
| void KsMainWindow::_setColorPhase(int f) |
| { |
| KsPlot::Color::setRainbowFrequency(f / 100.); |
| _graph.glPtr()->loadColors(); |
| _graph.glPtr()->model()->update(); |
| } |
| |
| void KsMainWindow::_changeScreenMode() |
| { |
| if (isFullScreen()) { |
| _fullScreenModeAction.setText("Full Screen Mode"); |
| _fullScreenModeAction.setIcon(QIcon::fromTheme("view-fullscreen")); |
| showNormal(); |
| } else { |
| _fullScreenModeAction.setText("Exit Full Screen Mode"); |
| _fullScreenModeAction.setIcon(QIcon::fromTheme("view-restore")); |
| showFullScreen(); |
| } |
| } |
| |
| void KsMainWindow::_aboutInfo() |
| { |
| KsMessageDialog *message; |
| QString text; |
| |
| text.append(" KernelShark\n\n version: "); |
| text.append(KS_VERSION_STRING); |
| text.append("\n"); |
| |
| message = new KsMessageDialog(text); |
| message->setWindowTitle("About"); |
| message->show(); |
| } |
| |
| void KsMainWindow::_contents() |
| { |
| QDesktopServices::openUrl(QUrl("http://kernelshark.org/", |
| QUrl::TolerantMode)); |
| } |
| |
| void KsMainWindow::_bugReport() |
| { |
| QUrl bugs("https://bugzilla.kernel.org/buglist.cgi?component=Trace-cmd%2FKernelshark&product=Tools&resolution=---", |
| QUrl::TolerantMode); |
| |
| QDesktopServices::openUrl(bugs); |
| } |
| |
| void KsMainWindow::_load(const QString& fileName, bool append) |
| { |
| QString pbLabel("Loading "); |
| bool loadDone = false; |
| struct stat st; |
| double shift(.0); |
| int ret, sd; |
| |
| ret = stat(fileName.toStdString().c_str(), &st); |
| if (ret != 0) { |
| QString text("Unable to find file "); |
| |
| text.append(fileName); |
| text.append("."); |
| _error(text, "loadDataErr1", true); |
| |
| return; |
| } |
| |
| qInfo() << "Loading " << fileName; |
| |
| if (fileName.size() < 40) { |
| pbLabel += fileName; |
| } else { |
| pbLabel += "..."; |
| pbLabel += fileName.sliced(fileName.size() - 37); |
| } |
| |
| setWindowTitle("Kernel Shark"); |
| KsWidgetsLib::KsProgressBar pb(pbLabel); |
| QApplication::processEvents(); |
| |
| _view.reset(); |
| if (!append) |
| _graph.reset(); |
| |
| auto lamLoadJob = [&, this] () { |
| QVector<kshark_dpi *> v; |
| for (auto const p: _plugins.getUserPlugins()) { |
| if (p->process_interface) |
| v.append(p->process_interface); |
| } |
| |
| sd = _data.loadDataFile(fileName, v); |
| loadDone = true; |
| }; |
| |
| auto lamAppendJob = [&, this] () { |
| sd = _data.appendDataFile(fileName, shift); |
| loadDone = true; |
| }; |
| |
| std::thread job; |
| if (append) { |
| job = std::thread(lamAppendJob); |
| } else { |
| job = std::thread(lamLoadJob); |
| } |
| |
| for (int i = 0; i < 160; ++i) { |
| /* |
| * TODO: The way this progress bar gets updated here is a pure |
| * cheat. See if this can be implemented better. |
| */ |
| if (loadDone) |
| break; |
| |
| pb.setValue(i); |
| usleep(150000); |
| } |
| |
| job.join(); |
| |
| if (sd < 0 || !_data.size()) { |
| QString text("File "); |
| |
| text.append(fileName); |
| text.append(" contains no data."); |
| _error(text, "loadDataErr2", true); |
| } |
| |
| _view.loadData(&_data); |
| pb.setValue(175); |
| |
| _graph.loadData(&_data, !append); |
| if (append) |
| _graph.cpuReDraw(sd, KsUtils::getCPUList(sd)); |
| |
| pb.setValue(195); |
| } |
| |
| /** Load trace data for file. */ |
| void KsMainWindow::loadDataFile(const QString& fileName) |
| { |
| _mState.reset(); |
| _load(fileName, false); |
| setWindowTitle("Kernel Shark (" + fileName + ")"); |
| } |
| |
| /** Append trace data for file. */ |
| void KsMainWindow::appendDataFile(const QString& fileName) |
| { |
| kshark_entry *eMarkA(nullptr), *eMarkB(nullptr); |
| int rowA = _mState.markerAPos(); |
| int rowB = _mState.markerBPos(); |
| |
| if (rowA >= 0) |
| eMarkA = _data.rows()[rowA]; |
| |
| if (rowB >= 0) |
| eMarkB = _data.rows()[rowB]; |
| |
| _load(fileName, true); |
| |
| markEntry(eMarkA, DualMarkerState::A); |
| markEntry(eMarkB, DualMarkerState::B); |
| } |
| |
| void KsMainWindow::_error(const QString &mesg, |
| const QString &errCode, |
| bool resize) |
| { |
| QErrorMessage *em = new QErrorMessage(this); |
| QString text = mesg; |
| QString html = mesg; |
| |
| if (resize) |
| _resizeEmpty(); |
| |
| text.replace("<br>", "\n", Qt::CaseInsensitive); |
| html.replace("\n", "<br>", Qt::CaseInsensitive); |
| |
| qCritical().noquote() << "ERROR:" << text; |
| em->showMessage(html, errCode); |
| em->exec(); |
| } |
| |
| /** |
| * @brief Load user session. |
| * |
| * @param fileName: Json file containing the description of the session. |
| */ |
| void KsMainWindow::loadSession(const QString &fileName) |
| { |
| kshark_context *kshark_ctx(nullptr); |
| bool loadDone = false; |
| struct stat st; |
| int ret; |
| |
| if (!kshark_instance(&kshark_ctx)) |
| return; |
| |
| ret = stat(fileName.toStdString().c_str(), &st); |
| if (ret != 0) { |
| QString text("Unable to find session file "); |
| |
| text.append(fileName); |
| text.append("\n"); |
| _error(text, "loadSessErr0", true); |
| |
| return; |
| } |
| |
| KsWidgetsLib::KsProgressBar pb("Loading session settings ..."); |
| pb.setValue(10); |
| |
| _updateSessionSize = false; |
| if (!_session.importFromFile(fileName)) { |
| QString text("Unable to open session description file "); |
| |
| text.append(fileName); |
| text.append(".\n"); |
| _error(text, "loadSessErr1", true); |
| |
| return; |
| } |
| |
| _view.reset(); |
| _graph.reset(); |
| _data.clear(); |
| |
| _session.loadUserPlugins(kshark_ctx, &_plugins); |
| pb.setValue(20); |
| |
| auto lamLoadJob = [&] () { |
| _session.loadDataStreams(kshark_ctx, &_data); |
| loadDone = true; |
| }; |
| |
| std::thread job = std::thread(lamLoadJob); |
| |
| for (int i = 0; i < 150; ++i) { |
| /* |
| * TODO: The way this progress bar gets updated here is a pure |
| * cheat. See if this can be implemented better. |
| */ |
| if (loadDone) |
| break; |
| |
| pb.setValue(i); |
| usleep(300000); |
| } |
| |
| job.join(); |
| |
| _view.loadData(&_data); |
| pb.setValue(155); |
| |
| _graph.loadData(&_data, true); |
| _filterSyncCBoxUpdate(kshark_ctx); |
| pb.setValue(175); |
| |
| _session.loadSplitterSize(&_splitter); |
| _session.loadMainWindowSize(this); |
| _updateSessionSize = true; |
| pb.setValue(180); |
| |
| _session.loadDualMarker(&_mState, &_graph); |
| _session.loadVisModel(_graph.glPtr()->model()); |
| _mState.updateMarkers(_data, _graph.glPtr()); |
| _session.loadGraphs(kshark_ctx, _graph); |
| pb.setValue(190); |
| |
| _session.loadTable(&_view); |
| _colorPhaseSlider.setValue(_session.getColorScheme() * 100); |
| _graph.updateGeom(); |
| } |
| |
| void KsMainWindow::_initCapture() |
| { |
| bool canDoAsRoot(false); |
| |
| #ifdef DO_AS_ROOT |
| canDoAsRoot = true; |
| #endif |
| |
| if (geteuid() && !canDoAsRoot) |
| return; |
| |
| if (geteuid()) { |
| _capture.setProgram("kshark-su-record"); |
| } else { |
| QStringList argv; |
| |
| _capture.setProgram("kshark-record"); |
| argv << QString("-o") << QDir::homePath() + "/trace.dat"; |
| _capture.setArguments(argv); |
| } |
| |
| connect(&_capture, &QProcess::started, |
| this, &KsMainWindow::_captureStarted); |
| |
| connect(&_capture, &QProcess::finished, |
| this, &KsMainWindow::_captureFinished); |
| |
| _captureErrorConnection = |
| connect(&_capture, &QProcess::errorOccurred, |
| this, &KsMainWindow::_captureError); |
| |
| connect(&_captureLocalServer, &QLocalServer::newConnection, |
| this, &KsMainWindow::_readSocket); |
| |
| } |
| |
| void KsMainWindow::_captureStarted() |
| { |
| _captureLocalServer.listen("KSCapture"); |
| } |
| |
| /** |
| * If the authorization could not be obtained because the user dismissed |
| * the authentication dialog (clicked Cancel), pkexec exits with a return |
| * value of 126. |
| */ |
| #define PKEXEC_DISMISS_RET 126 |
| |
| void KsMainWindow::_captureFinished(int ret, QProcess::ExitStatus st) |
| { |
| QProcess *capture = (QProcess *)sender(); |
| |
| _captureLocalServer.close(); |
| |
| if (ret == PKEXEC_DISMISS_RET) { |
| /* |
| * Authorization could not be obtained because the user |
| * dismissed the authentication dialog. |
| */ |
| return; |
| } |
| |
| if (ret != 0 && st == QProcess::NormalExit) |
| _captureErrorMessage(capture); |
| } |
| |
| void KsMainWindow::_captureError(QProcess::ProcessError error) |
| { |
| QProcess *capture = static_cast<QProcess*>(sender()); |
| _captureErrorMessage(capture); |
| } |
| |
| void KsMainWindow::_captureErrorMessage(QProcess *capture) |
| { |
| QString message = "Capture process failed: "; |
| |
| message += capture->errorString(); |
| message += "<br>Standard Error: "; |
| message += capture->readAllStandardError(); |
| _error(message, "captureFinishedErr", false); |
| } |
| |
| void KsMainWindow::_readSocket() |
| { |
| QLocalSocket *socket; |
| quint32 blockSize; |
| QString fileName; |
| |
| auto lamSocketError = [&](QString message) |
| { |
| message = "ERROR from Local Server: " + message; |
| _error(message, "readSocketErr", false); |
| }; |
| |
| socket = _captureLocalServer.nextPendingConnection(); |
| if (!socket) { |
| lamSocketError("Pending connectio not found!"); |
| return; |
| } |
| |
| QDataStream in(socket); |
| socket->waitForReadyRead(); |
| if (socket->bytesAvailable() < (int)sizeof(quint32)) { |
| lamSocketError("Message size is corrupted!"); |
| return; |
| }; |
| |
| in >> blockSize; |
| if (socket->bytesAvailable() < blockSize || in.atEnd()) { |
| lamSocketError("Message is corrupted!"); |
| return; |
| } |
| |
| in >> fileName; |
| loadDataFile(fileName); |
| } |
| |
| void KsMainWindow::_splitterMoved(int pos, int index) |
| { |
| _session.saveSplitterSize(_splitter); |
| } |
| |
| void KsMainWindow::_deselectActive() |
| { |
| _view.clearSelection(); |
| _mState.activeMarker().remove(); |
| _mState.updateLabels(); |
| _graph.glPtr()->model()->update(); |
| } |
| |
| void KsMainWindow::_deselectA() |
| { |
| if (_mState.getState() == DualMarkerState::A) |
| _view.clearSelection(); |
| else |
| _view.passiveMarkerSelectRow(KS_NO_ROW_SELECTED); |
| |
| _mState.markerA().remove(); |
| _mState.updateLabels(); |
| _graph.glPtr()->model()->update(); |
| } |
| |
| void KsMainWindow::_deselectB() |
| { |
| if (_mState.getState() == DualMarkerState::B) |
| _view.clearSelection(); |
| else |
| _view.passiveMarkerSelectRow(KS_NO_ROW_SELECTED); |
| |
| _mState.markerB().remove(); |
| _mState.updateLabels(); |
| _graph.glPtr()->model()->update(); |
| } |
| |
| void KsMainWindow::_rootWarning() |
| { |
| QString cbFlag("noRootWarn"); |
| |
| if (_settings.value(cbFlag).toBool()) |
| return; |
| |
| QMessageBox warn; |
| warn.setText("KernelShark is running with Root privileges."); |
| warn.setInformativeText("Continue at your own risk."); |
| warn.setIcon(QMessageBox::Warning); |
| warn.setStandardButtons(QMessageBox::Close); |
| |
| QCheckBox cb("Don't show this message again."); |
| |
| auto lamCbChec = [&] (int state) { |
| if (state) |
| _settings.setValue(cbFlag, true); |
| }; |
| |
| connect(&cb, &QCheckBox::stateChanged, lamCbChec); |
| warn.setCheckBox(&cb); |
| warn.exec(); |
| } |