From patchwork Tue Oct 16 15:52:57 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759565 Return-Path: Received: from mail-dm3nam03on0058.outbound.protection.outlook.com ([104.47.41.58]:32513 "EHLO NAM03-DM3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726986AbeJPXoN (ORCPT ); Tue, 16 Oct 2018 19:44:13 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" Subject: [PATCH v2 01/23] kernel-shark-qt: Fix a simple bug in KsDataStore::_freeData() Date: Tue, 16 Oct 2018 15:52:57 +0000 Message-ID: <20181016155232.5257-2-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: After freeing the memory used to store the tracing data, this function has to set the _dataSize field to zero. Signed-off-by: Yordan Karadzhov --- kernel-shark-qt/src/KsUtils.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel-shark-qt/src/KsUtils.cpp b/kernel-shark-qt/src/KsUtils.cpp index 13b648e..5e4c9c8 100644 --- a/kernel-shark-qt/src/KsUtils.cpp +++ b/kernel-shark-qt/src/KsUtils.cpp @@ -144,6 +144,8 @@ void KsDataStore::_freeData() free(_rows); _rows = nullptr; } + + _dataSize = 0; } /** Reload the trace data. */ From patchwork Tue Oct 16 15:52:58 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759579 Return-Path: Received: from mail-sn1nam01on0073.outbound.protection.outlook.com ([104.47.32.73]:27872 "EHLO NAM01-SN1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726986AbeJPXoL (ORCPT ); Tue, 16 Oct 2018 19:44:11 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 02/23] kernel-shark-qt: Add Dual Marker for KernelShark GUI. Date: Tue, 16 Oct 2018 15:52:58 +0000 Message-ID: <20181016155232.5257-3-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 14083 From: Yordan Karadzhov (VMware) This patch implements the Dual Marker used by the KernelShark GUI. The Dual Marker uses a simple State Machine having only two states (A and B). In the following patches the State Machine will connect to the Table and Graph widgets (not introduced yet) and will allow the user's actions in one of these widgets to have effect in the other one. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 2 + kernel-shark-qt/src/KsDualMarker.cpp | 332 +++++++++++++++++++++++++++ kernel-shark-qt/src/KsDualMarker.hpp | 188 +++++++++++++++ 3 files changed, 522 insertions(+) create mode 100644 kernel-shark-qt/src/KsDualMarker.cpp create mode 100644 kernel-shark-qt/src/KsDualMarker.hpp diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 2ac79ca..dc86cdf 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -32,11 +32,13 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) message(STATUS "libkshark-gui") set (ks-guiLib_hdr KsUtils.hpp + KsDualMarker.hpp KsWidgetsLib.hpp) QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr}) add_library(kshark-gui SHARED ${ks-guiLib_hdr_moc} KsUtils.cpp + KsDualMarker.cpp KsWidgetsLib.cpp) target_link_libraries(kshark-gui kshark-plot diff --git a/kernel-shark-qt/src/KsDualMarker.cpp b/kernel-shark-qt/src/KsDualMarker.cpp new file mode 100644 index 0000000..ae637aa --- /dev/null +++ b/kernel-shark-qt/src/KsDualMarker.cpp @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsDualMarker.cpp + * @brief KernelShark Dual Marker. + */ + +#include "KsDualMarker.hpp" + +/** + * @brief Create KsGraphMark object. + * + * @param s: The Identifier of the marker (state A or state B). + */ +KsGraphMark::KsGraphMark(DualMarkerState s) +: _color(Qt::darkGreen), + _state(s) +{ + reset(); + _mark._color << _color; +} + +/** + * @brief Create KsGraphMark object. + * + * @param s: The Identifier of the marker (state A or state B). + * @param col: The color of the marker. + */ +KsGraphMark::KsGraphMark(DualMarkerState s, QColor col) +: _color(col), + _state(s) +{ + reset(); + _mark._color << _color; +} + +/** Reset the marker. */ +void KsGraphMark::reset() +{ + _isSet = false; + _bin = -1; + _cpu = -1; + _task = -1; + _pos = 0; + + _mark._visible = false; +} + +/** + * @brief Set the marker. + * + * @param data: Input location for the Data Store object. + * @param histo: Input location for the model descriptor. + * @param pos: The index inside the data array this marker will points to. + * @param cpuGraph: The index of the CPU Graph this marker points to. + * @param taskGraph: The index of the Task Graph this marker points to. + */ +bool KsGraphMark::set(const KsDataStore &data, + kshark_trace_histo *histo, + size_t pos, int cpuGraph, int taskGraph) +{ + _isSet = true; + _pos = pos; + _ts = data.rows()[_pos]->ts; + _cpu = cpuGraph; + _task = taskGraph; + + if (_ts > histo->max || _ts < histo->min) { + _bin = -1; + _mark._visible = false; + return false; + } + + _bin = (_ts - histo->min)/histo->bin_size; + setVisible(true); + + return true; +} + +/** + * @brief Use this function to update the marker when the state of the model + * has changed. + * + * @param data: Input location for the Data Store object. + * @param histo: Input location for the model descriptor. + */ +bool KsGraphMark::update(const KsDataStore &data, kshark_trace_histo *histo) +{ + if (!_isSet) + return false; + + return set(data, histo, this->_pos, this->_cpu, this->_task); +} + +/** Unset the Marker and make it invisible. */ +void KsGraphMark::remove() +{ + _isSet = false; + setVisible(false); +} + +/** An operator for getting the opposite state of the marker identifier. */ +DualMarkerState operator!(const DualMarkerState &state) +{ + if (state == DualMarkerState::B) + return DualMarkerState::A; + + return DualMarkerState::B; +} + +/** @brief Create a Dual Marker State Machine. */ +KsDualMarkerSM::KsDualMarkerSM(QWidget *parent) +: QWidget(parent), + _buttonA("Marker A", this), + _buttonB("Marker B", this), + _labelDeltaDescr(" A,B Delta: ", this), + _markA(DualMarkerState::A, Qt::darkGreen), + _markB(DualMarkerState::B, Qt::darkCyan), + _scCtrlA(this), + _scCtrlB(this) +{ + QString styleSheetA, styleSheetB; + + _buttonA.setFixedWidth(STRING_WIDTH(" Marker A ")); + _buttonB.setFixedWidth(STRING_WIDTH(" Marker B ")); + + for (auto const &l: {&_labelMA, &_labelMB, &_labelDelta}) { + l->setFrameStyle(QFrame::Panel | QFrame::Sunken); + l->setStyleSheet("QLabel {background-color : white;}"); + l->setTextInteractionFlags(Qt::TextSelectableByMouse); + l->setFixedWidth(FONT_WIDTH * 16); + } + + styleSheetA = "background : " + + _markA._color.name() + + "; color : white"; + + _stateA = new QState; + _stateA->setObjectName("A"); + _stateA->assignProperty(&_buttonA, + "styleSheet", + styleSheetA); + + _stateA->assignProperty(&_buttonB, + "styleSheet", + "color : rgb(70, 70, 70)"); + + styleSheetB = "background : " + + _markB._color.name() + + "; color : white"; + + _stateB = new QState; + _stateB->setObjectName("B"); + _stateB->assignProperty(&_buttonA, + "styleSheet", + "color : rgb(70, 70, 70)"); + + _stateB->assignProperty(&_buttonB, + "styleSheet", + styleSheetB); + + /* Define transitions from State A to State B. */ + _stateA->addTransition(this, &KsDualMarkerSM::machineToB, _stateB); + + _scCtrlA.setKey(Qt::CTRL + Qt::Key_A); + _stateA->addTransition(&_scCtrlB, &QShortcut::activated, _stateB); + + connect(&_scCtrlA, &QShortcut::activated, + this, &KsDualMarkerSM::_doStateA); + + _stateA->addTransition(&_buttonB, &QPushButton::clicked, _stateB); + + connect(&_buttonB, &QPushButton::clicked, + this, &KsDualMarkerSM::_doStateB); + + /* Define transitions from State B to State A. */ + _stateB->addTransition(this, &KsDualMarkerSM::machineToA, _stateA); + + _scCtrlB.setKey(Qt::CTRL + Qt::Key_B); + _stateB->addTransition(&_scCtrlA, &QShortcut::activated, _stateA); + + connect(&_scCtrlB, &QShortcut::activated, + this, &KsDualMarkerSM::_doStateB); + + _stateB->addTransition(&_buttonA, &QPushButton::clicked, _stateA); + + connect(&_buttonA, &QPushButton::clicked, + this, &KsDualMarkerSM::_doStateA); + + _machine.addState(_stateA); + _machine.addState(_stateB); + _machine.setInitialState(_stateA); + _markState = DualMarkerState::A; + _machine.start(); +} + +/** + * Reset the Mark A and Mark B and clear the information shown by the Marker's + * toolbar. + */ +void KsDualMarkerSM::reset() +{ + _markA.reset(); + _markB.reset(); + _labelMA.setText(""); + _labelMB.setText(""); + _labelDelta.setText(""); +} + +/** Restart the Dual Marker State Machine. */ +void KsDualMarkerSM::restart() +{ + _machine.stop(); + reset(); + _markState = DualMarkerState::A; + _machine.start(); +} + +void KsDualMarkerSM::_doStateA() +{ + if (_markState != DualMarkerState::A) { + _markState = DualMarkerState::A; + emit markSwitchForView(); + } else if (activeMarker()._isSet) { + emit updateView(activeMarker()._pos, true); + emit updateGraph(activeMarker()._pos); + } +} + +void KsDualMarkerSM::_doStateB() +{ + if (_markState != DualMarkerState::B) { + _markState = DualMarkerState::B; + emit markSwitchForView(); + } else if (activeMarker()._isSet) { + emit updateView(activeMarker()._pos, true); + emit updateGraph(activeMarker()._pos); + } +} + +/** Get the Graph marker associated with a given state. */ +KsGraphMark &KsDualMarkerSM::getMarker(DualMarkerState s) +{ + if (s == DualMarkerState::A) + return _markA; + + return _markB; +} + +/** Position all buttons and labels of the Dual Marker in a toolbar. */ +void KsDualMarkerSM::placeInToolBar(QToolBar *tb) +{ + tb->addWidget(new QLabel(" ")); + tb->addWidget(&_buttonA); + tb->addWidget(&_labelMA); + tb->addSeparator(); + tb->addWidget(new QLabel(" ")); + tb->addWidget(&_buttonB); + tb->addWidget(&_labelMB); + tb->addSeparator(); + tb->addWidget(&_labelDeltaDescr); + tb->addWidget(&_labelDelta); +} + +/** Set the state of the Dual Marker State Machine. */ +void KsDualMarkerSM::setState(DualMarkerState st) { + if (st == _markState) + return; + + if (st == DualMarkerState::A) { + emit machineToA(); + _doStateA(); + } + + if (st == DualMarkerState::B) { + emit machineToB(); + _doStateB(); + } +} + +/** + * @brief Use this function to update the two marker when the state of the + * model has changed. + * + * @param data: Input location for the Data Store object. + * @param histo: Input location for the model descriptor. + */ +void KsDualMarkerSM::updateMarkers(const KsDataStore &data, + kshark_trace_histo *histo) +{ + _markA.update(data, histo); + _markB.update(data, histo); + + updateLabels(); +} + +/** + * @brief Use this function to update the labels when the state of the model + * has changed. + */ +void KsDualMarkerSM::updateLabels() +{ + QString mark, delta; + + // Marker A + if (_markA._isSet) { + mark = KsUtils::Ts2String(_markA._ts, 7); + _labelMA.setText(mark); + } else { + _labelMA.setText(""); + } + + // Marker B + if (_markB._isSet) { + mark = KsUtils::Ts2String(_markB._ts, 7); + _labelMB.setText(mark); + } else { + _labelMB.setText(""); + } + + // Delta + if (_markA._isSet && _markB._isSet) { + delta = KsUtils::Ts2String(_markB._ts - _markA._ts, 7); + _labelDelta.setText(delta); + } else { + _labelDelta.setText(""); + } +} diff --git a/kernel-shark-qt/src/KsDualMarker.hpp b/kernel-shark-qt/src/KsDualMarker.hpp new file mode 100644 index 0000000..401b41c --- /dev/null +++ b/kernel-shark-qt/src/KsDualMarker.hpp @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsDualMarker.hpp + * @brief KernelShark Dual Marker. + */ + +#ifndef _KS_DUAL_MARKER_H +#define _KS_DUAL_MARKER_H + +// Qt +#include + +// KernelShark +#include "KsUtils.hpp" +#include "KsPlotTools.hpp" + +/** The KsGraphMark represents a marker for KernelShark GUI. */ +class KsGraphMark : public QObject +{ + Q_OBJECT +public: + KsGraphMark() = delete; + + KsGraphMark(DualMarkerState s); + + KsGraphMark(DualMarkerState s, QColor col); + + void reset(); + + bool set(const KsDataStore &data, + kshark_trace_histo *histo, + size_t pos, + int cpuGraph, + int taskGraph); + + bool update(const KsDataStore &data, kshark_trace_histo *histo); + + /** Is this marker visible. */ + bool isVisible() const {return _mark._visible;} + + /** Draw this marker. */ + void draw() const {_mark.draw();} + + /** Set the visiblity of the marker. */ + void setVisible(bool v) {_mark._visible = v;} + + void remove(); + +public: + /** Is this marker set. */ + bool _isSet; + + /** The number of the bin this marker points to. */ + int _bin; + + /** The index of the CPU Graph this marker points to. */ + int _cpu; + + /** The index of the Task Graph this marker points to. */ + int _task; + + /** The index inside the data array this marker points to. */ + size_t _pos; + + /** The timestamp of the marker. */ + uint64_t _ts; + + /** The RGB color of the marker. */ + QColor _color; + + /** The Identifier of the marker (A or B). */ + const DualMarkerState _state; + + /** The graphical element of this marker. */ + KsPlot::Mark _mark; +}; + +DualMarkerState operator !(const DualMarkerState &state); + +/** + * The DualMarkerState represents the State Machine of the KernelShark GUI + * Dual Marker. + */ +class KsDualMarkerSM : public QWidget +{ + Q_OBJECT +public: + explicit KsDualMarkerSM(QWidget *parent = nullptr); + + void reset(); + + void restart(); + + void placeInToolBar(QToolBar *tb); + + /** Get the Identifier of the current state of the State Machine. */ + DualMarkerState getState() const {return _markState;} + + void setState(DualMarkerState st); + + KsGraphMark &getMarker(DualMarkerState s); + + /** Get the active marker. */ + KsGraphMark &activeMarker() {return getMarker(_markState);} + + /** Get the passive marker. */ + KsGraphMark &passiveMarker() {return getMarker(!_markState);} + + /** Get the marker A. */ + KsGraphMark &markerA() {return _markA;} + + /** Get the marker B. */ + KsGraphMark &markerB() {return _markB;} + + /** Get a pointer to the State A object. */ + QState *stateAPtr() {return _stateA;} + + /** Get a pointer to the State B object. */ + QState *stateBPtr() {return _stateB;} + + void updateMarkers(const KsDataStore &data, + kshark_trace_histo *histo); + + void updateLabels(); + +signals: + /** + * This signal is emitted when the Table View has to switch the color + * of the selected row. + */ + void markSwitchForView(); + + /** + * This signal is emitted when the Table View has to show a different + * entry (row). + */ + void updateView(size_t pos, bool mark); + + /** + * This signal is emitted when the Graph mark has to show a different + * entry. + */ + void updateGraph(size_t pos); + + /** + * This signal is emitted when the State Machine has to switch to + * state A. + */ + void machineToA(); + + /** + * This signal is emitted when the State Machine has to switch to + * state B. + */ + void machineToB(); + +private: + QPushButton _buttonA; + + QPushButton _buttonB; + + QLabel _labelMA, _labelMB, _labelDelta; + + QLabel _labelDeltaDescr; + + QState *_stateA; + + QState *_stateB; + + QStateMachine _machine; + + DualMarkerState _markState; + + KsGraphMark _markA, _markB; + + QShortcut _scCtrlA, _scCtrlB; + + void _doStateA(); + + void _doStateB(); +}; + +#endif From patchwork Tue Oct 16 15:52:59 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759585 Return-Path: Received: from mail-sn1nam01on0073.outbound.protection.outlook.com ([104.47.32.73]:27872 "EHLO NAM01-SN1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726778AbeJPXoO (ORCPT ); Tue, 16 Oct 2018 19:44:14 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 03/23] kernel-shark-qt: Add model for showing trace data in a text format. Date: Tue, 16 Oct 2018 15:52:59 +0000 Message-ID: <20181016155232.5257-4-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 15564 From: Yordan Karadzhov (VMware) This patch defines the model used by the Trace Viewer widget used to display trace data in a table-like form. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 2 + kernel-shark-qt/src/KsModels.cpp | 332 +++++++++++++++++++++++++++++ kernel-shark-qt/src/KsModels.hpp | 223 +++++++++++++++++++ 3 files changed, 557 insertions(+) create mode 100644 kernel-shark-qt/src/KsModels.cpp create mode 100644 kernel-shark-qt/src/KsModels.hpp diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index dc86cdf..3fd518b 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -32,12 +32,14 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) message(STATUS "libkshark-gui") set (ks-guiLib_hdr KsUtils.hpp + KsModels.hpp KsDualMarker.hpp KsWidgetsLib.hpp) QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr}) add_library(kshark-gui SHARED ${ks-guiLib_hdr_moc} KsUtils.cpp + KsModels.cpp KsDualMarker.cpp KsWidgetsLib.cpp) diff --git a/kernel-shark-qt/src/KsModels.cpp b/kernel-shark-qt/src/KsModels.cpp new file mode 100644 index 0000000..095e26b --- /dev/null +++ b/kernel-shark-qt/src/KsModels.cpp @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsModels.cpp + * @brief Models for data representation. + */ + +// KernelShark +#include "KsModels.hpp" +#include "KsWidgetsLib.hpp" +#include "KsUtils.hpp" + +/** Create a default (empty) KsFilterProxyModel object. */ +KsFilterProxyModel::KsFilterProxyModel(QObject *parent) +: QSortFilterProxyModel(parent), + _searchStop(false), + _source(nullptr) +{} + +/** + * Returns False if the item in the row indicated by the sourceRow and + * sourceParentshould be filtered out. Otherwise returns True. + */ +bool +KsFilterProxyModel::filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const +{ + if (_data[sourceRow]->visible & KS_TEXT_VIEW_FILTER_MASK) + return true; + + return false; +} + +/** Provide the Proxy model with data. */ +void KsFilterProxyModel::fill(KsDataStore *data) +{ + _data = data->rows(); +} + +/** Set the source model for this Proxy model. */ +void KsFilterProxyModel::setSource(KsViewModel *s) +{ + QSortFilterProxyModel::setSourceModel(s); + _source = s; +} + +void KsFilterProxyModel::_search(int column, + const QString &searchText, + condition_func cond, + QList *matchList, + int first, int last, + QProgressBar *pb, + QLabel *l, + bool notify) +{ + int row, nRows(last - first + 1); + int pbCount(1); + QVariant item; + QString text; + + _searchProgress = 0; + + if (nRows > KS_PROGRESS_BAR_MAX) + pbCount = nRows / KS_PROGRESS_BAR_MAX; + else + _searchProgress = KS_PROGRESS_BAR_MAX - nRows; + + /* Loop over the items of the proxy model. */ + for (int r = first; r <= last; ++r) { + /* + * Use the index of the proxy model to retrieve the value + * of the row number in the base model. + */ + row = mapRowFromSource(r); + item = _source->getValue(column, row); + if (cond(searchText, item.toString())) { + matchList->append(row); + } + + if (_searchStop) { + _searchStop = false; + break; + } + + /* Deal with the Progress bar of the seatch. */ + if ((r - first) % pbCount == 0) { + if (notify) { + std::lock_guard lk(_mutex); + ++_searchProgress; + _pbCond.notify_one(); + } else { + if (pb) + pb->setValue(pb->value() + 1); + if (l) + l->setText(QString(" %1").arg(matchList->count())); + QApplication::processEvents(); + } + } + } +} + +/** @brief Search the content of the table for a data satisfying an abstract + * condition. + * + * @param column: The number of the column to search in. + * @param searchText: The text to search for. + * @param cond: Matching condition function. + * @param matchList: Output location for a list containing the row indexes of + * the cells satisfying matching condition. + * @param pb: Input location for a Progressbar used to visualize the progress + * of the search. + * @param l: Input location for a label used to show the number of cells found. + * + * @returns The number of cells satisfying the matching condition. + */ +size_t KsFilterProxyModel::search(int column, + const QString &searchText, + condition_func cond, + QList *matchList, + QProgressBar *pb, + QLabel *l) +{ + int nRows = rowCount({}); + + _search(column, searchText, cond, matchList, 0, nRows - 1, + pb, l, false); + + return matchList->count(); +} + +/** @brief Search the content of the table for a data satisfying an abstract + * condition. + * + * @param column: The number of the column to search in. + * @param searchText: The text to search for. + * @param cond: Matching condition function. + * @param first: Row index specifying the position inside the table from + * where the search starts. + * @param last: Row index specifying the position inside the table from + * where the search ends. + * @param notify: Input location for flag specifying if the search has to + * notify the main thread when to update the progress bar. + * + * @returns A list containing the row indexes of the cells satisfying matching + * condition. + */ +QList KsFilterProxyModel::searchMap(int column, + const QString &searchText, + condition_func cond, + int first, + int last, + bool notify) +{ + QList matchList; + qInfo() << "searchMap" << first << last; + _search(column, searchText, cond, &matchList, first, last, + nullptr, nullptr, notify); + + return matchList; +} + +/** Create default (empty) KsViewModel object. */ +KsViewModel::KsViewModel(QObject *parent) +: QAbstractTableModel(parent), + _data(nullptr), + _nRows(0), + _header({"#", "CPU", "Time Stamp", "Task", "PID", + "Latency", "Event", "Info"}), + _markA(-1), + _markB(-1) +{} + +/** + * Get the data stored under the given role for the item referred to by + * the index. This is an implementation of the pure virtual method of the + * abstract model class. + */ +QVariant KsViewModel::data(const QModelIndex &index, int role) const +{ + if (role == Qt::ForegroundRole) { + if (index.row() == _markA) + return QVariant::fromValue(QColor(Qt::white)); + + if (index.row() == _markB) + return QVariant::fromValue(QColor(Qt::white)); + } + + if (role == Qt::BackgroundRole) { + if (index.row() == _markA) + return QVariant::fromValue(QColor(_colorMarkA)); + + if (index.row() == _markB) + return QVariant::fromValue(QColor(_colorMarkB)); + } + + if (role == Qt::DisplayRole) + return this->getValue(index.column(), index.row()); + + return {}; +} + +/** Get the data stored in a given cell of the table. */ +QVariant KsViewModel::getValue(int column, int row) const +{ + switch (column) { + case TRACE_VIEW_COL_INDEX : + return row; + + case TRACE_VIEW_COL_CPU: + return _data[row]->cpu; + + case TRACE_VIEW_COL_TS: + return KsUtils::Ts2String(_data[row]->ts, 6); + + case TRACE_VIEW_COL_COMM: + return kshark_get_task_easy(_data[row]); + + case TRACE_VIEW_COL_PID: + return kshark_get_pid_easy(_data[row]); + + case TRACE_VIEW_COL_LAT: + return kshark_get_latency_easy(_data[row]); + + case TRACE_VIEW_COL_EVENT: + return kshark_get_event_name_easy(_data[row]); + + case TRACE_VIEW_COL_INFO : + return kshark_get_info_easy(_data[row]); + + default: + return {}; + } +} + +/** + * Get the header of a column. This is an implementation of the pure virtual + * method of the abstract model class. + */ +QVariant KsViewModel::headerData(int column, + Qt::Orientation orientation, + int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return {}; + + if (column < _header.count()) + return _header.at(column); + + return {}; +} + +/** Provide the model with data. */ +void KsViewModel::fill(KsDataStore *data) +{ + beginInsertRows(QModelIndex(), 0, data->size() - 1); + + _data = data->rows(); + _nRows = data->size(); + + endInsertRows(); +} + +/** @brief Select a row in the table. + * + * @param state: Identifier of the marker used to select the row. + * @param row: Row index. + */ +void KsViewModel::selectRow(DualMarkerState state, int row) +{ + if (state == DualMarkerState::A) { + _markA = row; + _markB = -1; + } else { + _markB = row; + _markA = -1; + } +} + +/** Reset the model. */ +void KsViewModel::reset() +{ + beginResetModel(); + + _data = nullptr; + _nRows = 0; + + endResetModel(); +} + +/** Update the model. Use this function if the data has changed. */ +void KsViewModel::update(KsDataStore *data) +{ + /* + * Do not try to skip the reset(). The row count has to be set to + * zero before you full. + */ + reset(); + fill(data); +} + +/** @brief Search the content of the table for a data satisfying an abstract + * condition. + * + * @param column: The number of the column to search in. + * @param searchText: The text to search for. + * @param cond: Matching condition function. + * @param matchList: Output location for a list containing the row indexes of + * the cells satisfying the matching condition. + * + * @returns The number of cells satisfying the matching condition. + */ +size_t KsViewModel::search(int column, + const QString &searchText, + condition_func cond, + QList *matchList) +{ + int nRows = rowCount({}); + QVariant item; + + for (int r = 0; r < nRows; ++r) { + item = getValue(r, column); + if (cond(searchText, item.toString())) { + matchList->append(r); + } + } + + return matchList->count(); +} diff --git a/kernel-shark-qt/src/KsModels.hpp b/kernel-shark-qt/src/KsModels.hpp new file mode 100644 index 0000000..8935fd4 --- /dev/null +++ b/kernel-shark-qt/src/KsModels.hpp @@ -0,0 +1,223 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsModels.hpp + * @brief Models for data representation. + */ + +#ifndef _KS_MODELS_H +#define _KS_MODELS_H + +// C++11 +#include +#include + +// Qt +#include +#include +#include +#include +#include + +// KernelShark +#include "libkshark.h" +#include "libkshark-model.h" + +/** Matching condition function type. To be user for searching. */ +typedef bool (*condition_func)(QString, QString); + +enum class DualMarkerState; + +class KsDataStore; + +/** + * Class KsViewModel provides models for trace data representation in a + * table view. + */ +class KsViewModel : public QAbstractTableModel +{ +public: + explicit KsViewModel(QObject *parent = nullptr); + + /** Set the colors of the two markers. */ + void setColors(const QColor &colA, const QColor &colB) { + _colorMarkA = colA; + _colorMarkB = colB; + }; + + /** + * Get the number of rows. This is an implementation of the pure + * virtual method of the abstract model class. + */ + int rowCount(const QModelIndex &) const override {return _nRows;} + + /** + * Get the number of columns. This is an implementation of the pure + * virtual method of the abstract model class. + */ + int columnCount(const QModelIndex &) const override + { + return _header.count(); + } + + QVariant headerData(int section, + Qt::Orientation orientation, + int role) const override; + + QVariant data(const QModelIndex &index, int role) const override; + + void fill(KsDataStore *data); + + void selectRow(DualMarkerState state, int row); + + void reset(); + + void update(KsDataStore *data); + + /** Get the list of column's headers. */ + QStringList header() const {return _header;} + + QVariant getValue(int column, int row) const; + + size_t search(int column, + const QString &searchText, + condition_func cond, + QList *matchList); + + /** Table columns Identifiers. */ + enum { + /** Identifier of the Index column. */ + TRACE_VIEW_COL_INDEX, + + /** Identifier of the CPU column. */ + TRACE_VIEW_COL_CPU, + + /** Identifier of the Timestamp column. */ + TRACE_VIEW_COL_TS, + + /** Identifier of the Task name (command) column. */ + TRACE_VIEW_COL_COMM, + + /** Identifier of the Process Id column. */ + TRACE_VIEW_COL_PID, + + /** Identifier of the Latency Id column. */ + TRACE_VIEW_COL_LAT, + + /** Identifier of the Event name Id column. */ + TRACE_VIEW_COL_EVENT, + + /** Identifier of the Event name Id column. */ + TRACE_VIEW_COL_INFO, + + /** Number of column. */ + TRACE_VIEW_N_COLUMNS, + }; + +private: + /** Trace data array. */ + kshark_entry **_data; + + /** The size of the data array. */ + size_t _nRows; + + /** The headers of the individual columns. */ + QStringList _header; + + /** The index of marker A inside the data array. */ + int _markA; + + /** The index of marker A inside the data array. */ + int _markB; + + /** The color of the row selected by marker A. */ + QColor _colorMarkA; + + /** The color of the row selected by marker B. */ + QColor _colorMarkB; +}; + +/** + * Class KsFilterProxyModel provides support for filtering trace data in + * table view. + */ +class KsFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + explicit KsFilterProxyModel(QObject *parent = nullptr); + + bool filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const override; + + void fill(KsDataStore *data); + + void setSource(KsViewModel *s); + + size_t search(int column, + const QString &searchText, + condition_func cond, + QList *matchList, + QProgressBar *pb = nullptr, + QLabel *l = nullptr); + + QList searchMap(int column, + const QString &searchText, + condition_func cond, + int first, + int last, + bool notify); + + /** Get the progress of the search. */ + int searchProgress() const {return _searchProgress;} + + /** Reset the progress value of the search. */ + void searchReset() {_searchProgress = 0;} + + /** Stop the serch for all threads. */ + void searchStop() {_searchStop = true;} + + /** + * Use the "row" index in the Proxy model to retrieve the "row" index + * in the source model. + */ + int mapRowFromSource(int r) const + { + /*This works because the row number is shown in column "0". */ + return this->data(this->index(r, 0)).toInt(); + } + + /** + * A condition variable used to notify the main thread to update the + * search progressbar. + */ + std::condition_variable _pbCond; + + /** A mutex used by the condition variable. */ + std::mutex _mutex; + +private: + int _searchProgress; + + bool _searchStop; + + /** Trace data array. */ + kshark_entry **_data; + + KsViewModel *_source; + + void _search(int column, + const QString &searchText, + condition_func cond, + QList *matchList, + int first, int last, + QProgressBar *pb, + QLabel *l, + bool notify); +}; + +#endif // _KS_MODELS_H From patchwork Tue Oct 16 15:53:01 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759567 Return-Path: Received: from mail-sn1nam01on0073.outbound.protection.outlook.com ([104.47.32.73]:27872 "EHLO NAM01-SN1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726986AbeJPXoS (ORCPT ); Tue, 16 Oct 2018 19:44:18 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 04/23] kernel-shark-qt: Add Trace Viewer widget. Date: Tue, 16 Oct 2018 15:53:01 +0000 Message-ID: <20181016155232.5257-5-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 22885 From: Yordan Karadzhov (VMware) This patch defines widget for browsing in the trace data shown in a text form. The provides a search panel and a Table view area. The panel and the table are integrated with the Dual Marker. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 6 +- kernel-shark-qt/src/KsTraceViewer.cpp | 657 ++++++++++++++++++++++++++ kernel-shark-qt/src/KsTraceViewer.hpp | 149 ++++++ 3 files changed, 810 insertions(+), 2 deletions(-) create mode 100644 kernel-shark-qt/src/KsTraceViewer.cpp create mode 100644 kernel-shark-qt/src/KsTraceViewer.hpp diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 3fd518b..3f40930 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -34,14 +34,16 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) set (ks-guiLib_hdr KsUtils.hpp KsModels.hpp KsDualMarker.hpp - KsWidgetsLib.hpp) + KsWidgetsLib.hpp + KsTraceViewer.hpp) QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr}) add_library(kshark-gui SHARED ${ks-guiLib_hdr_moc} KsUtils.cpp KsModels.cpp KsDualMarker.cpp - KsWidgetsLib.cpp) + KsWidgetsLib.cpp + KsTraceViewer.cpp) target_link_libraries(kshark-gui kshark-plot ${CMAKE_DL_LIBS} diff --git a/kernel-shark-qt/src/KsTraceViewer.cpp b/kernel-shark-qt/src/KsTraceViewer.cpp new file mode 100644 index 0000000..7f12365 --- /dev/null +++ b/kernel-shark-qt/src/KsTraceViewer.cpp @@ -0,0 +1,657 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsTraceViewer.cpp + * @brief KernelShark Trace Viewer widget. + */ + +// C++11 +#include +#include + +// KernelShark +#include "KsTraceViewer.hpp" +#include "KsWidgetsLib.hpp" + +/** Create a default (empty) Trace viewer widget. */ +KsTraceViewer::KsTraceViewer(QWidget *parent) +: QWidget(parent), + _view(this), + _model(this), + _proxyModel(this), + _tableHeader(_model.header()), + _toolbar(this), + _labelSearch("Search: Column", this), + _labelGrFollows("Graph follows ", this), + _columnComboBox(this), + _selectComboBox(this), + _searchLineEdit(this), + _prevButton("Prev", this), + _nextButton("Next", this), + _searchStopButton(QIcon::fromTheme("process-stop"), "", this), + _pbAction(nullptr), + _graphFollowsCheckBox(this), + _searchProgBar(this), + _searchCountLabel("", this), + _searchDone(false), + _graphFollows(true), + _mState(nullptr), + _data(nullptr) +{ + int bWidth; + + this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + + /* Make a search toolbar. */ + _toolbar.setOrientation(Qt::Horizontal); + _toolbar.setMaximumHeight(FONT_HEIGHT * 1.75); + + /* On the toolbar make two Combo boxes for the search settings. */ + _toolbar.addWidget(&_labelSearch); + _columnComboBox.addItems(_tableHeader); + + /* + * Using the old Signal-Slot syntax because + * QComboBox::currentIndexChanged has overloads. + */ + connect(&_columnComboBox, SIGNAL(currentIndexChanged(int)), + this, SLOT(_searchEdit(int))); + + _toolbar.addWidget(&_columnComboBox); + + _selectComboBox.addItem("contains"); + _selectComboBox.addItem("full match"); + _selectComboBox.addItem("does not have"); + + /* + * Using the old Signal-Slot syntax because + * QComboBox::currentIndexChanged has overloads. + */ + connect(&_selectComboBox, SIGNAL(currentIndexChanged(int)), + this, SLOT(_searchEdit(int))); + + _toolbar.addWidget(&_selectComboBox); + + /* On the toolbar, make a Line edit field for search. */ + _searchLineEdit.setMaximumWidth(FONT_WIDTH * 20); + + connect(&_searchLineEdit, &QLineEdit::returnPressed, + this, &KsTraceViewer::_search); + + connect(&_searchLineEdit, &QLineEdit::textEdited, + this, &KsTraceViewer::_searchEditText); + + _toolbar.addWidget(&_searchLineEdit); + _toolbar.addSeparator(); + + /* On the toolbar, add Prev & Next buttons. */ + bWidth = FONT_WIDTH * 6; + + _nextButton.setFixedWidth(bWidth); + _toolbar.addWidget(&_nextButton); + connect(&_nextButton, &QPushButton::pressed, + this, &KsTraceViewer::_next); + + _prevButton.setFixedWidth(bWidth); + _toolbar.addWidget(&_prevButton); + connect(&_prevButton, &QPushButton::pressed, + this, &KsTraceViewer::_prev); + + _toolbar.addSeparator(); + _searchProgBar.setMaximumWidth(FONT_WIDTH * 10); + _searchProgBar.setRange(0, 200); + _pbAction = _toolbar.addWidget(&_searchProgBar); + _pbAction->setVisible(false); + _toolbar.addWidget(&_searchCountLabel); + _searchStopAction = _toolbar.addWidget(&_searchStopButton); + _searchStopAction->setVisible(false); + connect(&_searchStopButton, &QPushButton::pressed, + this, &KsTraceViewer::_searchStop); + + /* + * On the toolbar, make a Check box for connecting the search pannel + * to the Graph widget. + */ + _toolbar.addSeparator(); + _toolbar.addWidget(&_graphFollowsCheckBox); + _toolbar.addWidget(&_labelGrFollows); + _graphFollowsCheckBox.setCheckState(Qt::Checked); + connect(&_graphFollowsCheckBox, &QCheckBox::stateChanged, + this, &KsTraceViewer::_graphFollowsChanged); + + /* Initialize the trace viewer. */ + _view.horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); + _view.verticalHeader()->setVisible(false); + _view.setEditTriggers(QAbstractItemView::NoEditTriggers); + _view.setSelectionBehavior(QAbstractItemView::SelectRows); + _view.setSelectionMode(QAbstractItemView::SingleSelection); + _view.verticalHeader()->setDefaultSectionSize(FONT_HEIGHT * 1.25); + + _proxyModel.setSource(&_model); + _view.setModel(&_proxyModel); + connect(&_proxyModel, &QAbstractItemModel::modelReset, + this, &KsTraceViewer::_searchReset); + + _view.setContextMenuPolicy(Qt::CustomContextMenu); + connect(&_view, &QTableView::customContextMenuRequested, + this, &KsTraceViewer::_onCustomContextMenu); + + connect(&_view, &QTableView::clicked, + this, &KsTraceViewer::_clicked); + + /* Set the layout. */ + _layout.addWidget(&_toolbar); + _layout.addWidget(&_view); + this->setLayout(&_layout); +} + +/** + * @brief Load and show trace data. + * + * @param data: Input location for the KsDataStore object. + * KsDataStore::loadDataFile() must be called first. + */ +void KsTraceViewer::loadData(KsDataStore *data) +{ + _data = data; + _model.reset(); + _proxyModel.fill(data); + _model.fill(data); + this->_resizeToContents(); + + this->setMinimumHeight(SCREEN_HEIGHT / 5); +} + +/** Connect the QTableView widget and the State machine of the Dual marker. */ +void KsTraceViewer::setMarkerSM(KsDualMarkerSM *m) +{ + QString styleSheetA, styleSheetB; + + _mState = m; + _model.setColors(_mState->markerA()._color, + _mState->markerB()._color); + + /* + * Assign a property to State A of the Dual marker state machine. When + * the marker is in State A the background color of the selected row + * will be the same as the color of Marker A. + */ + styleSheetA = "selection-background-color : " + + _mState->markerA()._color.name() + ";"; + + _mState->stateAPtr()->assignProperty(&_view, "styleSheet", + styleSheetA); + + /* + * Assign a property to State B. When the marker is in State B the + * background color of the selected row will be the same as the color + * of Marker B. + */ + styleSheetB = "selection-background-color : " + + _mState->markerB()._color.name() + ";"; + + _mState->stateBPtr()->assignProperty(&_view, "styleSheet", + styleSheetB); +} + +/** Reset (empty) the table. */ +void KsTraceViewer::reset() +{ + this->setMinimumHeight(FONT_HEIGHT * 10); + _model.reset(); + _resizeToContents(); +} + +void KsTraceViewer::_searchReset() +{ + _searchProgBar.setValue(0); + _searchCountLabel.setText(""); + _proxyModel.searchProgress(); + _searchDone = false; +} + +/** Get the index of the first (top) visible row. */ +size_t KsTraceViewer::getTopRow() const +{ + return _view.indexAt(_view.rect().topLeft()).row(); +} + +/** Position given row at the top of the table. */ +void KsTraceViewer::setTopRow(size_t r) +{ + _view.scrollTo(_proxyModel.index(r, 0), + QAbstractItemView::PositionAtTop); +} + +/** Update the content of the table. */ +void KsTraceViewer::update(KsDataStore *data) +{ + /* The Proxy model has to be updated first! */ + _proxyModel.fill(data); + _model.update(data); + _data = data; + if (_mState->activeMarker()._isSet) + showRow(_mState->activeMarker()._pos, true); +} + +void KsTraceViewer::_onCustomContextMenu(const QPoint &point) +{ + QModelIndex i = _view.indexAt(point); + + if (i.isValid()) { + /* + * Use the index of the proxy model to retrieve the value + * of the row number in the source model. + */ + size_t row = _proxyModel.mapRowFromSource(i.row()); + KsQuickEntryMenu menu(_data, row, this); + + connect(&menu, &KsQuickEntryMenu::plotTask, + this, &KsTraceViewer::plotTask); + + menu.exec(mapToGlobal(point)); + } +} + +void KsTraceViewer::_searchEdit(int index) +{ + _searchReset(); // The search has been modified. +} + +void KsTraceViewer::_searchEditText(const QString &text) +{ + _searchReset(); // The search has been modified. +} + +void KsTraceViewer::_graphFollowsChanged(int state) +{ + _graphFollows = (bool) state; + + if (_graphFollows && _searchDone) + emit select(*_it); // Send a signal to the Graph widget. +} + +static bool notHaveCond(QString searchText, QString itemText) +{ + return !itemText.contains(searchText, Qt::CaseInsensitive); +} + +static bool containsCond(QString searchText, QString itemText) +{ + return itemText.contains(searchText, Qt::CaseInsensitive); +} + +static bool matchCond(QString searchText, QString itemText) +{ + return (itemText.compare(searchText, Qt::CaseInsensitive) == 0); +} + +void KsTraceViewer::_search() +{ + /* Disable the user input until the search is done. */ + _searchLineEdit.setReadOnly(true); + if (!_searchDone) { + int xColumn, xSelect; + QString xText; + + /* + * The search is not done. This means that the search settings + * have been modified since the last time we searched. + */ + _matchList.clear(); + xText = _searchLineEdit.text(); + xColumn = _columnComboBox.currentIndex(); + xSelect = _selectComboBox.currentIndex(); + + switch (xSelect) { + case Condition::Containes: + _searchItems(xColumn, xText, &containsCond); + break; + + case Condition::Match: + _searchItems(xColumn, xText, &matchCond); + break; + + case Condition::NotHave: + _searchItems(xColumn, xText, ¬HaveCond); + break; + + default: + break; + } + + if (!_matchList.empty()) { + this->showRow(*_it, true); + + if (_graphFollows) + emit select(*_it); // Send a signal to the Graph widget. + } + } else { + /* + * If the search is done, pressing "Enter" is equivalent + * to pressing "Next" button. + */ + this->_next(); + } + + /* Enable the user input. */ + _searchLineEdit.setReadOnly(false); +} + +void KsTraceViewer::_next() +{ + if (!_searchDone) { + _search(); + return; + } + + if (!_matchList.empty()) { // Items have been found. + ++_it; // Move the iterator. + if (_it == _matchList.end() ) { + // This is the last item of the list. Go back to the beginning. + _it = _matchList.begin(); + } + + // Select the row of the item. + showRow(*_it, true); + + if (_graphFollows) + emit select(*_it); // Send a signal to the Graph widget. + } +} + +void KsTraceViewer::_prev() +{ + if (!_searchDone) { + _search(); + return; + } + + if (!_matchList.empty()) { // Items have been found. + if (_it == _matchList.begin()) { + // This is the first item of the list. Go to the last item. + _it = _matchList.end() - 1; + } else { + --_it; // Move the iterator. + } + + // Select the row of the item. + showRow(*_it, true); + + if (_graphFollows) + emit select(*_it); // Send a signal to the Graph widget. + } +} + +void KsTraceViewer::_searchStop() +{ + _searchStopAction->setVisible(false); + _proxyModel.searchStop(); +} + +void KsTraceViewer::_clicked(const QModelIndex& i) +{ + if (_graphFollows) { + /* + * Use the index of the proxy model to retrieve the value + * of the row number in the base model. + */ + size_t row = _proxyModel.mapRowFromSource(i.row()); + emit select(row); // Send a signal to the Graph widget. + } +} + +/** Make a given row of the table visible. */ +void KsTraceViewer::showRow(size_t r, bool mark) +{ + /* + * Use the index in the source model to retrieve the value of the row number + * in the proxy model. + */ + QModelIndex index = _proxyModel.mapFromSource(_model.index(r, 0)); + + if (mark) { // The row will be selected (colored). + /* Get the first and the last visible rows of the table. */ + int visiTot = _view.indexAt(_view.rect().topLeft()).row(); + int visiBottom = _view.indexAt(_view.rect().bottomLeft()).row() - 2; + + /* Scroll only if the row to be shown in not vizible. */ + if (index.row() < visiTot || index.row() > visiBottom) + _view.scrollTo(index, QAbstractItemView::PositionAtCenter); + + _view.selectRow(index.row()); + } else { + /* + * Just make sure that the row is visible. It will show up at + * the top of the visible part of the table. + */ + _view.scrollTo(index, QAbstractItemView::PositionAtTop); + } +} + +/** Deselects the selected items (row) if any. */ +void KsTraceViewer::deselect() +{ + _view.clearSelection(); +} + +/** Switch the Dual marker. */ +void KsTraceViewer::markSwitch() +{ + /* The state of the Dual marker has changed. Get the new active marker. */ + DualMarkerState state = _mState->getState(); + + /* First deal with the passive marker. */ + if (_mState->getMarker(!state)._isSet) { + /* + * The passive marker is set. Use the model to color the row of + * the passive marker. + */ + _model.selectRow(!state, _mState->getMarker(!state)._pos); + } + else { + /* + * The passive marker is not set. + * Make sure that the model colors nothing. + */ + _model.selectRow(!state, -1); + } + + /* + * Now deal with the active marker. This has to be done after dealing + * with the model, because changing the model clears the selection. + */ + if (_mState->getMarker(state)._isSet) { + /* + * The active marker is set. Use QTableView to select its row. + * The index in the source model is used to retrieve the value + * of the row number in the proxy model. + */ + size_t row =_mState->getMarker(state)._pos; + + QModelIndex index = _proxyModel.mapFromSource(_model.index(row, 0)); + + /* + * The row of the active marker will be colored according to + * the assigned property of the current state of the Dual marker. + */ + _view.selectRow(index.row()); + } else { + _view.clearSelection(); + } +} + +/** + * Reimplemented event handler used to update the geometry of the widget on + * resize events. + */ +void KsTraceViewer::resizeEvent(QResizeEvent* event) +{ + int nColumns = _tableHeader.count(); + int tableSize(0), viewSize, freeSpace; + + for (int c = 0; c < nColumns; ++c) { + tableSize += _view.columnWidth(c); + } + + viewSize = _view.width() - + qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent); + + if ((freeSpace = viewSize - tableSize) > 0) { + _view.setColumnWidth(nColumns - 1, _view.columnWidth(nColumns - 1) + + freeSpace - + 2); /* Just a little bit less space. + * This will allow the scroll bar + * to disappear when the widget + * is extended to maximum. */ + } +} + +/** + * Reimplemented event handler used to move the active marker. + */ +void KsTraceViewer::keyReleaseEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Up || event->key() == Qt::Key_Down) { + QItemSelectionModel *sm = _view.selectionModel(); + if (sm->hasSelection()) { + /* Only one row at the time can be selected. */ + int row = sm->selectedRows()[0].row(); + emit select(row); // Send a signal to the Graph widget. + } + + return; + } + + QWidget::keyReleaseEvent(event); +} + +void KsTraceViewer::_resizeToContents() +{ + int rows, columnSize; + + _view.setVisible(false); + _view.resizeColumnsToContents(); + _view.setVisible(true); + + /* + * Because of some unknown reason the first column doesn't get + * resized properly by the code above. We will resize this + * column by hand. + */ + rows = _model.rowCount({}); + columnSize = STRING_WIDTH(QString("%1").arg(rows)) + FONT_WIDTH; + _view.setColumnWidth(0, columnSize); +} + +size_t KsTraceViewer::_searchItems(int column, + const QString &searchText, + condition_func cond) +{ + int count; + + _searchProgBar.show(); + _pbAction->setVisible(true); + + if (column == KsViewModel::TRACE_VIEW_COL_INFO || + column == KsViewModel::TRACE_VIEW_COL_LAT) { + _searchStopAction->setVisible(true); + _proxyModel.search(column, searchText, cond, &_matchList, + &_searchProgBar, &_searchCountLabel); + + _searchStopAction->setVisible(false); + } else { + _searchItemsMapReduce(column, searchText, cond); + } + + count = _matchList.count(); + + _pbAction->setVisible(false); + _searchCountLabel.setText(QString(" %1").arg(count)); + _searchDone = true; + + if (count == 0) // No items have been found. Do nothing. + return 0; + + QItemSelectionModel *sm = _view.selectionModel(); + if (sm->hasSelection()) { + /* Only one row at the time can be selected. */ + int row = sm->selectedRows()[0].row(); + + _view.clearSelection(); + _it = _matchList.begin(); + /* + * Move the iterator to the first element of the match list + * after the selected one. + */ + while (*_it <= row) { + ++_it; // Move the iterator. + if (_it == _matchList.end()) { + /* + * This is the last item of the list. Go back + * to the beginning. + */ + _it = _matchList.begin(); + break; + } + } + } else { + /* Move the iterator to the beginning of the match list. */ + _view.clearSelection(); + _it = _matchList.begin(); + } + + return count; +} + +void KsTraceViewer::_searchItemsMapReduce(int column, + const QString &searchText, + condition_func cond) +{ + int nThreads = std::thread::hardware_concurrency(); + std::vector> ranges(nThreads); + std::vector>> maps; + int i(0), nRows(_proxyModel.rowCount({})); + int delta(nRows / nThreads); + + auto lamSearchMap = [&] (const QPair &range, + bool notify) { + return _proxyModel.searchMap(column, searchText, cond, + range.first, range.second, + notify); + }; + + auto lamSearchReduce = [&] (QList &resultList, + const QList &mapList) { + resultList << mapList; + _searchProgBar.setValue(_searchProgBar.value() + 1); + }; + + for (auto &r: ranges) { + r.first = (i++) * delta; + r.second = r.first + delta - 1; + } + + /* + * If the range is not multiple of the number of threads, adjust + * the last range interval. + */ + ranges.back().second = nRows - 1; + maps.push_back(std::async(lamSearchMap, ranges[0], true)); + for (int r = 1; r < nThreads; ++r) + maps.push_back(std::async(lamSearchMap, ranges[r], false)); + + while (_proxyModel.searchProgress() < KS_PROGRESS_BAR_MAX- nThreads) { + std::unique_lock lk(_proxyModel._mutex); + _proxyModel._pbCond.wait(lk); + _searchProgBar.setValue(_proxyModel.searchProgress()); + QApplication::processEvents(); + } + + for (auto &m: maps) + lamSearchReduce(_matchList, m.get()); +} diff --git a/kernel-shark-qt/src/KsTraceViewer.hpp b/kernel-shark-qt/src/KsTraceViewer.hpp new file mode 100644 index 0000000..2a85e4f --- /dev/null +++ b/kernel-shark-qt/src/KsTraceViewer.hpp @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsTraceViewer.hpp + * @brief KernelShark Trace Viewer widget. + */ + +#ifndef _KS_TRACEVIEW_H +#define _KS_TRACEVIEW_H + +// Qt +#include + +// KernelShark +#include "KsUtils.hpp" +#include "KsModels.hpp" +#include "KsDualMarker.hpp" + +/** Matching condition function type. To be user for searchong. */ +typedef bool (*condition_func)(QString, QString); + +/** + * The KsTraceViewer class provides a widget for browsing in the trace data + * shown in a text form. + */ +class KsTraceViewer : public QWidget +{ + Q_OBJECT +public: + explicit KsTraceViewer(QWidget *parent = nullptr); + + void loadData(KsDataStore *data); + + void setMarkerSM(KsDualMarkerSM *m); + + void reset(); + + size_t getTopRow() const; + + void setTopRow(size_t r); + + void resizeEvent(QResizeEvent* event) override; + + void keyReleaseEvent(QKeyEvent *event); + + void markSwitch(); + + void showRow(size_t r, bool mark); + + void deselect(); + + void update(KsDataStore *data); + +signals: + /** Signal emitted when new row is selected. */ + void select(size_t); + + /** + * This signal is used to re-emitted the plotTask signal of the + * KsQuickEntryMenu. + */ + void plotTask(int pid); + +private: + QVBoxLayout _layout; + + QTableView _view; + + KsViewModel _model; + + KsFilterProxyModel _proxyModel; + + QStringList _tableHeader; + + QToolBar _toolbar; + + QLabel _labelSearch, _labelGrFollows; + + QComboBox _columnComboBox; + + QComboBox _selectComboBox; + + QLineEdit _searchLineEdit; + + QPushButton _prevButton, _nextButton, _searchStopButton; + + QAction *_pbAction, *_searchStopAction; + + QCheckBox _graphFollowsCheckBox; + + QProgressBar _searchProgBar; + + QLabel _searchCountLabel; + + bool _searchDone; + + bool _graphFollows; + + QList _matchList; + + QList::iterator _it; + + KsDualMarkerSM *_mState; + + KsDataStore *_data; + + enum Condition + { + Containes = 0, + Match = 1, + NotHave = 2 + }; + + void _searchReset(); + + void _resizeToContents(); + + size_t _searchItems(int column, const QString &searchText, + condition_func cond); + + void _searchItemsMapReduce(int column, const QString &searchText, + condition_func cond); + + void _searchEditText(const QString &); + + void _graphFollowsChanged(int); + + void _search(); + + void _next(); + + void _prev(); + + void _searchStop(); + + void _clicked(const QModelIndex& i); + + void _onCustomContextMenu(const QPoint &); + +private slots: + + void _searchEdit(int); +}; + +#endif // _KS_TRACEVIEW_H From patchwork Tue Oct 16 15:53:02 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759571 Return-Path: Received: from mail-sn1nam01on0073.outbound.protection.outlook.com ([104.47.32.73]:27872 "EHLO NAM01-SN1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726778AbeJPXoU (ORCPT ); Tue, 16 Oct 2018 19:44:20 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 05/23] kernel-shark-qt: Add visualization (graph) model Date: Tue, 16 Oct 2018 15:53:02 +0000 Message-ID: <20181016155232.5257-6-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 6244 From: Yordan Karadzhov (VMware) This patch defines the class KsGraphModel which provides a model for visualization of trace data. This class is a wrapper of kshark_trace_histo and is needed only because we want to use the signals defined in QAbstractTableModel (a class inherited by KsGraphModel). Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/KsModels.cpp | 153 +++++++++++++++++++++++++++++++ kernel-shark-qt/src/KsModels.hpp | 66 +++++++++++++ 2 files changed, 219 insertions(+) diff --git a/kernel-shark-qt/src/KsModels.cpp b/kernel-shark-qt/src/KsModels.cpp index 095e26b..5eb1646 100644 --- a/kernel-shark-qt/src/KsModels.cpp +++ b/kernel-shark-qt/src/KsModels.cpp @@ -330,3 +330,156 @@ size_t KsViewModel::search(int column, return matchList->count(); } + +/** Create a default (empty) KsFilterProxyModel object. */ +KsGraphModel::KsGraphModel(QObject *parent) +: QAbstractTableModel(parent) +{ + ksmodel_init(&_histo); +} + +/** Destroy KsFilterProxyModel object. */ +KsGraphModel::~KsGraphModel() +{ + ksmodel_clear(&_histo); +} + +/** + * @brief Provide the Visualization model with data. Calculate the current + * state of the model. + * + * @param entries: Input location for the trace data. + * @param n: Number of bins. + */ +void KsGraphModel::fill(kshark_entry **entries, size_t n) +{ + if (n == 0) + return; + + beginResetModel(); + + if (_histo.n_bins == 0) + ksmodel_set_bining(&_histo, + KS_DEFAULT_NBUNS, + entries[0]->ts, + entries[n-1]->ts); + + ksmodel_fill(&_histo, entries, n); + + endResetModel(); +} + +/** + * @brief Shift the time-window of the model forward. Recalculate the current + * state of the model. + * + * @param n: Number of bins to shift. + */ +void KsGraphModel::shiftForward(size_t n) +{ + beginResetModel(); + ksmodel_shift_forward(&_histo, n); + endResetModel(); +} + +/** + * @brief Shift the time-window of the model backward. Recalculate the current + * state of the model. + * + * @param n: Number of bins to shift. + */ +void KsGraphModel::shiftBackward(size_t n) +{ + beginResetModel(); + ksmodel_shift_backward(&_histo, n); + endResetModel(); +} + +/** + * @brief Move the time-window of the model to a given location. Recalculate + * the current state of the model. + * + * @param ts: position in time to be visualized. + */ +void KsGraphModel::jumpTo(size_t ts) +{ + beginResetModel(); + ksmodel_jump_to(&_histo, ts); + endResetModel(); +} + +/** + * @brief Extend the time-window of the model. Recalculate the current state + * of the model. + * + * @param r: Scale factor of the zoom-out. + * @param mark: Focus point of the zoom-out. + */ +void KsGraphModel::zoomOut(double r, int mark) +{ + beginResetModel(); + ksmodel_zoom_out(&_histo, r, mark); + endResetModel(); +} + +/** + * @brief Shrink the time-window of the model. Recalculate the current state + * of the model. + * + * @param r: Scale factor of the zoom-in. + * @param mark: Focus point of the zoom-in. + */ +void KsGraphModel::zoomIn(double r, int mark) +{ + beginResetModel(); + ksmodel_zoom_in(&_histo, r, mark); + endResetModel(); +} + +/** Quick zoom out. The entire data-set will be visualized. */ +void KsGraphModel::quickZoomOut() +{ + beginResetModel(); + + ksmodel_set_bining(&_histo, + _histo.n_bins, + _histo.data[0]->ts, + _histo.data[_histo.data_size - 1]->ts); + + ksmodel_fill(&_histo, _histo.data, _histo.data_size); + + endResetModel(); +} + +/** + * @brief Quick zoom in to a state of the Visualization model which has the + * given bin size. The actual value of the bin size may en up being slightly + * different because of the fine tuning performed by the model. + * + * @param binSize: an approximate value for the new size of the bins. + */ +void KsGraphModel::quickZoomIn(uint64_t binSize) +{ + double range, r; + + range = _histo.max - _histo.min; + r = 1 - (binSize * _histo.n_bins) / range; + zoomIn(r); +} + +/** Reset the model. */ +void KsGraphModel::reset() +{ + beginResetModel(); + ksmodel_clear(&_histo); + endResetModel(); +} + +/** Update the model. Use this function if the data has changed. */ +void KsGraphModel::update(KsDataStore *data) +{ + beginResetModel(); + if (data) + ksmodel_fill(&_histo, data->rows(), data->size()); + endResetModel(); +} diff --git a/kernel-shark-qt/src/KsModels.hpp b/kernel-shark-qt/src/KsModels.hpp index 8935fd4..00b20b2 100644 --- a/kernel-shark-qt/src/KsModels.hpp +++ b/kernel-shark-qt/src/KsModels.hpp @@ -220,4 +220,70 @@ private: bool notify); }; +/** + * Class KsGraphModel provides a model for visualization of trace data. This + * class is a wrapper of kshark_trace_histo and is needed only because we want + * to use the signals defined in QAbstractTableModel. + */ +class KsGraphModel : public QAbstractTableModel +{ +public: + explicit KsGraphModel(QObject *parent = nullptr); + + virtual ~KsGraphModel(); + + /** + * This dummy function is an implementation of the pure + * virtual method of the abstract model class. + */ + int rowCount(const QModelIndex &) const override + { + return _histo.n_bins; + } + + /** + * This dummy function is an implementation of the pure + * virtual method of the abstract model class. + */ + int columnCount(const QModelIndex &) const override {return 0;} + + /** + * This dummy function is an implementation of the pure + * virtual method of the abstract model class. + */ + QVariant data(const QModelIndex &index, int role) const override + { + return {}; + } + + /** Get the kshark_trace_histo object. */ + kshark_trace_histo *histo() {return &_histo;} + + void fill(kshark_entry **entries, size_t n); + + void shiftForward(size_t n); + + void shiftBackward(size_t n); + + void jumpTo(size_t ts); + + void zoomOut(double r, int mark = -1); + + void zoomIn(double r, int mark = -1); + + void quickZoomOut(); + + void quickZoomIn(uint64_t binSize); + + void reset(); + + void update(KsDataStore *data = nullptr); + +private: + kshark_trace_histo _histo; +}; + +/** Defines a default number of bins to be used by the visualization model. */ +#define KS_DEFAULT_NBUNS 1024 + #endif // _KS_MODELS_H From patchwork Tue Oct 16 15:53:03 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759589 Return-Path: Received: from mail-sn1nam01on0073.outbound.protection.outlook.com ([104.47.32.73]:27872 "EHLO NAM01-SN1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727242AbeJPXoW (ORCPT ); Tue, 16 Oct 2018 19:44:22 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 06/23] kernel-shark-qt: Add widget for OpenGL rendering Date: Tue, 16 Oct 2018 15:53:03 +0000 Message-ID: <20181016155232.5257-7-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 30604 From: Yordan Karadzhov (VMware) This patch defines the widget for rendering OpenGL graphics used to plot trace graphs. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 2 + kernel-shark-qt/src/KsDualMarker.cpp | 12 +- kernel-shark-qt/src/KsDualMarker.hpp | 4 +- kernel-shark-qt/src/KsGLWidget.cpp | 913 +++++++++++++++++++++++++++ kernel-shark-qt/src/KsGLWidget.hpp | 220 +++++++ 5 files changed, 1146 insertions(+), 5 deletions(-) create mode 100644 kernel-shark-qt/src/KsGLWidget.cpp create mode 100644 kernel-shark-qt/src/KsGLWidget.hpp diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 3f40930..2ca5187 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -33,6 +33,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) message(STATUS "libkshark-gui") set (ks-guiLib_hdr KsUtils.hpp KsModels.hpp + KsGLWidget.hpp KsDualMarker.hpp KsWidgetsLib.hpp KsTraceViewer.hpp) @@ -41,6 +42,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) add_library(kshark-gui SHARED ${ks-guiLib_hdr_moc} KsUtils.cpp KsModels.cpp + KsGLWidget.cpp KsDualMarker.cpp KsWidgetsLib.cpp KsTraceViewer.cpp) diff --git a/kernel-shark-qt/src/KsDualMarker.cpp b/kernel-shark-qt/src/KsDualMarker.cpp index ae637aa..ef126f7 100644 --- a/kernel-shark-qt/src/KsDualMarker.cpp +++ b/kernel-shark-qt/src/KsDualMarker.cpp @@ -10,6 +10,7 @@ */ #include "KsDualMarker.hpp" +#include "KsGLWidget.hpp" /** * @brief Create KsGraphMark object. @@ -287,13 +288,16 @@ void KsDualMarkerSM::setState(DualMarkerState st) { * model has changed. * * @param data: Input location for the Data Store object. - * @param histo: Input location for the model descriptor. + * @param glw: Input location for the OpenGL widget object. */ void KsDualMarkerSM::updateMarkers(const KsDataStore &data, - kshark_trace_histo *histo) + KsGLWidget *glw) { - _markA.update(data, histo); - _markB.update(data, histo); + if(_markA.update(data, glw->model()->histo())) + glw->setMark(&_markA); + + if(_markB.update(data, glw->model()->histo())) + glw->setMark(&_markB); updateLabels(); } diff --git a/kernel-shark-qt/src/KsDualMarker.hpp b/kernel-shark-qt/src/KsDualMarker.hpp index 401b41c..73d4f8a 100644 --- a/kernel-shark-qt/src/KsDualMarker.hpp +++ b/kernel-shark-qt/src/KsDualMarker.hpp @@ -19,6 +19,8 @@ #include "KsUtils.hpp" #include "KsPlotTools.hpp" +class KsGLWidget; + /** The KsGraphMark represents a marker for KernelShark GUI. */ class KsGraphMark : public QObject { @@ -124,7 +126,7 @@ public: QState *stateBPtr() {return _stateB;} void updateMarkers(const KsDataStore &data, - kshark_trace_histo *histo); + KsGLWidget *glw); void updateLabels(); diff --git a/kernel-shark-qt/src/KsGLWidget.cpp b/kernel-shark-qt/src/KsGLWidget.cpp new file mode 100644 index 0000000..22cbd96 --- /dev/null +++ b/kernel-shark-qt/src/KsGLWidget.cpp @@ -0,0 +1,913 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + + /** + * @file KsGLWidget.cpp + * @brief OpenGL widget for plotting trace graphs. + */ + +// OpenGL +#include +#include + +// KernelShark +#include "KsGLWidget.hpp" +#include "KsUtils.hpp" +#include "KsPlugins.hpp" +#include "KsDualMarker.hpp" + +/** Create a default (empty) OpenGL widget. */ +KsGLWidget::KsGLWidget(QWidget *parent) +: QOpenGLWidget(parent), + _hMargin(20), + _vMargin(30), + _vSpacing(20), + _mState(nullptr), + _data(nullptr), + _rubberBand(QRubberBand::Rectangle, this), + _rubberBandOrigin(0, 0), + _dpr(1) +{ + setMouseTracking(true); + + /* + * Using the old Signal-Slot syntax because QWidget::update has + * overloads. + */ + connect(&_model, SIGNAL(modelReset()), this, SLOT(update())); +} + +/** Reimplemented function used to set up all required OpenGL resources. */ +void KsGLWidget::initializeGL() +{ + _dpr = QApplication::desktop()->devicePixelRatio(); + ksplot_init_opengl(_dpr); +} + +/** + * Reimplemented function used to reprocess all graphs whene the widget has + * been resized. + */ +void KsGLWidget::resizeGL(int w, int h) +{ + ksplot_resize_opengl(w, h); + if(!_data) + return; + + /* + * From the size of the widget, calculate the number of bins. + * One bin will correspond to one pixel. + */ + int nBins = width() - _hMargin * 2; + + /* + * Reload the data. The range of the histogram is the same + * but the number of bins changes. + */ + ksmodel_set_bining(_model.histo(), + nBins, + _model.histo()->min, + _model.histo()->max); + + _model.fill(_data->rows(), _data->size()); +} + +/** Reimplemented function used to plot trace graphs. */ +void KsGLWidget::paintGL() +{ + glClear(GL_COLOR_BUFFER_BIT); + + loadColors(); + + /* Draw the time axis. */ + if(_data) + _drawAxisX(); + + /* Process and draw all graphs by using the built-in logic. */ + _makeGraphs(_cpuList, _taskList); + for (auto const &g: _graphs) + g->draw(1.5 * _dpr); + + /* Process and draw all plugin-specific shapes. */ + _makePluginShapes(_cpuList, _taskList); + while (!_shapes.empty()) { + auto s = _shapes.front(); + s->draw(); + delete s; + _shapes.pop_front(); + } + + /* + * Update and draw the markers. Make sure that the active marker + * is drawn on top. + */ + _mState->updateMarkers(*_data, this); + _mState->passiveMarker().draw(); + _mState->activeMarker().draw(); +} + +/** Reimplemented event handler used to receive mouse press events. */ +void KsGLWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + _posMousePress = _posInRange(event->pos().x()); + _rangeBoundInit(_posMousePress); + } else if (event->button() == Qt::RightButton) { + emit deselect(); + _mState->activeMarker().remove(); + _mState->updateLabels(); + _model.update(); + } +} + +int KsGLWidget::_getLastTask(struct kshark_trace_histo *histo, + int bin, int cpu) +{ + kshark_context *kshark_ctx(nullptr); + kshark_entry_collection *col; + int pid; + + if (!kshark_instance(&kshark_ctx)) + return KS_EMPTY_BIN; + + col = kshark_find_data_collection(kshark_ctx->collections, + KsUtils::matchCPUVisible, + cpu); + + for (int b = bin; b >= 0; --b) { + pid = ksmodel_get_pid_back(histo, b, cpu, false, col, nullptr); + if (pid >= 0) + return pid; + } + + return ksmodel_get_pid_back(histo, LOWER_OVERFLOW_BIN, + cpu, + false, + col, + nullptr); +} + +int KsGLWidget::_getLastCPU(struct kshark_trace_histo *histo, + int bin, int pid) +{ + kshark_context *kshark_ctx(nullptr); + kshark_entry_collection *col; + int cpu; + + if (!kshark_instance(&kshark_ctx)) + return KS_EMPTY_BIN; + + col = kshark_find_data_collection(kshark_ctx->collections, + kshark_match_pid, + pid); + + for (int b = bin; b >= 0; --b) { + cpu = ksmodel_get_cpu_back(histo, b, pid, false, col, nullptr); + if (cpu >= 0) + return cpu; + } + + return ksmodel_get_cpu_back(histo, LOWER_OVERFLOW_BIN, + pid, + false, + col, + nullptr); + +} + +/** Reimplemented event handler used to receive mouse move events. */ +void KsGLWidget::mouseMoveEvent(QMouseEvent *event) +{ + int bin, cpu, pid; + size_t row; + bool ret; + + if (_rubberBand.isVisible()) + _rangeBoundStretched(_posInRange(event->pos().x())); + + bin = event->pos().x() - _hMargin; + cpu = _getCPU(event->pos().y()); + pid = _getPid(event->pos().y()); + + ret = _find(bin, cpu, pid, 5, false, &row); + if (ret) { + emit found(row); + } else { + if (cpu >= 0) { + pid = _getLastTask(_model.histo(), bin, cpu); + } + + if (pid > 0) { + cpu = _getLastCPU(_model.histo(), bin, pid); + } + + emit notFound(ksmodel_bin_ts(_model.histo(), bin), cpu, pid); + } +} + +/** Reimplemented event handler used to receive mouse release events. */ +void KsGLWidget::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + size_t posMouseRel = _posInRange(event->pos().x()); + int min, max; + if (_posMousePress < posMouseRel) { + min = _posMousePress - _hMargin; + max = posMouseRel - _hMargin; + } else { + max = _posMousePress - _hMargin; + min = posMouseRel - _hMargin; + } + + _rangeChanged(min, max); + } +} + +/** Reimplemented event handler used to receive mouse double click events. */ +void KsGLWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + _findAndSelect(event); +} + +/** Reimplemented event handler used to receive mouse wheel events. */ +void KsGLWidget::wheelEvent(QWheelEvent * event) +{ + int zoomFocus; + + if (_mState->activeMarker()._isSet && + _mState->activeMarker().isVisible()) { + /* + * Use the position of the marker as a focus point for the + * zoom. + */ + zoomFocus = _mState->activeMarker()._bin; + } else { + /* + * Use the position of the mouse as a focus point for the + * zoom. + */ + zoomFocus = event->pos().x() - _hMargin; + } + + if (event->delta() > 0) { + _model.zoomIn(.05, zoomFocus); + } else { + _model.zoomOut(.05, zoomFocus); + } + + _mState->updateMarkers(*_data, this); +} + +/** Reimplemented event handler used to receive key press events. */ +void KsGLWidget::keyPressEvent(QKeyEvent *event) +{ + if (event->isAutoRepeat()) + return; + + switch (event->key()) { + case Qt::Key_Plus: + emit zoomIn(); + return; + + case Qt::Key_Minus: + emit zoomOut(); + return; + + case Qt::Key_Left: + emit scrollLeft(); + return; + + case Qt::Key_Right: + emit scrollRight(); + return; + + default: + QOpenGLWidget::keyPressEvent(event); + return; + } +} + +/** Reimplemented event handler used to receive key release events. */ +void KsGLWidget::keyReleaseEvent(QKeyEvent *event) +{ + if (event->isAutoRepeat()) + return; + + if(event->key() == Qt::Key_Plus || + event->key() == Qt::Key_Minus || + event->key() == Qt::Key_Left || + event->key() == Qt::Key_Right) { + emit stopUpdating(); + return; + } + + QOpenGLWidget::keyPressEvent(event); + return; +} + +/** + * @brief Load and show trace data. + * + * @param data: Input location for the KsDataStore object. + * KsDataStore::loadDataFile() must be called first. + */ +void KsGLWidget::loadData(KsDataStore *data) +{ + uint64_t tMin, tMax; + int nCPUs, nBins; + + _data = data; + + /* + * From the size of the widget, calculate the number of bins. + * One bin will correspond to one pixel. + */ + nBins = width() - _hMargin * 2; + nCPUs = tep_get_cpus(_data->tep()); + + _model.reset(); + + /* Now load the entire set of trace data. */ + tMin = _data->rows()[0]->ts; + tMax = _data->rows()[_data->size() - 1]->ts; + ksmodel_set_bining(_model.histo(), nBins, tMin, tMax); + _model.fill(_data->rows(), _data->size()); + + /* Make a default CPU list. All CPUs will be plotted. */ + _cpuList = {}; + for (int i = 0; i < nCPUs; ++i) + _cpuList.append(i); + + /* Make a default task list. No tasks will be plotted. */ + _taskList = {}; + + loadColors(); + _makeGraphs(_cpuList, _taskList); +} + +/** + * Create a Hash table of Rainbow colors. The sorted Pid values are mapped to + * the palette of Rainbow colors. + */ +void KsGLWidget::loadColors() +{ + _pidColors.clear(); + _pidColors = KsPlot::getColorTable(); +} + +/** + * Position the graphical elements of the marker according to the current + * position of the graphs inside the GL widget. + */ +void KsGLWidget::setMark(KsGraphMark *mark) +{ + mark->_mark.setDPR(_dpr); + mark->_mark.setX(mark->_bin + _hMargin); + mark->_mark.setY(_vMargin / 2 + 2, height() - _vMargin); + + if (mark->_cpu >= 0) { + mark->_mark.setCPUY(_graphs[mark->_cpu]->getBase()); + mark->_mark.setCPUVisible(true); + } else { + mark->_mark.setCPUVisible(false); + } + + if (mark->_task >= 0) { + mark->_mark.setTaskY(_graphs[mark->_task]->getBase()); + mark->_mark.setTaskVisible(true); + } else { + mark->_mark.setTaskVisible(false); + } +} + +/** + * @brief Check if a given KernelShark entry is ploted. + * + * @param e: Input location for the KernelShark entry. + * @param graphCPU: Output location for index of the CPU graph to which this + * entry belongs. If such a graph does not exist the outputted + * value is "-1". + * @param graphTask: Output location for index of the Task graph to which this + * entry belongs. If such a graph does not exist the + * outputted value is "-1". + */ +void KsGLWidget::findGraphIds(const kshark_entry &e, + int *graphCPU, + int *graphTask) +{ + int graph(0); + bool cpuFound(false), taskFound(false); + + /* + * Loop over all CPU graphs and try to find the one that + * contains the entry. + */ + for (auto const &c: _cpuList) { + if (c == e.cpu) { + cpuFound = true; + break; + } + ++graph; + } + + if (cpuFound) + *graphCPU = graph; + else + *graphCPU = -1; + + /* + * Loop over all Task graphs and try to find the one that + * contains the entry. + */ + graph = _cpuList.count(); + for (auto const &p: _taskList) { + if (p == e.pid) { + taskFound = true; + break; + } + ++graph; + } + + if (taskFound) + *graphTask = graph; + else + *graphTask = -1; +} + +void KsGLWidget::_drawAxisX() +{ + KsPlot::Point a0(_hMargin, _vMargin / 4), a1(_hMargin, _vMargin / 2); + KsPlot::Point b0(width()/2, _vMargin / 4), b1(width() / 2, _vMargin / 2); + KsPlot::Point c0(width() - _hMargin, _vMargin / 4), + c1(width() - _hMargin, _vMargin / 2); + int lineSize = 2 * _dpr; + + a0._size = c0._size = _dpr; + + a0.draw(); + c0.draw(); + KsPlot::drawLine(a0, a1, {}, lineSize); + KsPlot::drawLine(b0, b1, {}, lineSize); + KsPlot::drawLine(c0, c1, {}, lineSize); + KsPlot::drawLine(a0, c0, {}, lineSize); +} + +void KsGLWidget::_makeGraphs(QVector cpuList, QVector taskList) +{ + /* The very first thing to do is to clean up. */ + for (auto &g: _graphs) + delete g; + _graphs.resize(0); + + if (!_data || !_data->size()) + return; + + auto lamAddGraph = [&](KsPlot::Graph *graph) { + /* + * Calculate the base level of the CPU graph inside the widget. + * Remember that the "Y" coordinate is inverted. + */ + if (!graph) + return; + + int base = _vMargin + + _vSpacing * _graphs.count() + + KS_GRAPH_HEIGHT * (_graphs.count() + 1); + + graph->setBase(base); + _graphs.append(graph); + }; + + /* Create CPU graphs according to the cpuList. */ + for (auto const &cpu: cpuList) + lamAddGraph(_newCPUGraph(cpu)); + + /* Create Task graphs taskList to the taskList. */ + for (auto const &pid: taskList) + lamAddGraph(_newTaskGraph(pid)); +} + +void KsGLWidget::_makePluginShapes(QVector cpuList, QVector taskList) +{ + kshark_context *kshark_ctx(nullptr); + kshark_event_handler *evt_handlers; + KsCppArgV cppArgv; + + if (!kshark_instance(&kshark_ctx)) + return; + + cppArgv._histo = _model.histo(); + cppArgv._shapes = &_shapes; + + for (int g = 0; g < cpuList.count(); ++g) { + cppArgv._graph = _graphs[g]; + evt_handlers = kshark_ctx->event_handlers; + while (evt_handlers) { + evt_handlers->draw_func(cppArgv.toC(), + cpuList[g], + KSHARK_PLUGIN_CPU_DRAW); + + evt_handlers = evt_handlers->next; + } + } + + for (int g = 0; g < taskList.count(); ++g) { + cppArgv._graph = _graphs[cpuList.count() + g]; + evt_handlers = kshark_ctx->event_handlers; + while (evt_handlers) { + evt_handlers->draw_func(cppArgv.toC(), + taskList[g], + KSHARK_PLUGIN_TASK_DRAW); + + evt_handlers = evt_handlers->next; + } + } +} + +KsPlot::Graph *KsGLWidget::_newCPUGraph(int cpu) +{ + KsPlot::Graph *graph = new KsPlot::Graph(_model.histo(), + &_pidColors); + kshark_context *kshark_ctx(nullptr); + kshark_entry_collection *col; + + if (!kshark_instance(&kshark_ctx)) + return nullptr; + + graph->setHMargin(_hMargin); + graph->setHeight(KS_GRAPH_HEIGHT); + + col = kshark_find_data_collection(kshark_ctx->collections, + KsUtils::matchCPUVisible, + cpu); + + graph->setDataCollectionPtr(col); + graph->fillCPUGraph(cpu); + + return graph; +} + +KsPlot::Graph *KsGLWidget::_newTaskGraph(int pid) +{ + KsPlot::Graph *graph = new KsPlot::Graph(_model.histo(), + &_pidColors); + kshark_context *kshark_ctx(nullptr); + kshark_entry_collection *col; + + if (!kshark_instance(&kshark_ctx)) + return nullptr; + + graph->setHMargin(_hMargin); + graph->setHeight(KS_GRAPH_HEIGHT); + + col = kshark_find_data_collection(kshark_ctx->collections, + kshark_match_pid, pid); + if (!col) { + /* + * If a data collection for this task does not exist, + * register a new one. + */ + col = kshark_register_data_collection(kshark_ctx, + _data->rows(), + _data->size(), + kshark_match_pid, pid, + 25); + } + + /* + * Data collections are efficient only when used on graphs, having a + * lot of empty bins. + * TODO: Determine the optimal criteria to decide whether to use or + * not use data collection for this graph. + */ + if (_data->size() < 1e6 && + col && col->size && + _data->size() / col->size < 100) { + /* + * No need to use collection in this case. Free the collection + * data, but keep the collection registered. This will prevent + * from recalculating the same collection next time when this + * task is ploted. + */ + kshark_reset_data_collection(col); + } + + graph->setDataCollectionPtr(col); + graph->fillTaskGraph(pid); + + return graph; +} + +bool KsGLWidget::_find(QMouseEvent *event, int variance, bool joined, + size_t *row) +{ + /* + * Get the bin, pid and cpu numbers. + * Remember that one bin corresponds to one pixel. + */ + int bin = event->pos().x() - _hMargin; + int cpu = _getCPU(event->pos().y()); + int pid = _getPid(event->pos().y()); + + return _find(bin, cpu, pid, variance, joined, row); +} + +int KsGLWidget::_getNextCPU(int pid, int bin) +{ + kshark_context *kshark_ctx(nullptr); + kshark_entry_collection *col; + int cpu; + + if (!kshark_instance(&kshark_ctx)) + return KS_EMPTY_BIN; + + col = kshark_find_data_collection(kshark_ctx->collections, + kshark_match_pid, + pid); + if (!col) + return KS_EMPTY_BIN; + + for (int i = bin; i < _model.histo()->n_bins; ++i) { + cpu = ksmodel_get_cpu_front(_model.histo(), i, pid, + false, col, nullptr); + if (cpu >= 0) + return cpu; + } + + return KS_EMPTY_BIN; +} + +bool KsGLWidget::_find(int bin, int cpu, int pid, + int variance, bool joined, size_t *row) +{ + int hSize = _model.histo()->n_bins; + ssize_t found; + + if (bin < 0 || bin > hSize || (cpu < 0 && pid < 0)) { + /* + * The click is outside of the range of the histogram. + * Do nothing. + */ + *row = 0; + return false; + } + + auto lamGetEntryByCPU = [&](int b) { + /* Get the first data entry in this bin. */ + found = ksmodel_first_index_at_cpu(_model.histo(), + b, cpu); + if (found < 0) { + /* + * The bin is empty or the entire connect of the bin + * has been filtered. + */ + return false; + } + + *row = found; + return true; + }; + + auto lamGetEntryByPid = [&](int b) { + /* Get the first data entry in this bin. */ + found = ksmodel_first_index_at_pid(_model.histo(), + b, pid); + if (found < 0) { + /* + * The bin is empty or the entire connect of the bin + * has been filtered. + */ + return false; + } + + *row = found; + return true; + }; + + auto lamFindEntryByCPU = [&](int b) { + /* + * The click is over the CPU graphs. First try the exact + * match. + */ + if (lamGetEntryByCPU(bin)) + return true; + + /* Now look for a match, nearby the position of the click. */ + for (int i = 1; i < variance; ++i) { + if (bin + i <= hSize && lamGetEntryByCPU(bin + i)) + return true; + + if (bin - i >= 0 && lamGetEntryByCPU(bin - i)) + return true; + } + + *row = 0; + return false; + }; + + auto lamFindEntryByPid = [&](int b) { + /* + * The click is over the Task graphs. First try the exact + * match. + */ + if (lamGetEntryByPid(bin)) + return true; + + /* Now look for a match, nearby the position of the click. */ + for (int i = 1; i < variance; ++i) { + if ((bin + i <= hSize) && lamGetEntryByPid(bin + i)) + return true; + + if ((bin - i >= 0) && lamGetEntryByPid(bin - i)) + return true; + } + + *row = 0; + return false; + }; + + if (cpu >= 0) + return lamFindEntryByCPU(bin); + + if (pid >= 0) { + bool ret = lamFindEntryByPid(bin); + + /* + * If no entry has been found and we have a joined search, look + * for an entry on the next CPU used by this task. + */ + if (!ret && joined) { + cpu = _getNextCPU(pid, bin); + ret = lamFindEntryByCPU(bin); + } + + return ret; + } + + *row = 0; + return false; +} + +bool KsGLWidget::_findAndSelect(QMouseEvent *event) +{ + size_t row; + bool found = _find(event, 10, true, &row); + + emit deselect(); + if (found) { + emit select(row); + emit updateView(row, true); + } + + return found; +} + +void KsGLWidget::_rangeBoundInit(int x) +{ + /* + * Set the origin of the rubber band that shows the new range. Only + * the X coordinate of the origin matters. The Y coordinate will be + * set to zero. + */ + _rubberBandOrigin.rx() = x; + _rubberBandOrigin.ry() = 0; + + _rubberBand.setGeometry(_rubberBandOrigin.x(), + _rubberBandOrigin.y(), + 0, 0); + + /* Make the rubber band visible, although its size is zero. */ + _rubberBand.show(); +} + +void KsGLWidget::_rangeBoundStretched(int x) +{ + QPoint pos; + + pos.rx() = x; + pos.ry() = this->height(); + + /* + * Stretch the rubber band between the origin position and the current + * position of the mouse. Only the X coordinate matters. The Y + * coordinate will be the height of the widget. + */ + if (_rubberBandOrigin.x() < pos.x()) { + _rubberBand.setGeometry(QRect(_rubberBandOrigin.x(), + _rubberBandOrigin.y(), + pos.x() - _rubberBandOrigin.x(), + pos.y() - _rubberBandOrigin.y())); + } else { + _rubberBand.setGeometry(QRect(pos.x(), + _rubberBandOrigin.y(), + _rubberBandOrigin.x() - pos.x(), + pos.y() - _rubberBandOrigin.y())); + } +} + +void KsGLWidget::_rangeChanged(int binMin, int binMax) +{ + size_t nBins = _model.histo()->n_bins; + int binMark = _mState->activeMarker()._bin; + uint64_t min, max; + + /* The rubber band is no longer needed. Make it invisible. */ + _rubberBand.hide(); + + if ( (binMax - binMin) < 4) { + /* Most likely this is an accidental click. Do nothing. */ + return; + } + + /* + * Calculate the new range of the histogram. The number of bins will + * stay the same. + */ + + min = ksmodel_bin_ts(_model.histo(), binMin); + max = ksmodel_bin_ts(_model.histo(), binMax); + if (max - min < nBins) { + /* + * The range cannot be smaller than the number of bins. + * Do nothing. + */ + return; + } + + /* Recalculate the model and update the markers. */ + ksmodel_set_bining(_model.histo(), nBins, min, max); + _model.fill(_data->rows(), _data->size()); + _mState->updateMarkers(*_data, this); + + /* + * If the Marker is inside the new range, make sure that it will + * be visible in the table. Note that for this check we use the + * bin number of the marker, retrieved before its update. + */ + if (_mState->activeMarker()._isSet && + binMark < binMax && binMark > binMin) { + emit updateView(_mState->activeMarker()._pos, true); + return; + } + + /* + * Find the first bin which contains unfiltered data and send a signal + * to the View widget to make this data visible. + */ + for (int bin = 0; bin < _model.histo()->n_bins; ++bin) { + int64_t row = ksmodel_first_index_at_bin(_model.histo(), bin); + if (row != KS_EMPTY_BIN && + (_data->rows()[row]->visible & KS_TEXT_VIEW_FILTER_MASK)) { + emit updateView(row, false); + return; + } + } +} + +int KsGLWidget::_posInRange(int x) +{ + int posX; + if (x < _hMargin) + posX = _hMargin; + else if (x > (width() - _hMargin)) + posX = width() - _hMargin; + else + posX = x; + + return posX; +} + +int KsGLWidget::_getCPU(int y) +{ + int cpuId; + + if (_cpuList.count() == 0) + return -1; + + cpuId = (y - _vMargin + _vSpacing / 2) / (_vSpacing + KS_GRAPH_HEIGHT); + if (cpuId < 0 || cpuId >= _cpuList.count()) + return -1; + + return _cpuList[cpuId]; +} + +int KsGLWidget::_getPid(int y) +{ + int pidId; + + if (_taskList.count() == 0) + return -1; + + pidId = (y - _vMargin - + _cpuList.count()*(KS_GRAPH_HEIGHT + _vSpacing) + + _vSpacing / 2) / (_vSpacing + KS_GRAPH_HEIGHT); + + if (pidId < 0 || pidId >= _taskList.count()) + return -1; + + return _taskList[pidId]; +} diff --git a/kernel-shark-qt/src/KsGLWidget.hpp b/kernel-shark-qt/src/KsGLWidget.hpp new file mode 100644 index 0000000..5b8ff8c --- /dev/null +++ b/kernel-shark-qt/src/KsGLWidget.hpp @@ -0,0 +1,220 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + + /** + * @file KsGLWidget.hpp + * @brief OpenGL widget for plotting trace graphs. + */ + +#ifndef _KS_GLWIDGET_H +#define _KS_GLWIDGET_H + +// Qt +#include + +// KernelShark +#include "KsUtils.hpp" +#include "KsPlotTools.hpp" +#include "KsModels.hpp" +#include "KsDualMarker.hpp" + +/** + * The KsGLWidget class provides a widget for rendering OpenGL graphics used + * to plot trace graphs. + */ +class KsGLWidget : public QOpenGLWidget +{ + Q_OBJECT +public: + explicit KsGLWidget(QWidget *parent = NULL); + + void initializeGL() override; + + void resizeGL(int w, int h) override; + + void paintGL() override; + + void mousePressEvent(QMouseEvent *event); + + void mouseMoveEvent(QMouseEvent *event); + + void mouseReleaseEvent(QMouseEvent *event); + + void mouseDoubleClickEvent(QMouseEvent *event); + + void wheelEvent(QWheelEvent * event); + + void keyPressEvent(QKeyEvent *event); + + void keyReleaseEvent(QKeyEvent *event); + + void loadData(KsDataStore *data); + + void loadColors(); + + /** + * Provide the widget with a pointer to the Dual Marker state machine + * object. + */ + void setMarkerSM(KsDualMarkerSM *m) {_mState = m;} + + /** Get the KsGraphModel object. */ + KsGraphModel *model() {return &_model;} + + /** Get the number of CPU graphs. */ + int cpuGraphCount() const {return _cpuList.count();} + + /** Get the number of Task graphs. */ + int taskGraphCount() const {return _taskList.count();} + + /** Get the total number of graphs. */ + int graphCount() const {return _cpuList.count() + _taskList.count();} + + /** Get the height of the widget. */ + int height() const + { + return graphCount() * (KS_GRAPH_HEIGHT + _vSpacing) + + _vMargin * 2; + } + + /** Get the device pixel ratio. */ + int dpr() const {return _dpr;} + + /** Get the size of the horizontal margin space. */ + int hMargin() const {return _hMargin;} + + /** Get the size of the vertical margin space. */ + int vMargin() const {return _vMargin;} + + /** Get the size of the vertical spaceing between the graphs. */ + int vSpacing() const {return _vSpacing;} + + void setMark(KsGraphMark *mark); + + void findGraphIds(const kshark_entry &e, + int *graphCPU, + int *graphTask); + + /** CPUs to be plotted. */ + QVector _cpuList; + + /** Tasks to be plotted. */ + QVector _taskList; + +signals: + /** + * This signal is emitted when the mouse moves over a visible + * KernelShark entry. + */ + void found(size_t pos); + + /** + * This signal is emitted when the mouse moves but there is no visible + * KernelShark entry under the cursor. + */ + void notFound(uint64_t ts, int cpu, int pid); + + /** This signal is emitted when the Plus key is pressed. */ + void zoomIn(); + + /** This signal is emitted when the Minus key is pressed. */ + void zoomOut(); + + /** This signal is emitted when the Left Arrow key is pressed. */ + void scrollLeft(); + + /** This signal is emitted when the Right Arrow key is pressed. */ + void scrollRight(); + + /** + * This signal is emitted when one of the 4 Action keys is release + * (after being pressed). + */ + void stopUpdating(); + + /** + * This signal is emitted in the case of a double click over a visible + * KernelShark entry. + */ + void select(size_t pos); + + /** + * This signal is emitted in the case of a right mouse button click or + * in the case of a double click over an empty area (no visible + * KernelShark entries). + */ + void deselect(); + + /** + * This signal is emitted when the KsTraceViewer widget needs to be + * updated. + */ + void updateView(size_t pos, bool mark); + +private: + QVector _graphs; + + KsPlot::PlotObjList _shapes; + + KsPlot::ColorTable _pidColors; + + int _hMargin, _vMargin; + + unsigned int _vSpacing; + + KsGraphModel _model; + + KsDualMarkerSM *_mState; + + KsDataStore *_data; + + QRubberBand _rubberBand; + + QPoint _rubberBandOrigin; + + size_t _posMousePress; + + bool _keyPressed; + + int _dpr; + + void _drawAxisX(); + + void _makeGraphs(QVector cpuMask, QVector taskMask); + + KsPlot::Graph *_newCPUGraph(int cpu); + + KsPlot::Graph *_newTaskGraph(int pid); + + void _makePluginShapes(QVector cpuMask, QVector taskMask); + + int _posInRange(int x); + + int _getCPU(int y); + + int _getPid(int y); + + void _rangeBoundInit(int x); + + void _rangeBoundStretched(int x); + + void _rangeChanged(int binMin, int binMax); + + bool _findAndSelect(QMouseEvent *event); + + bool _find(QMouseEvent *event, int variance, bool joined, size_t *row); + + bool _find(int bin, int cpu, int pid, + int variance, bool joined, size_t *row); + + int _getNextCPU(int pid, int bin); + + int _getLastTask(struct kshark_trace_histo *histo, int bin, int cpu); + + int _getLastCPU(struct kshark_trace_histo *histo, int bin, int pid); +}; + +#endif From patchwork Tue Oct 16 15:53:05 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759569 Return-Path: Received: from mail-eopbgr710064.outbound.protection.outlook.com ([40.107.71.64]:54000 "EHLO NAM05-BY2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727223AbeJPXoT (ORCPT ); Tue, 16 Oct 2018 19:44:19 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 07/23] kernel-shark-qt: Add Trace Graph widget. Date: Tue, 16 Oct 2018 15:53:05 +0000 Message-ID: <20181016155232.5257-8-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 23405 From: Yordan Karadzhov (VMware) This patch defines widget for interactive visualization of trace data shown as time-series. The provides an info panel and a graph plotting area (KsGLWidget). The panel and the plotting area are integrated with the Dual Marker. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 2 + kernel-shark-qt/src/KsTraceGraph.cpp | 690 +++++++++++++++++++++++++++ kernel-shark-qt/src/KsTraceGraph.hpp | 137 ++++++ 3 files changed, 829 insertions(+) create mode 100644 kernel-shark-qt/src/KsTraceGraph.cpp create mode 100644 kernel-shark-qt/src/KsTraceGraph.hpp diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 2ca5187..d406866 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -36,6 +36,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) KsGLWidget.hpp KsDualMarker.hpp KsWidgetsLib.hpp + KsTraceGraph.hpp KsTraceViewer.hpp) QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr}) @@ -45,6 +46,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) KsGLWidget.cpp KsDualMarker.cpp KsWidgetsLib.cpp + KsTraceGraph.cpp KsTraceViewer.cpp) target_link_libraries(kshark-gui kshark-plot diff --git a/kernel-shark-qt/src/KsTraceGraph.cpp b/kernel-shark-qt/src/KsTraceGraph.cpp new file mode 100644 index 0000000..21a09d0 --- /dev/null +++ b/kernel-shark-qt/src/KsTraceGraph.cpp @@ -0,0 +1,690 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsTraceGraph.cpp + * @brief KernelShark Trace Graph widget. + */ + +// KernelShark +#include "KsUtils.hpp" +#include "KsDualMarker.hpp" +#include "KsTraceGraph.hpp" + +/** Create a default (empty) Trace graph widget. */ +KsTraceGraph::KsTraceGraph(QWidget *parent) +: QWidget(parent), + _pointerBar(this), + _navigationBar(this), + _zoomInButton("+", this), + _quickZoomInButton("++", this), + _zoomOutButton("-", this), + _quickZoomOutButton("- -", this), + _scrollLeftButton("<", this), + _scrollRightButton(">", this), + _labelP1("Pointer: ", this), + _labelP2("", this), + _labelI1("", this), + _labelI2("", this), + _labelI3("", this), + _labelI4("", this), + _labelI5("", this), + _scrollArea(this), + _drawWindow(&_scrollArea), + _legendWindow(&_drawWindow), + _legendAxisX(&_drawWindow), + _labelXMin("", &_legendAxisX), + _labelXMid("", &_legendAxisX), + _labelXMax("", &_legendAxisX), + _glWindow(&_drawWindow), + _mState(nullptr), + _data(nullptr), + _keyPressed(false) +{ + auto lamMakeNavButton = [&](QPushButton *b) { + b->setMaximumWidth(FONT_WIDTH * 5); + + connect(b, &QPushButton::released, + this, &KsTraceGraph::_stopUpdating); + _navigationBar.addWidget(b); + }; + + _pointerBar.setMaximumHeight(FONT_HEIGHT * 1.75); + _pointerBar.setOrientation(Qt::Horizontal); + + _navigationBar.setMaximumHeight(FONT_HEIGHT * 1.75); + _navigationBar.setMinimumWidth(FONT_WIDTH * 90); + _navigationBar.setOrientation(Qt::Horizontal); + + _pointerBar.addWidget(&_labelP1); + _labelP2.setFrameStyle(QFrame::Panel | QFrame::Sunken); + _labelP2.setStyleSheet("QLabel { background-color : white;}"); + _labelP2.setTextInteractionFlags(Qt::TextSelectableByMouse); + _labelP2.setFixedWidth(FONT_WIDTH * 16); + _pointerBar.addWidget(&_labelP2); + _pointerBar.addSeparator(); + + _labelI1.setStyleSheet("QLabel {color : blue;}"); + _labelI2.setStyleSheet("QLabel {color : green;}"); + _labelI3.setStyleSheet("QLabel {color : red;}"); + _labelI4.setStyleSheet("QLabel {color : blue;}"); + _labelI5.setStyleSheet("QLabel {color : green;}"); + + _pointerBar.addWidget(&_labelI1); + _pointerBar.addSeparator(); + _pointerBar.addWidget(&_labelI2); + _pointerBar.addSeparator(); + _pointerBar.addWidget(&_labelI3); + _pointerBar.addSeparator(); + _pointerBar.addWidget(&_labelI4); + _pointerBar.addSeparator(); + _pointerBar.addWidget(&_labelI5); + + _legendAxisX.setFixedHeight(FONT_HEIGHT * 1.5); + _legendAxisX.setLayout(new QHBoxLayout); + _legendAxisX.layout()->setSpacing(0); + _legendAxisX.layout()->setContentsMargins(0, 0, FONT_WIDTH, 0); + + _labelXMin.setAlignment(Qt::AlignLeft); + _labelXMid.setAlignment(Qt::AlignHCenter); + _labelXMax.setAlignment(Qt::AlignRight); + + _legendAxisX.layout()->addWidget(&_labelXMin); + _legendAxisX.layout()->addWidget(&_labelXMid); + _legendAxisX.layout()->addWidget(&_labelXMax); + _drawWindow.setMinimumSize(100, 100); + _drawWindow.setStyleSheet("QWidget {background-color : white;}"); + + _drawLayout.setContentsMargins(0, 0, 0, 0); + _drawLayout.setSpacing(0); + _drawLayout.addWidget(&_legendAxisX, 0, 1); + _drawLayout.addWidget(&_legendWindow, 1, 0); + _drawLayout.addWidget(&_glWindow, 1, 1); + _drawWindow.setLayout(&_drawLayout); + + _drawWindow.installEventFilter(this); + + connect(&_glWindow, &KsGLWidget::select, + this, &KsTraceGraph::markEntry); + + connect(&_glWindow, &KsGLWidget::found, + this, &KsTraceGraph::_setPointerInfo); + + connect(&_glWindow, &KsGLWidget::notFound, + this, &KsTraceGraph::_resetPointer); + + connect(&_glWindow, &KsGLWidget::zoomIn, + this, &KsTraceGraph::_zoomIn); + + connect(&_glWindow, &KsGLWidget::zoomOut, + this, &KsTraceGraph::_zoomOut); + + connect(&_glWindow, &KsGLWidget::scrollLeft, + this, &KsTraceGraph::_scrollLeft); + + connect(&_glWindow, &KsGLWidget::scrollRight, + this, &KsTraceGraph::_scrollRight); + + connect(&_glWindow, &KsGLWidget::stopUpdating, + this, &KsTraceGraph::_stopUpdating); + + connect(_glWindow.model(), &KsGraphModel::modelReset, + this, &KsTraceGraph::_updateTimeLegends); + + _scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _scrollArea.setWidget(&_drawWindow); + + lamMakeNavButton(&_scrollLeftButton); + connect(&_scrollLeftButton, &QPushButton::pressed, + this, &KsTraceGraph::_scrollLeft); + + lamMakeNavButton(&_zoomInButton); + connect(&_zoomInButton, &QPushButton::pressed, + this, &KsTraceGraph::_zoomIn); + + lamMakeNavButton(&_zoomOutButton); + connect(&_zoomOutButton, &QPushButton::pressed, + this, &KsTraceGraph::_zoomOut); + + lamMakeNavButton(&_scrollRightButton); + connect(&_scrollRightButton, &QPushButton::pressed, + this, &KsTraceGraph::_scrollRight); + + _navigationBar.addSeparator(); + + lamMakeNavButton(&_quickZoomInButton); + connect(&_quickZoomInButton, &QPushButton::pressed, + this, &KsTraceGraph::_quickZoomIn); + + lamMakeNavButton(&_quickZoomOutButton); + connect(&_quickZoomOutButton, &QPushButton::pressed, + this, &KsTraceGraph::_quickZoomOut); + + _layout.addWidget(&_pointerBar); + _layout.addWidget(&_navigationBar); + _layout.addWidget(&_scrollArea); + this->setLayout(&_layout); + updateGeom(); +} + +/** + * @brief Load and show trace data. + * + * @param data: Input location for the KsDataStore object. + * KsDataStore::loadDataFile() must be called first. + */ +void KsTraceGraph::loadData(KsDataStore *data) +{ + _data = data; + _glWindow.loadData(data); + _updateGraphLegends(); + updateGeom(); +} + +/** Connect the KsGLWidget widget and the State machine of the Dual marker. */ +void KsTraceGraph::setMarkerSM(KsDualMarkerSM *m) +{ + _mState = m; + _navigationBar.addSeparator(); + _mState->placeInToolBar(&_navigationBar); + _glWindow.setMarkerSM(m); +} + +/** Reset (empty) the widget. */ +void KsTraceGraph::reset() +{ + /* Clear the all graph lists and update. */ + _glWindow._cpuList = {}; + _glWindow._taskList = {}; + + _labelP2.setText(""); + for (auto l1: {&_labelI1, &_labelI2, &_labelI3, &_labelI4, &_labelI5}) + l1->setText(""); + + _glWindow.model()->reset(); + _selfUpdate(); + for (auto l2: {&_labelXMin, &_labelXMid, &_labelXMax}) + l2->setText(""); +} + +void KsTraceGraph::_selfUpdate() +{ + _updateGraphLegends(); + _updateTimeLegends(); + _markerReDraw(); + updateGeom(); +} + +void KsTraceGraph::_zoomIn() +{ + _updateGraphs(GraphActions::ZoomIn); +} + +void KsTraceGraph::_zoomOut() +{ + _updateGraphs(GraphActions::ZoomOut); +} + +void KsTraceGraph::_quickZoomIn() +{ + /* Bin size will be 100 ns. */ + _glWindow.model()->quickZoomIn(100); + if (_mState->activeMarker()._isSet && + _mState->activeMarker().isVisible()) { + /* + * Use the position of the active marker as + * a focus point of the zoom. + */ + uint64_t ts = _mState->activeMarker()._ts; + _glWindow.model()->jumpTo(ts); + } +} + +void KsTraceGraph::_quickZoomOut() +{ + _glWindow.model()->quickZoomOut(); +} + +void KsTraceGraph::_scrollLeft() +{ + _updateGraphs(GraphActions::ScrollLeft); +} + +void KsTraceGraph::_scrollRight() +{ + _updateGraphs(GraphActions::ScrollRight); +} + +void KsTraceGraph::_stopUpdating() +{ + /* + * The user is no longer pressing the action button. Reset the + * "Key Pressed" flag. This will stop the ongoing user action. + */ + _keyPressed = false; +} + +void KsTraceGraph::_resetPointer(uint64_t ts, int cpu, int pid) +{ + uint64_t sec, usec; + QString pointer; + + kshark_convert_nano(ts, &sec, &usec); + pointer.sprintf("%lu.%lu", sec, usec); + _labelP2.setText(pointer); + + if (pid > 0 && cpu >= 0) { + struct kshark_context *kshark_ctx(NULL); + + if (!kshark_instance(&kshark_ctx)) + return; + + QString comm(tep_data_comm_from_pid(kshark_ctx->pevent, pid)); + comm.append("-"); + comm.append(QString("%1").arg(pid)); + _labelI1.setText(comm); + _labelI2.setText(QString("CPU %1").arg(cpu)); + } else { + _labelI1.setText(""); + _labelI2.setText(""); + } + + for (auto const &l: {&_labelI3, &_labelI4, &_labelI5}) { + l->setText(""); + } +} + +void KsTraceGraph::_setPointerInfo(size_t i) +{ + kshark_entry *e = _data->rows()[i]; + QString event(kshark_get_event_name_easy(e)); + QString lat(kshark_get_latency_easy(e)); + QString info(kshark_get_info_easy(e)); + QString comm(kshark_get_task_easy(e)); + QString pointer, elidedText; + int labelWidth, width; + uint64_t sec, usec; + + kshark_convert_nano(e->ts, &sec, &usec); + pointer.sprintf("%lu.%lu", sec, usec); + _labelP2.setText(pointer); + + comm.append("-"); + comm.append(QString("%1").arg(kshark_get_pid_easy(e))); + + _labelI1.setText(comm); + _labelI2.setText(QString("CPU %1").arg(e->cpu)); + _labelI3.setText(lat); + _labelI4.setText(event); + _labelI5.setText(info); + QCoreApplication::processEvents(); + + labelWidth = + _pointerBar.geometry().right() - _labelI4.geometry().right(); + if (labelWidth > STRING_WIDTH(info) + FONT_WIDTH * 5) + return; + + /* + * The Info string is too long and cannot be displayed on the toolbar. + * Try to fit the text in the available space. + */ + QFontMetrics metrix(_labelI5.font()); + width = labelWidth - FONT_WIDTH * 3; + elidedText = metrix.elidedText(info, Qt::ElideRight, width); + + while(labelWidth < STRING_WIDTH(elidedText) + FONT_WIDTH * 5) { + width -= FONT_WIDTH * 3; + elidedText = metrix.elidedText(info, Qt::ElideRight, width); + } + + _labelI5.setText(elidedText); + _labelI5.setVisible(true); + QCoreApplication::processEvents(); +} + +/** + * @brief Use the active marker to select particular entry. + * + * @param row: The index of the entry to be selected by the marker. + */ +void KsTraceGraph::markEntry(size_t row) +{ + int graph, cpuGrId, taskGrId; + + _glWindow.findGraphIds(*_data->rows()[row], &cpuGrId, &taskGrId); + + /* + * If a Task graph has been found, this Task graph will be + * visible. If no Task graph has been found, make visible + * the corresponding CPU graph. + */ + if (taskGrId >= 0) + graph = taskGrId; + else + graph = cpuGrId; + + _scrollArea.ensureVisible(0, + _legendAxisX.height() + + _glWindow.vMargin() + + KS_GRAPH_HEIGHT / 2 + + graph*(KS_GRAPH_HEIGHT + _glWindow.vSpacing()), + 50, + KS_GRAPH_HEIGHT / 2 + _glWindow.vSpacing() / 2); + + _glWindow.model()->jumpTo(_data->rows()[row]->ts); + _mState->activeMarker().set(*_data, + _glWindow.model()->histo(), + row, cpuGrId, taskGrId); + + _mState->updateMarkers(*_data, &_glWindow); +} + +void KsTraceGraph::_markerReDraw() +{ + int cpuGrId, taskGrId; + size_t row; + + if (_mState->markerA()._isSet) { + row = _mState->markerA()._pos; + _glWindow.findGraphIds(*_data->rows()[row], &cpuGrId, &taskGrId); + _mState->markerA().set(*_data, + _glWindow.model()->histo(), + row, cpuGrId, taskGrId); + } + + if (_mState->markerB()._isSet) { + row = _mState->markerB()._pos; + _glWindow.findGraphIds(*_data->rows()[row], &cpuGrId, &taskGrId); + _mState->markerB().set(*_data, + _glWindow.model()->histo(), + row, cpuGrId, taskGrId); + } +} + +/** + * @brief Redreaw all CPU graphs. + * + * @param v: CPU ids to be plotted. + */ +void KsTraceGraph::cpuReDraw(QVector v) +{ + _glWindow._cpuList = v; + _selfUpdate(); +} + +/** + * @brief Redreaw all Task graphs. + * + * @param v: Process ids of the tasks to be plotted. + */ +void KsTraceGraph::taskReDraw(QVector v) +{ + _glWindow._taskList = v; + _selfUpdate(); +} + +/** Add (and plot) a CPU graph to the existing list of CPU graphs. */ +void KsTraceGraph::addCPUPlot(int cpu) +{ + if (_glWindow._cpuList.contains(cpu)) + return; + + _glWindow._cpuList.append(cpu); + _selfUpdate(); +} + +/** Add (and plot) a Task graph to the existing list of Task graphs. */ +void KsTraceGraph::addTaskPlot(int pid) +{ + if (_glWindow._taskList.contains(pid)) + return; + + _glWindow._taskList.append(pid); + _selfUpdate(); +} + +/** Update the content of all graphs. */ +void KsTraceGraph::update(KsDataStore *data) +{ + _glWindow.model()->update(data); + _selfUpdate(); +} + +/** Update the geometry of the widget. */ +void KsTraceGraph::updateGeom() +{ + int saWidth, saHeight, dwWidth, hMin; + + /* Set the size of the Scroll Area. */ + saWidth = width() - _layout.contentsMargins().left() - + _layout.contentsMargins().right(); + + saHeight = height() - _pointerBar.height() - + _navigationBar.height() - + _layout.spacing() * 2 - + _layout.contentsMargins().top() - + _layout.contentsMargins().bottom(); + + _scrollArea.resize(saWidth, saHeight); + + /* + * Calculate the width of the Draw Window, taking into account the size + * of the scroll bar. + */ + dwWidth = _scrollArea.width(); + if (_glWindow.height() + _legendAxisX.height() > _scrollArea.height()) + dwWidth -= + qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent); + + /* + * Set the height of the Draw window according to the number of + * plotted graphs. + */ + _drawWindow.resize(dwWidth, + _glWindow.height() + _legendAxisX.height()); + + /* Set the minimum height of the Graph widget. */ + hMin = _drawWindow.height() + + _pointerBar.height() + + _navigationBar.height() + + _layout.contentsMargins().top() + + _layout.contentsMargins().bottom(); + + if (hMin > KS_GRAPH_HEIGHT * 8) + hMin = KS_GRAPH_HEIGHT * 8; + + setMinimumHeight(hMin); + + /* + * Now use the height of the Draw Window to fix the maximum height + * of the Graph widget. + */ + setMaximumHeight(_drawWindow.height() + + _pointerBar.height() + + _navigationBar.height() + + _layout.spacing() * 2 + + _layout.contentsMargins().top() + + _layout.contentsMargins().bottom() + + 2); /* Just a little bit of extra space. This will + * allow the scroll bar to disappear when the + * widget is extended to maximum. + */ +} + +void KsTraceGraph::_updateGraphLegends() +{ + QString graphLegends, graphName; + QVBoxLayout *layout; + int width = 0; + + if (_legendWindow.layout()) { + /* + * Remove and delete the existing layout of the legend window. + */ + QLayoutItem *child; + while ((child = _legendWindow.layout()->takeAt(0)) != 0) { + delete child->widget(); + delete child; + } + + delete _legendWindow.layout(); + } + + layout = new QVBoxLayout; + layout->setContentsMargins(FONT_WIDTH, 0, 0, 0); + layout->setSpacing(_glWindow.vSpacing()); + layout->setAlignment(Qt::AlignTop); + layout->addSpacing(_glWindow.vMargin()); + + auto lamMakeName = [&]() { + QLabel *name = new QLabel(graphName); + + if (width < STRING_WIDTH(graphName)) + width = STRING_WIDTH(graphName); + + name->setAlignment(Qt::AlignBottom); + name->setStyleSheet("QLabel {background-color : white;}"); + name->setFixedHeight(KS_GRAPH_HEIGHT); + layout->addWidget(name); + }; + + for (auto const &cpu: _glWindow._cpuList) { + graphName = QString("CPU %1").arg(cpu); + lamMakeName(); + } + + for (auto const &pid: _glWindow._taskList) { + graphName = QString(tep_data_comm_from_pid(_data->tep(), + pid)); + graphName.append(QString("-%1").arg(pid)); + lamMakeName(); + } + + _legendWindow.setLayout(layout); + _legendWindow.setMaximumWidth(width + FONT_WIDTH); +} + +void KsTraceGraph::_updateTimeLegends() +{ + uint64_t sec, usec, tsMid; + QString tMin, tMid, tMax; + + kshark_convert_nano(_glWindow.model()->histo()->min, &sec, &usec); + tMin.sprintf("%lu.%lu", sec, usec); + _labelXMin.setText(tMin); + + tsMid = (_glWindow.model()->histo()->min + + _glWindow.model()->histo()->max) / 2; + kshark_convert_nano(tsMid, &sec, &usec); + tMid.sprintf("%lu.%lu", sec, usec); + _labelXMid.setText(tMid); + + kshark_convert_nano(_glWindow.model()->histo()->max, &sec, &usec); + tMax.sprintf("%lu.%lu", sec, usec); + _labelXMax.setText(tMax); +} + +/** + * Reimplemented event handler used to update the geometry of the widget on + * resize events. + */ +void KsTraceGraph::resizeEvent(QResizeEvent* event) +{ + updateGeom(); +} + +/** + * Reimplemented event handler (overriding a virtual function from QObject) + * used to detect the position of the mouse with respect to the Draw window and + * according to this position to grab / release the focus of the keyboard. The + * function has nothing to do with the filtering of the trace events. + */ +bool KsTraceGraph::eventFilter(QObject* obj, QEvent* evt) +{ + if (obj == &_drawWindow && evt->type() == QEvent::Enter) + _glWindow.setFocus(); + + if (obj == &_drawWindow && evt->type() == QEvent::Leave) + _glWindow.clearFocus(); + + return QWidget::eventFilter(obj, evt); +} + +void KsTraceGraph::_updateGraphs(GraphActions action) +{ + double k; + int bin; + + /* + * Set the "Key Pressed" flag. The flag will stay set as long as the user + * keeps the corresponding action button pressed. + */ + _keyPressed = true; + + /* Initialize the zooming factor with a small value. */ + k = .01; + while (_keyPressed) { + switch (action) { + case GraphActions::ZoomIn: + if (_mState->activeMarker()._isSet && + _mState->activeMarker().isVisible()) { + /* + * Use the position of the active marker as + * a focus point of the zoom. + */ + bin = _mState->activeMarker()._bin; + _glWindow.model()->zoomIn(k, bin); + } else { + /* + * The default focus point is the center of the + * range interval of the model. + */ + _glWindow.model()->zoomIn(k); + } + + break; + + case GraphActions::ZoomOut: + if (_mState->activeMarker()._isSet && + _mState->activeMarker().isVisible()) { + /* + * Use the position of the active marker as + * a focus point of the zoom. + */ + bin = _mState->activeMarker()._bin; + _glWindow.model()->zoomOut(k, bin); + } else { + /* + * The default focus point is the center of the + * range interval of the model. + */ + _glWindow.model()->zoomOut(k); + } + + break; + + case GraphActions::ScrollLeft: + _glWindow.model()->shiftBackward(10); + break; + + case GraphActions::ScrollRight: + _glWindow.model()->shiftForward(10); + break; + } + + /* + * As long as the action button is pressed, the zooming factor + * will grow smoothly until it reaches a maximum value. This + * will have a visible effect of an accelerating zoom. + */ + if (k < .25) + k *= 1.02; + + _mState->updateMarkers(*_data, &_glWindow); + _updateTimeLegends(); + QCoreApplication::processEvents(); + } +} diff --git a/kernel-shark-qt/src/KsTraceGraph.hpp b/kernel-shark-qt/src/KsTraceGraph.hpp new file mode 100644 index 0000000..395cc1b --- /dev/null +++ b/kernel-shark-qt/src/KsTraceGraph.hpp @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsTraceGraph.hpp + * @brief KernelShark Trace Graph widget. + */ +#ifndef _KS_TRACEGRAPH_H +#define _KS_TRACEGRAPH_H + +// KernelShark +#include "KsGLWidget.hpp" + +/** + * Scroll Area class, needed in order to reimplemented the handler for mouse + * wheel events. + */ +class KsGraphScrollArea : public QScrollArea { +public: + /** Create a default Scroll Area. */ + explicit KsGraphScrollArea(QWidget *parent = nullptr) + : QScrollArea(parent) {} + + /** + * Reimplemented handler for mouse wheel events. All mouse wheel + * events will be ignored. + */ + void wheelEvent(QWheelEvent *evt) {evt->ignore();} +}; + +/** + * The KsTraceViewer class provides a widget for interactive visualization of + * trace data shown as time-series. + */ +class KsTraceGraph : public QWidget +{ + Q_OBJECT +public: + explicit KsTraceGraph(QWidget *parent = nullptr); + + void loadData(KsDataStore *data); + + void setMarkerSM(KsDualMarkerSM *m); + + void reset(); + + /** Get the KsGLWidget object. */ + KsGLWidget *glPtr() {return &_glWindow;} + + void markEntry(size_t); + + void cpuReDraw(QVector); + + void taskReDraw(QVector); + + void addCPUPlot(int); + + void addTaskPlot(int); + + void update(KsDataStore *data); + + void updateGeom(); + + void resizeEvent(QResizeEvent* event) override; + + bool eventFilter(QObject* obj, QEvent* evt) override; + +private: + + void _zoomIn(); + + void _zoomOut(); + + void _quickZoomIn(); + + void _quickZoomOut(); + + void _scrollLeft(); + + void _scrollRight(); + + void _stopUpdating(); + + void _resetPointer(uint64_t ts, int cpu, int pid); + + void _setPointerInfo(size_t); + + void _updateTimeLegends(); + + void _updateGraphLegends(); + + void _selfUpdate(); + + void _markerReDraw(); + + enum class GraphActions { + ZoomIn, + ZoomOut, + ScrollLeft, + ScrollRight + }; + + void _updateGraphs(GraphActions action); + + QToolBar _pointerBar, _navigationBar; + + QPushButton _zoomInButton, _quickZoomInButton; + QPushButton _zoomOutButton, _quickZoomOutButton; + + QPushButton _scrollLeftButton, _scrollRightButton; + + QLabel _labelP1, _labelP2, // Pointer + _labelI1, _labelI2, _labelI3, _labelI4, _labelI5; // Proc. info + + KsGraphScrollArea _scrollArea; + + QWidget _drawWindow, _legendWindow, _legendAxisX; + + QLabel _labelXMin, _labelXMid, _labelXMax; + + KsGLWidget _glWindow; + + QGridLayout _drawLayout; + + QVBoxLayout _layout; + + KsDualMarkerSM *_mState; + + KsDataStore *_data; + + bool _keyPressed; +}; + +#endif // _KS_TRACEGRAPH_H From patchwork Tue Oct 16 15:53:06 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759575 Return-Path: Received: from mail-eopbgr710064.outbound.protection.outlook.com ([40.107.71.64]:54000 "EHLO NAM05-BY2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727223AbeJPXoZ (ORCPT ); Tue, 16 Oct 2018 19:44:25 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 08/23] kernel-shark-qt: Add dialog for Advanced filtering. Date: Tue, 16 Oct 2018 15:53:06 +0000 Message-ID: <20181016155232.5257-9-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 15192 From: Yordan Karadzhov (VMware) This patch defines a dialog for configuring and using Advanced filtering. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 6 +- kernel-shark-qt/src/KsAdvFilteringDialog.cpp | 440 +++++++++++++++++++ kernel-shark-qt/src/KsAdvFilteringDialog.hpp | 91 ++++ 3 files changed, 535 insertions(+), 2 deletions(-) create mode 100644 kernel-shark-qt/src/KsAdvFilteringDialog.cpp create mode 100644 kernel-shark-qt/src/KsAdvFilteringDialog.hpp diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index d406866..26b45f4 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -37,7 +37,8 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) KsDualMarker.hpp KsWidgetsLib.hpp KsTraceGraph.hpp - KsTraceViewer.hpp) + KsTraceViewer.hpp + KsAdvFilteringDialog.hpp) QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr}) @@ -47,7 +48,8 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) KsDualMarker.cpp KsWidgetsLib.cpp KsTraceGraph.cpp - KsTraceViewer.cpp) + KsTraceViewer.cpp + KsAdvFilteringDialog.cpp) target_link_libraries(kshark-gui kshark-plot ${CMAKE_DL_LIBS} diff --git a/kernel-shark-qt/src/KsAdvFilteringDialog.cpp b/kernel-shark-qt/src/KsAdvFilteringDialog.cpp new file mode 100644 index 0000000..ff5a39d --- /dev/null +++ b/kernel-shark-qt/src/KsAdvFilteringDialog.cpp @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsAdvFilteringDialog.cpp + * @brief GUI Dialog for Advanced filtering settings. + */ + +// KernelShark +#include "KsAdvFilteringDialog.hpp" +#include "libkshark.h" +#include "KsUtils.hpp" + +/** Create dialog for Advanced Filtering. */ +KsAdvFilteringDialog::KsAdvFilteringDialog(QWidget *parent) +: QDialog(parent), + _condToolBar1(this), + _condToolBar2(this), + _condToolBar3(this), + _descrLabel(this), + _sysEvLabel("System/Event: ", &_condToolBar1), + _opsLabel("Operator: ", this), + _fieldLabel("Field: ", this), + _systemComboBox(&_condToolBar1), + _eventComboBox(&_condToolBar1), + _opsComboBox(&_condToolBar2), + _fieldComboBox(&_condToolBar3), + _filterEdit(this), + _helpButton("Show Help", this), + _insertEvtButton("Insert", this), + _insertOpButton("Insert", this), + _insertFieldButton("Insert", this), + _applyButton("Apply", this), + _cancelButton("Cancel", this) +{ + struct kshark_context *kshark_ctx(NULL); + int buttonWidth; + + if (!kshark_instance(&kshark_ctx)) + return; + + auto lamAddLine = [&] { + QFrame* line = new QFrame(); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + _topLayout.addWidget(line); + }; + + setMinimumWidth(FONT_WIDTH * 80); + + buttonWidth = STRING_WIDTH("--Show Help--"); + + _helpButton.setFixedWidth(buttonWidth); + _helpButton.setDefault(false); + _topLayout.addWidget(&_helpButton); + + connect(&_helpButton, &QPushButton::pressed, + this, &KsAdvFilteringDialog::_help); + + _descrLabel.setText(_description()); + _topLayout.addWidget(&_descrLabel); + + /* + * For the moment do not show the syntax description. It will be shown + * only if the "Show Help" button is clicked. + */ + _descrLabel.hide(); + + lamAddLine(); + + _getFilters(kshark_ctx); + + if (_filters.count()) { + _makeFilterTable(kshark_ctx); + lamAddLine(); + } + + _condToolBar1.addWidget(&_sysEvLabel); + _condToolBar1.addWidget(&_systemComboBox); + _condToolBar1.addWidget(&_eventComboBox); + + /* + * Using the old Signal-Slot syntax because QComboBox::currentIndexChanged + * has overloads. + */ + connect(&_systemComboBox, SIGNAL(currentIndexChanged(const QString&)), + this, SLOT(_systemChanged(const QString&))); + + connect(&_eventComboBox, SIGNAL(currentIndexChanged(const QString&)), + this, SLOT(_eventChanged(const QString&))); + + _setSystemCombo(kshark_ctx); + + _condToolBar1.addSeparator(); + _condToolBar1.addWidget(&_insertEvtButton); + _topLayout.addWidget(&_condToolBar1); + + _opsComboBox.addItems(_operators()); + + _condToolBar2.addWidget(&_opsLabel); + _condToolBar2.addWidget(&_opsComboBox); + + _condToolBar2.addSeparator(); + _condToolBar2.addWidget(&_insertOpButton); + _topLayout.addWidget(&_condToolBar2); + + _condToolBar3.addWidget(&_fieldLabel); + _condToolBar3.addWidget(&_fieldComboBox); + + _condToolBar3.addSeparator(); + _condToolBar3.addWidget(&_insertFieldButton); + _topLayout.addWidget(&_condToolBar3); + + lamAddLine(); + + _filterEdit.setMinimumWidth(50 * FONT_WIDTH); + _topLayout.addWidget(&_filterEdit); + this->setLayout(&_topLayout); + + buttonWidth = STRING_WIDTH("--Cancel--"); + _applyButton.setFixedWidth(buttonWidth); + _applyButton.setDefault(true); + _cancelButton.setFixedWidth(buttonWidth); + _buttonLayout.addWidget(&_applyButton); + _buttonLayout.addWidget(&_cancelButton); + _buttonLayout.setAlignment(Qt::AlignLeft); + _topLayout.addLayout(&_buttonLayout); + + connect(&_insertEvtButton, &QPushButton::pressed, + this, &KsAdvFilteringDialog::_insertEvt); + + connect(&_insertOpButton, &QPushButton::pressed, + this, &KsAdvFilteringDialog::_insertOperator); + + connect(&_insertFieldButton, &QPushButton::pressed, + this, &KsAdvFilteringDialog::_insertField); + + _applyButtonConnection = + connect(&_applyButton, &QPushButton::pressed, + this, &KsAdvFilteringDialog::_applyPress); + + connect(&_applyButton, &QPushButton::pressed, + this, &QWidget::close); + + connect(&_cancelButton, &QPushButton::pressed, + this, &QWidget::close); +} + +void KsAdvFilteringDialog::_setSystemCombo(struct kshark_context *kshark_ctx) +{ + tep_event_format **events; + QStringList sysList; + int i(0), nEvts(0); + + if (kshark_ctx->pevent) { + nEvts = tep_get_events_count(kshark_ctx->pevent); + events = tep_list_events(kshark_ctx->pevent, + TEP_EVENT_SORT_SYSTEM); + } + + while (i < nEvts) { + QString sysName(events[i]->system); + sysList << sysName; + while (sysName == events[i]->system) { + if (++i == nEvts) + break; + } + } + + qSort(sysList); + _systemComboBox.addItems(sysList); + + i = _systemComboBox.findText("ftrace"); + if (i >= 0) + _systemComboBox.setCurrentIndex(i); +} + +QString KsAdvFilteringDialog::_description() +{ + QString descrText = "Usage:\n"; + descrText += " [,] : [!][(][)]"; + descrText += "[&&/|| [(][)]]\n\n"; + descrText += "Examples:\n\n"; + descrText += " sched/sched_switch : next_prio < 100 && (prev_prio > 100"; + descrText += "&& prev_pid != 0)\n\n"; + descrText += " irq.* : irq != 38\n\n"; + descrText += " .* : common_pid == 1234\n"; + + return descrText; +} + +QStringList KsAdvFilteringDialog::_operators() +{ + QStringList OpsList; + OpsList << ":" << "," << "==" << "!=" << ">" << "<" << ">=" << "<="; + OpsList << "=~" << "!~" << "!" << "(" << ")" << "+" << "-"; + OpsList << "*" << "/" << "<<" << ">>" << "&&" << "||" << "&"; + + return OpsList; +} + +void KsAdvFilteringDialog::_getFilters(struct kshark_context *kshark_ctx) +{ + tep_event_format **events; + char *str; + + events = tep_list_events(kshark_ctx->pevent, TEP_EVENT_SORT_SYSTEM); + + for (int i = 0; events[i]; i++) { + str = tep_filter_make_string(kshark_ctx->advanced_event_filter, + events[i]->id); + if (!str) + continue; + + _filters.insert(events[i]->id, + QString("%1/%2:%3\n").arg(events[i]->system, + events[i]->name, str)); + + free(str); + } +} + +void KsAdvFilteringDialog::_makeFilterTable(struct kshark_context *kshark_ctx) +{ + QMapIterator f(_filters); + QTableWidgetItem *i1, *i2, *i3; + QStringList headers; + int count(0); + + _table = new KsCheckBoxTable(this); + _table->setSelectionMode(QAbstractItemView::SingleSelection); + headers << "Delete" << "Event" << " Id" << "Filter"; + _table->init(headers, _filters.count()); + + for(auto f : _filters.keys()) { + QStringList thisFilter = _filters.value(f).split(":"); + + i1 = new QTableWidgetItem(thisFilter[0]); + _table->setItem(count, 1, i1); + + i2 = new QTableWidgetItem(tr("%1").arg(f)); + _table->setItem(count, 2, i2); + + i3 = new QTableWidgetItem(thisFilter[1]); + _table->setItem(count, 3, i3); + + ++count; + } + + _table->setVisible(false); + _table->resizeColumnsToContents(); + _table->setVisible(true); + + _topLayout.addWidget(_table); +} + +void KsAdvFilteringDialog::_help() +{ + if (_descrLabel.isVisible()) { + _descrLabel.hide(); + QApplication::processEvents(); + + _helpButton.setText("Show Help"); + resize(width(), _noHelpHeight); + } else { + _helpButton.setText("Hide Help"); + _noHelpHeight = height(); + _descrLabel.show(); + } +} + +void KsAdvFilteringDialog::_systemChanged(const QString &sysName) +{ + kshark_context *kshark_ctx(NULL); + tep_event_format **events; + QStringList evtsList; + int i, nEvts; + + _eventComboBox.clear(); + if (!kshark_instance(&kshark_ctx) || !kshark_ctx->pevent) + return; + + nEvts = tep_get_events_count(kshark_ctx->pevent); + events = tep_list_events(kshark_ctx->pevent, TEP_EVENT_SORT_SYSTEM); + + for (i = 0; i < nEvts; ++i) { + if (sysName == events[i]->system) + evtsList << events[i]->name; + } + + qSort(evtsList); + _eventComboBox.addItems(evtsList); + + i = _eventComboBox.findText("function"); + if (i >= 0) + _eventComboBox.setCurrentIndex(i); +} + +QStringList +KsAdvFilteringDialog::_getEventFormatFields(struct tep_event_format *event) +{ + tep_format_field *field, **fields = tep_event_fields(event); + QStringList fieldList; + + for (field = *fields; field; field = field->next) + fieldList << field->name; + + free(fields); + + qSort(fieldList); + return fieldList; +} + +void KsAdvFilteringDialog::_eventChanged(const QString &evtName) +{ + QString sysName = _systemComboBox.currentText(); + kshark_context *kshark_ctx(NULL); + tep_event_format **events; + QStringList fieldList; + int nEvts; + + _fieldComboBox.clear(); + if (!kshark_instance(&kshark_ctx) || !kshark_ctx->pevent) + return; + + nEvts = tep_get_events_count(kshark_ctx->pevent); + events = tep_list_events(kshark_ctx->pevent, TEP_EVENT_SORT_SYSTEM); + + for (int i = 0; i < nEvts; ++i) { + if (evtName == events[i]->name && + sysName == events[i]->system) { + fieldList = _getEventFormatFields(events[i]); + _fieldComboBox.addItems(fieldList); + + return; + } + } +} + +void KsAdvFilteringDialog::_insertEvt() +{ + QString text = _filterEdit.text(); + + auto set_evt = [&] () + { + text += _systemComboBox.currentText(); + text += "/"; + text += _eventComboBox.currentText(); + }; + + if (text == "") { + set_evt(); + text += ":"; + } else { + QString evt = text; + text = ""; + set_evt(); + text += ","; + text += evt; + } + _filterEdit.setText(text); +} + +void KsAdvFilteringDialog::_insertOperator() +{ + QString text = _filterEdit.text(); + + text += _opsComboBox.currentText(); + _filterEdit.setText(text); +} + +void KsAdvFilteringDialog::_insertField() +{ + QString text = _filterEdit.text(); + + text += _fieldComboBox.currentText(); + _filterEdit.setText(text); +} + +void KsAdvFilteringDialog::_applyPress() +{ + QMapIterator f(_filters); + kshark_context *kshark_ctx(NULL); + const char *text; + tep_errno ret; + char *filter; + int i(0); + + if (!kshark_instance(&kshark_ctx)) + return; + + while (f.hasNext()) { + f.next(); + if (_table->_cb[i]->checkState() == Qt::Checked) { + tep_filter_remove_event(kshark_ctx->advanced_event_filter, + f.key()); + } + ++i; + } + + auto job_done = [&]() { + /* + * Disconnect Apply button. This is done in order to protect + * against multiple clicks. + */ + disconnect(_applyButtonConnection); + emit dataReload(); + }; + + text = _filterEdit.text().toLocal8Bit().data(); + if (strlen(text) == 0) { + job_done(); + return; + } + + filter = (char*) malloc(strlen(text) + 1); + strcpy(filter, text); + + ret = tep_filter_add_filter_str(kshark_ctx->advanced_event_filter, + filter); + + if (ret < 0) { + char error_str[200]; + + tep_strerror(kshark_ctx->pevent, ret, error_str, + sizeof(error_str)); + + fprintf(stderr, "filter failed due to: %s\n", error_str); + free(filter); + + return; + } + + free(filter); + + job_done(); +} diff --git a/kernel-shark-qt/src/KsAdvFilteringDialog.hpp b/kernel-shark-qt/src/KsAdvFilteringDialog.hpp new file mode 100644 index 0000000..cde685c --- /dev/null +++ b/kernel-shark-qt/src/KsAdvFilteringDialog.hpp @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsAdvFilteringDialog.hpp + * @brief GUI Dialog for Advanced filtering settings. + */ + +#ifndef _KS_ADV_FILTERING_DIALOG_H +#define _KS_ADV_FILTERING_DIALOG_H + +// Qt +#include + +// KernelShark +#include "KsWidgetsLib.hpp" + +/** + * The KsAdvFilteringDialog class provides a dialog for Advanced filtering. + */ +class KsAdvFilteringDialog : public QDialog +{ + Q_OBJECT +public: + explicit KsAdvFilteringDialog(QWidget *parent = nullptr); + +signals: + /** Signal emitted when the _apply button of the dialog is pressed. */ + void dataReload(); + +private: + int _noHelpHeight; + + QMap _filters; + + KsCheckBoxTable *_table; + + QVBoxLayout _topLayout; + + QHBoxLayout _buttonLayout; + + QToolBar _condToolBar1, _condToolBar2, _condToolBar3; + + QLabel _descrLabel, _sysEvLabel, _opsLabel, _fieldLabel; + + QComboBox _systemComboBox, _eventComboBox; + + QComboBox _opsComboBox, _fieldComboBox; + + QLineEdit _filterEdit; + + QPushButton _helpButton; + + QPushButton _insertEvtButton, _insertOpButton, _insertFieldButton; + + QPushButton _applyButton, _cancelButton; + + QMetaObject::Connection _applyButtonConnection; + + void _help(); + + void _applyPress(); + + void _insertEvt(); + + void _insertOperator(); + + void _insertField(); + + QString _description(); + + QStringList _operators(); + + void _getFilters(struct kshark_context *kshark_ctx); + + void _makeFilterTable(struct kshark_context *kshark_ctx); + + QStringList _getEventFormatFields(struct tep_event_format *event); + + void _setSystemCombo(struct kshark_context *kshark_ctx); + +private slots: + void _systemChanged(const QString&); + + void _eventChanged(const QString&); +}; + +#endif // _KS_ADV_FILTERING_DIALOG_H From patchwork Tue Oct 16 15:53:07 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759599 Return-Path: Received: from mail-eopbgr710064.outbound.protection.outlook.com ([40.107.71.64]:54000 "EHLO NAM05-BY2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726778AbeJPXo0 (ORCPT ); Tue, 16 Oct 2018 19:44:26 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 09/23] kernel-shark-qt: Add a manager class for GUI sessions. Date: Tue, 16 Oct 2018 15:53:07 +0000 Message-ID: <20181016155232.5257-10-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 19587 From: Yordan Karadzhov (VMware) The KsSession class provides instruments for importing/exporting the state of the different components of the GUI from/to Json documents. These instruments are used to save/load user session in the GUI. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 1 + kernel-shark-qt/src/KsSession.cpp | 574 +++++++++++++++++++++++++++++ kernel-shark-qt/src/KsSession.hpp | 100 +++++ 3 files changed, 675 insertions(+) create mode 100644 kernel-shark-qt/src/KsSession.cpp create mode 100644 kernel-shark-qt/src/KsSession.hpp diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 26b45f4..192cd12 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -44,6 +44,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) add_library(kshark-gui SHARED ${ks-guiLib_hdr_moc} KsUtils.cpp KsModels.cpp + KsSession.cpp KsGLWidget.cpp KsDualMarker.cpp KsWidgetsLib.cpp diff --git a/kernel-shark-qt/src/KsSession.cpp b/kernel-shark-qt/src/KsSession.cpp new file mode 100644 index 0000000..96e09f2 --- /dev/null +++ b/kernel-shark-qt/src/KsSession.cpp @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsSession.cpp + * @brief KernelShark Session. + */ + +// KernelShark +#include "libkshark.h" +#include "KsSession.hpp" + +/** Create a KsSession object. */ +KsSession::KsSession() +{ + _config = kshark_config_new("kshark.config.session", + KS_CONFIG_JSON); +} + +/** Destroy a KsSession object. */ +KsSession::~KsSession() +{ + kshark_free_config_doc(_config); +} + +/** Import a user session from a Json file. */ +void KsSession::importFromFile(QString jfileName) +{ + if (_config) + kshark_free_config_doc(_config); + + _config = kshark_open_config_file(jfileName.toStdString().c_str(), + "kshark.config.session"); +} + +/** Export the current user session from a Json file. */ +void KsSession::exportToFile(QString jfileName) +{ + kshark_save_config_file(jfileName.toStdString().c_str(), _config); +} + +/** + * @brief Save the state of the visualization model. + * + * @param histo: Input location for the model descriptor. + */ +void KsSession::saveVisModel(kshark_trace_histo *histo) +{ + kshark_config_doc *model = + kshark_export_model(histo, KS_CONFIG_JSON); + + kshark_config_doc_add(_config, "Model", model); +} + +/** + * @brief Load the state of the visualization model. + * + * @param model: Input location for the KsGraphModel object. + */ +void KsSession::loadVisModel(KsGraphModel *model) +{ + kshark_config_doc *modelConf = kshark_config_alloc(KS_CONFIG_JSON); + + if (!kshark_config_doc_get(_config, "Model", modelConf)) + return; + + kshark_import_model(model->histo(), modelConf); + model->update(); +} + +/** Save the trace data file. */ +void KsSession::saveDataFile(QString fileName) +{ + kshark_config_doc *file = + kshark_export_trace_file(fileName.toStdString().c_str(), + KS_CONFIG_JSON); + + kshark_config_doc_add(_config, "Data", file); +} + +/** Get the trace data file. */ +QString KsSession::getDataFile(kshark_context *kshark_ctx) +{ + kshark_config_doc *file = kshark_config_alloc(KS_CONFIG_JSON); + const char *file_str; + + if (!kshark_config_doc_get(_config, "Data", file)) + return QString(); + + file_str = kshark_import_trace_file(kshark_ctx, file); + if (file_str) + return QString(file_str); + + return QString(); +} + +/** + * @brief Save the configuration of the filters. + * + * @param kshark_ctx: Input location for context pointer. + */ +void KsSession::saveFilters(kshark_context *kshark_ctx) +{ + kshark_config_doc *filters = + kshark_export_all_filters(kshark_ctx, KS_CONFIG_JSON); + + kshark_config_doc_add(_config, "Filters", filters); +} + +/** + * @brief Load the configuration of the filters and filter the data. + * + * @param kshark_ctx: Input location for context pointer. + * @param data: Input location for KsDataStore object; + */ +void KsSession::loadFilters(kshark_context *kshark_ctx, KsDataStore *data) +{ + kshark_config_doc *filters = kshark_config_alloc(KS_CONFIG_JSON); + + if (!kshark_config_doc_get(_config, "Filters", filters)) + return; + + kshark_import_all_filters(kshark_ctx, filters); + + if (kshark_ctx->advanced_event_filter->filters) + data->reload(); + else + kshark_filter_entries(kshark_ctx, data->rows(), data->size()); + + data->registerCPUCollections(); + + emit data->updateWidgets(data); +} + +/** + * @brief Save the state of the table. + * + * @param view: Input location for the KsTraceViewer widget. + */ +void KsSession::saveTable(const KsTraceViewer &view) { + kshark_config_doc *topRow = kshark_config_alloc(KS_CONFIG_JSON); + int64_t r = view.getTopRow(); + + topRow->conf_doc = json_object_new_int64(r); + kshark_config_doc_add(_config, "ViewTop",topRow); +} + +/** + * @brief Load the state of the table. + * + * @param view: Input location for the KsTraceViewer widget. + */ +void KsSession::loadTable(KsTraceViewer *view) { + kshark_config_doc *topRow = kshark_config_alloc(KS_CONFIG_JSON); + size_t r = 0; + + if (!kshark_config_doc_get(_config, "ViewTop", topRow)) + return; + + if (_config->format == KS_CONFIG_JSON) + r = json_object_get_int64(KS_JSON_CAST(topRow->conf_doc)); + + view->setTopRow(r); +} + +/** + * @brief Save the KernelShark Main window size. + * + * @param window: Input location for the KsMainWindow widget. + */ +void KsSession::saveMainWindowSize(const QMainWindow &window) +{ + kshark_config_doc *windowConf = kshark_config_alloc(KS_CONFIG_JSON); + int width = window.width(), height = window.height(); + json_object *jwindow = json_object_new_array(); + + json_object_array_put_idx(jwindow, 0, json_object_new_int(width)); + json_object_array_put_idx(jwindow, 1, json_object_new_int(height)); + + windowConf->conf_doc = jwindow; + kshark_config_doc_add(_config, "MainWindow", windowConf); +} + +/** + * @brief Load the KernelShark Main window size. + * + * @param window: Input location for the KsMainWindow widget. + */ +void KsSession::loadMainWindowSize(QMainWindow *window) +{ + kshark_config_doc *windowConf = kshark_config_alloc(KS_CONFIG_JSON); + json_object *jwindow, *jwidth, *jheight; + int width, height; + + if (!kshark_config_doc_get(_config, "MainWindow", windowConf)) + return; + + if (_config->format == KS_CONFIG_JSON) { + jwindow = KS_JSON_CAST(windowConf->conf_doc); + jwidth = json_object_array_get_idx(jwindow, 0); + jheight = json_object_array_get_idx(jwindow, 1); + + width = json_object_get_int(jwidth); + height = json_object_get_int(jheight); + + window->resize(width, height); + } +} + +/** + * @brief Save the state of the Main window spliter. + * + * @param splitter: Input location for the splitter widget. + */ +void KsSession::saveSplitterSize(const QSplitter &splitter) +{ + kshark_config_doc *spl = kshark_config_alloc(KS_CONFIG_JSON); + json_object *jspl = json_object_new_array(); + QList sizes = splitter.sizes(); + + json_object_array_put_idx(jspl, 0, json_object_new_int(sizes[0])); + json_object_array_put_idx(jspl, 1, json_object_new_int(sizes[1])); + + spl->conf_doc = jspl; + kshark_config_doc_add(_config, "Splitter", spl); +} + +/** + * @brief Load the state of the Main window spliter. + * + * @param splitter: Input location for the splitter widget. + */ +void KsSession::loadSplitterSize(QSplitter *splitter) +{ + kshark_config_doc *spl = kshark_config_alloc(KS_CONFIG_JSON); + json_object *jspl, *jgraphsize, *jviewsize; + int graphSize, viewSize; + QList sizes; + + if (!kshark_config_doc_get(_config, "Splitter", spl)) + return; + + if (_config->format == KS_CONFIG_JSON) { + jspl = KS_JSON_CAST(spl->conf_doc); + jgraphsize = json_object_array_get_idx(jspl, 0); + jviewsize = json_object_array_get_idx(jspl, 1); + + graphSize = json_object_get_int(jgraphsize); + viewSize = json_object_get_int(jviewsize); + } + + sizes << graphSize << viewSize; + splitter->setSizes(sizes); +} + +/** @brief Save the Color scheme used. */ +void KsSession::saveColorScheme() { + kshark_config_doc *colSch = kshark_config_alloc(KS_CONFIG_JSON); + double s = KsPlot::Color::getRainbowFrequency(); + + colSch->conf_doc = json_object_new_double(s); + kshark_config_doc_add(_config, "ColorScheme", colSch); +} + +/** @brief Get the Color scheme used. */ +float KsSession::getColorScheme() { + kshark_config_doc *colSch = kshark_config_alloc(KS_CONFIG_JSON); + + /* Default color scheme. */ + float s = 0.75; + + if (!kshark_config_doc_get(_config, "ColorScheme", colSch)) + return s; + + if (_config->format == KS_CONFIG_JSON) + s = json_object_get_double(KS_JSON_CAST(colSch->conf_doc)); + + return s; +} + +/** + * @brief Save the list of the graphs plotted. + * + * @param glw: Input location for the KsGLWidget widget. + */ +void KsSession::saveGraphs(const KsGLWidget &glw) +{ + _saveCPUPlots(glw._cpuList); + _saveTaskPlots(glw._taskList); +} + +/** + * @brief Load the list of the graphs and plot. + * + * @param graphs: Input location for the KsTraceGraph widget. + */ +void KsSession::loadGraphs(KsTraceGraph *graphs) +{ + graphs->cpuReDraw(_getCPUPlots()); + graphs->taskReDraw(_getTaskPlots()); +} + +void KsSession::_saveCPUPlots(const QVector &cpus) +{ + kshark_config_doc *cpuPlts = kshark_config_alloc(KS_CONFIG_JSON); + json_object *jcpus = json_object_new_array(); + + for (int i = 0; i < cpus.count(); ++i) { + json_object *jcpu = json_object_new_int(cpus[i]); + json_object_array_put_idx(jcpus, i, jcpu); + } + + cpuPlts->conf_doc = jcpus; + kshark_config_doc_add(_config, "CPUPlots", cpuPlts); +} + +QVector KsSession::_getCPUPlots() +{ + kshark_config_doc *cpuPlts = kshark_config_alloc(KS_CONFIG_JSON); + json_object *jcpus; + QVector cpus; + size_t length; + + if (!kshark_config_doc_get(_config, "CPUPlots", cpuPlts)) + return cpus; + + if (_config->format == KS_CONFIG_JSON) { + jcpus = KS_JSON_CAST(cpuPlts->conf_doc); + length = json_object_array_length(jcpus); + for (size_t i = 0; i < length; ++i) { + int cpu = json_object_get_int(json_object_array_get_idx(jcpus, + i)); + cpus.append(cpu); + } + } + + return cpus; +} + +void KsSession::_saveTaskPlots(const QVector &tasks) +{ + kshark_config_doc *taskPlts = kshark_config_alloc(KS_CONFIG_JSON); + json_object *jtasks = json_object_new_array(); + + for (int i = 0; i < tasks.count(); ++i) { + json_object *jtask = json_object_new_int(tasks[i]); + json_object_array_put_idx(jtasks, i, jtask); + } + + taskPlts->conf_doc = jtasks; + kshark_config_doc_add(_config, "TaskPlots", taskPlts); +} + +QVector KsSession::_getTaskPlots() +{ + kshark_config_doc *taskPlts = kshark_config_alloc(KS_CONFIG_JSON); + json_object *jtasks; + QVector tasks; + size_t length; + + if (!kshark_config_doc_get(_config, "TaskPlots", taskPlts)) + return tasks; + + if (_config->format == KS_CONFIG_JSON) { + jtasks = KS_JSON_CAST(taskPlts->conf_doc); + length = json_object_array_length(jtasks); + for (size_t i = 0; i < length; ++i) { + int pid = json_object_get_int(json_object_array_get_idx(jtasks, + i)); + tasks.append(pid); + } + } + + return tasks; +} + +/** + * @brief Save the state of the Dual marker. + * + * @param dm: Input location for the KsDualMarkerSM object. + */ +void KsSession::saveDualMarker(KsDualMarkerSM *dm) +{ + struct kshark_config_doc *markers = + kshark_config_new("kshark.config.markers", KS_CONFIG_JSON); + json_object *jd_mark = KS_JSON_CAST(markers->conf_doc); + + auto save_mark = [&jd_mark] (KsGraphMark *m, const char *name) + { + json_object *jmark = json_object_new_object(); + + if (m->_isSet) { + json_object_object_add(jmark, "isSet", + json_object_new_boolean(true)); + + json_object_object_add(jmark, "row", + json_object_new_int(m->_pos)); + } else { + json_object_object_add(jmark, "isSet", + json_object_new_boolean(false)); + } + + json_object_object_add(jd_mark, name, jmark); + }; + + save_mark(&dm->markerA(), "markA"); + save_mark(&dm->markerB(), "markB"); + + if (dm->getState() == DualMarkerState::A) + json_object_object_add(jd_mark, "Active", + json_object_new_string("A")); + else + json_object_object_add(jd_mark, "Active", + json_object_new_string("B")); + + kshark_config_doc_add(_config, "Markers", markers); +} + +/** + * @brief Load the state of the Dual marker. + * + * @param dm: Input location for the KsDualMarkerSM object. + * @param graphs: Input location for the KsTraceGraph widget. + */ +void KsSession::loadDualMarker(KsDualMarkerSM *dm, KsTraceGraph *graphs) +{ + uint64_t pos; + + dm->reset(); + dm->setState(DualMarkerState::A); + + if (_getMarker("markA", &pos)) { + graphs->markEntry(pos); + } else { + dm->markerA().remove(); + } + + dm->setState(DualMarkerState::B); + if (_getMarker("markB", &pos)) { + graphs->markEntry(pos); + } else { + dm->markerB().remove(); + } + + dm->setState(_getMarkerState()); + pos = dm->activeMarker()._pos; + + emit graphs->glPtr()->updateView(pos, true); +} + +json_object *KsSession::_getMarkerJson() +{ + struct kshark_config_doc *markers = + kshark_config_alloc(KS_CONFIG_JSON); + + if (!kshark_config_doc_get(_config, "Markers", markers) || + !kshark_type_check(markers, "kshark.config.markers")) + return nullptr; + + return KS_JSON_CAST(markers->conf_doc); +} + +bool KsSession::_getMarker(const char* name, size_t *pos) +{ + json_object *jd_mark, *jmark; + + *pos = 0; + jd_mark = _getMarkerJson(); + if (!jd_mark) + return false; + + if (json_object_object_get_ex(jd_mark, name, &jmark)) { + json_object *jis_set; + json_object_object_get_ex(jmark, "isSet", &jis_set); + if (!json_object_get_boolean(jis_set)) + return false; + + json_object *jpos; + json_object_object_get_ex(jmark, "row", &jpos); + *pos = json_object_get_int64(jpos); + } + + return true; +} + +DualMarkerState KsSession::_getMarkerState() +{ + json_object *jd_mark, *jstate; + const char* state; + + jd_mark = _getMarkerJson(); + json_object_object_get_ex(jd_mark, "Active", &jstate); + state = json_object_get_string(jstate); + + if (strcmp(state, "A") == 0) + return DualMarkerState::A; + + return DualMarkerState::B; +} + +/** + * @brief Save the configuration of the plugins. + * + * @param pm: Input location for the KsPluginManager object. + */ +void KsSession::savePlugins(const KsPluginManager &pm) +{ + struct kshark_config_doc *plugins = + kshark_config_new("kshark.config.plugins", KS_CONFIG_JSON); + json_object *jplugins = KS_JSON_CAST(plugins->conf_doc); + const QVector ®isteredPlugins = pm._registeredKsPlugins; + const QStringList &pluginList = pm._ksPluginList; + int nPlugins = pluginList.length(); + json_object *jlist, *jpl; + QByteArray array; + char* buffer; + bool active; + + jlist = json_object_new_array(); + for (int i = 0; i < nPlugins; ++i) { + array = pluginList[i].toLocal8Bit(); + buffer = array.data(); + jpl = json_object_new_array(); + json_object_array_put_idx(jpl, 0, json_object_new_string(buffer)); + + active = registeredPlugins[i]; + json_object_array_put_idx(jpl, 1, json_object_new_boolean(active)); + json_object_array_put_idx(jlist, i, jpl); + } + + json_object_object_add(jplugins, "Plugin List", jlist); + kshark_config_doc_add(_config, "Plugins", plugins); +} + +/** + * @brief Load the configuration of the plugins. + * + * @param kshark_ctx: Input location for context pointer. + * @param pm: Input location for the KsPluginManager object. + */ +void KsSession::loadPlugins(kshark_context *kshark_ctx, KsPluginManager *pm) +{ + kshark_config_doc *plugins = kshark_config_alloc(KS_CONFIG_JSON); + json_object *jplugins, *jlist, *jpl; + int length; + + if (!kshark_config_doc_get(_config, "Plugins", plugins) || + !kshark_type_check(plugins, "kshark.config.plugins")) + return; + + if (plugins->format == KS_CONFIG_JSON) { + jplugins = KS_JSON_CAST(plugins->conf_doc); + json_object_object_get_ex(jplugins, "Plugin List", &jlist); + if (!jlist || + json_object_get_type(jlist) != json_type_array || + !json_object_array_length(jlist)) + return; + + length = json_object_array_length(jlist); + for (int i = 0; i < length; ++i) { + jpl = json_object_array_get_idx(jlist, i); + pm->_ksPluginList[i] = + json_object_get_string(json_object_array_get_idx(jpl, 0)); + + pm->_registeredKsPlugins[i] = + json_object_get_boolean(json_object_array_get_idx(jpl, 1)); + } + } + + pm->registerFromList(kshark_ctx); +} diff --git a/kernel-shark-qt/src/KsSession.hpp b/kernel-shark-qt/src/KsSession.hpp new file mode 100644 index 0000000..4f5a2c4 --- /dev/null +++ b/kernel-shark-qt/src/KsSession.hpp @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsSession.hpp + * @brief KernelShark Session. + */ + +#ifndef _KS_SESSION_H +#define _KS_SESSION_H + +// Qt +#include + +// KernelShark +#include "KsDualMarker.hpp" +#include "KsTraceGraph.hpp" +#include "KsTraceViewer.hpp" + +/** + * The KsSession class provides instruments for importing/exporting the state + * of the different components of the GUI from/to Json documents. These + * instruments are used to save/load user session in the GUI. + */ +class KsSession +{ +public: + KsSession(); + + virtual ~KsSession(); + + /** Get the configuration document object. */ + kshark_config_doc *getConfDocPtr() const {return _config;} + + void importFromFile(QString jfileName); + + void exportToFile(QString jfileName); + + void saveDataFile(QString fileName); + + QString getDataFile(kshark_context *kshark_ctx); + + void saveVisModel(kshark_trace_histo *histo); + + void loadVisModel(KsGraphModel *model); + + void saveGraphs(const KsGLWidget &glw); + + void loadGraphs(KsTraceGraph *graphs); + + void saveFilters(kshark_context *kshark_ctx); + + void loadFilters(kshark_context *kshark_ctx, KsDataStore *data); + + void saveMainWindowSize(const QMainWindow &window); + + void loadMainWindowSize(QMainWindow *window); + + void saveSplitterSize(const QSplitter &splitter); + + void loadSplitterSize(QSplitter *splitter); + + void saveDualMarker(KsDualMarkerSM *dm); + + void loadDualMarker(KsDualMarkerSM *dmm, KsTraceGraph *graphs); + + void savePlugins(const KsPluginManager &pm); + + void loadPlugins(kshark_context *kshark_ctx, KsPluginManager *pm); + + void saveTable(const KsTraceViewer &view); + + void loadTable(KsTraceViewer *view); + + void saveColorScheme(); + + float getColorScheme(); + +private: + kshark_config_doc *_config; + + json_object *_getMarkerJson(); + + void _saveCPUPlots(const QVector &cpus); + + QVector _getCPUPlots(); + + void _saveTaskPlots(const QVector &tasks); + + QVector _getTaskPlots(); + + bool _getMarker(const char* name, size_t *pos); + + DualMarkerState _getMarkerState(); +}; + +#endif From patchwork Tue Oct 16 15:53:09 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759581 Return-Path: Received: from mail-eopbgr710064.outbound.protection.outlook.com ([40.107.71.64]:54000 "EHLO NAM05-BY2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727254AbeJPXob (ORCPT ); Tue, 16 Oct 2018 19:44:31 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 10/23] kernel-shark-qt: Add Main Window widget for the KernelShark GUI. Date: Tue, 16 Oct 2018 15:53:09 +0000 Message-ID: <20181016155232.5257-11-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 29351 From: Yordan Karadzhov (VMware) The KsMainWindow class provides the Main window for the KernelShark GUI. It implements the frame used by the two trace data viewing areas (Graphs and Table) together withe the system of menus, used to perform different actions. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/CMakeLists.txt | 5 + kernel-shark-qt/src/CMakeLists.txt | 2 + kernel-shark-qt/src/KsMainWindow.cpp | 909 +++++++++++++++++++++++++++ kernel-shark-qt/src/KsMainWindow.hpp | 191 ++++++ 4 files changed, 1107 insertions(+) create mode 100644 kernel-shark-qt/src/KsMainWindow.cpp create mode 100644 kernel-shark-qt/src/KsMainWindow.hpp diff --git a/kernel-shark-qt/CMakeLists.txt b/kernel-shark-qt/CMakeLists.txt index 71a021e..0ccb61e 100644 --- a/kernel-shark-qt/CMakeLists.txt +++ b/kernel-shark-qt/CMakeLists.txt @@ -12,6 +12,11 @@ message("\n project: Kernel Shark: (version: ${KS_VERSION_STRING})\n") set(KS_DIR ${CMAKE_SOURCE_DIR}) +# Make a directory to hold configuration files. To change this do: +# cmake .. -DKS_CONF_DIR=/your/preferred/path +set(KS_CONF_DIR "${KS_DIR}/.ksconf" CACHE STRING "Directory for configuration files.") +file(MAKE_DIRECTORY ${KS_CONF_DIR}) + include(${KS_DIR}/build/FindTraceCmd.cmake) include(${KS_DIR}/build/FindJSONC.cmake) diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 192cd12..d8daada 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -38,6 +38,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) KsWidgetsLib.hpp KsTraceGraph.hpp KsTraceViewer.hpp + KsMainWindow.hpp KsAdvFilteringDialog.hpp) QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr}) @@ -50,6 +51,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) KsWidgetsLib.cpp KsTraceGraph.cpp KsTraceViewer.cpp + KsMainWindow.cpp KsAdvFilteringDialog.cpp) target_link_libraries(kshark-gui kshark-plot diff --git a/kernel-shark-qt/src/KsMainWindow.cpp b/kernel-shark-qt/src/KsMainWindow.cpp new file mode 100644 index 0000000..d30f752 --- /dev/null +++ b/kernel-shark-qt/src/KsMainWindow.cpp @@ -0,0 +1,909 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsMainWindow.cpp + * @brief KernelShark GUI main window. + */ + +// C +#include +#include +#include + +// C++11 +#include + +// Qt +#include +#include +#include +#include +#include + +// KernelShark +#include "libkshark.h" +#include "KsCmakeDef.hpp" +#include "KsMainWindow.hpp" +#include "KsAdvFilteringDialog.hpp" + +/** Create KernelShark Main window. */ +KsMainWindow::KsMainWindow(QWidget *parent) +: QMainWindow(parent), + _splitter(Qt::Vertical, this), + _data(this), + _view(this), + _graph(this), + _mState(this), + _plugins(this), + _openAction("Open", this), + _restorSessionAction("Restor Last Session", this), + _importSessionAction("Import Session", this), + _exportSessionAction("Export Sassion", this), + _quitAction("Quit", this), + _importFilterAction("Import Filter", this), + _exportFilterAction("Export Filter", this), + _graphFilterSyncAction(this), + _listFilterSyncAction(this), + _showEventsAction("Show events", this), + _showTasksAction("Show tasks", this), + _hideTasksAction("Hide tasks", this), + _advanceFilterAction("Advance Filtering", this), + _clearAllFilters("Clear all filters", this), + _cpuSelectAction("CPUs", this), + _taskSelectAction("Tasks", this), + _pluginsAction("Plugins", this), + _colorAction(this), + _colSlider(this), + _colorPhaseSlider(Qt::Horizontal, this), + _fullScreenModeAction("Full Screen Mode", this), + _isFullScreen(false), + _aboutAction("About", this), + _contentsAction("Contents", this) +{ + setWindowTitle("Kernel Shark"); + _createActions(); + _createMenus(); + + _splitter.addWidget(&_graph); + _splitter.addWidget(&_view); + setCentralWidget(&_splitter); + 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::plotTask, + &_graph, &KsTraceGraph::addTaskPlot); + + connect(_graph.glPtr(), &KsGLWidget::updateView, + &_view, &KsTraceViewer::showRow); + + connect(_graph.glPtr(), &KsGLWidget::deselect, + &_view, &KsTraceViewer::deselect); + + connect(&_data, &KsDataStore::updateWidgets, + &_view, &KsTraceViewer::update); + + connect(&_data, &KsDataStore::updateWidgets, + &_graph, &KsTraceGraph::update); + + connect(&_plugins, &KsPluginManager::dataReload, + &_data, &KsDataStore::reload); + + _resizeEmpty(); +} + +/** Destroy KernelShark Main window. */ +KsMainWindow::~KsMainWindow() +{ + kshark_context *kshark_ctx(nullptr); + QString file = KS_CONF_DIR; + + file += "/lastsession.json"; + + _updateSession(); + kshark_save_config_file(file.toLocal8Bit().data(), + _session.getConfDocPtr()); + + _data.clear(); + + if (kshark_instance(&kshark_ctx)) + kshark_free(kshark_ctx); +} + +/** + * Reimplemented event handler used to update the geometry of the window on + * resize events. + */ +void KsMainWindow::resizeEvent(QResizeEvent* event) +{ + QMainWindow::resizeEvent(event); + + _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); + + _restorSessionAction.setIcon(QIcon::fromTheme("document-open-recent")); + connect(&_restorSessionAction, &QAction::triggered, + this, &KsMainWindow::_restorSession); + + _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 */ + _importFilterAction.setIcon(QIcon::fromTheme("document-send")); + _importFilterAction.setStatusTip("Load a filter"); + + connect(&_importFilterAction, &QAction::triggered, + this, &KsMainWindow::_importFilter); + + _exportFilterAction.setIcon(QIcon::fromTheme("document-revert")); + _exportFilterAction.setStatusTip("Export a filter"); + + connect(&_exportFilterAction, &QAction::triggered, + this, &KsMainWindow::_exportFilter); + + connect(&_showEventsAction, &QAction::triggered, + this, &KsMainWindow::_showEvents); + + connect(&_showTasksAction, &QAction::triggered, + this, &KsMainWindow::_showTasks); + + connect(&_hideTasksAction, &QAction::triggered, + this, &KsMainWindow::_hideTasks); + + 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 */ + _pluginsAction.setShortcut(tr("Ctrl+P")); + _pluginsAction.setStatusTip("Manage plugins"); + + connect(&_pluginsAction, &QAction::triggered, + this, &KsMainWindow::_pluginSelect); + + _colorPhaseSlider.setMinimum(20); + _colorPhaseSlider.setMaximum(180); + _colorPhaseSlider.setValue(KsPlot::Color::getRainbowFrequency() * 100); + _colorPhaseSlider.setFixedWidth(FONT_WIDTH * 15); + + connect(&_colorPhaseSlider, &QSlider::valueChanged, + this, &KsMainWindow::_setColorPhase); + + _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::_fullScreenMode); + + /* 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); +} + +void KsMainWindow::_createMenus() +{ + QMenu *file, *sessions, *filter, *plots, *tools, *help; + kshark_context *kshark_ctx(nullptr); + QCheckBox *cbf2g, *cbf2l; + + if (!kshark_instance(&kshark_ctx)) + return; + + /* File menu */ + file = menuBar()->addMenu("File"); + file->addAction(&_openAction); + + sessions = file->addMenu("Sessions"); + sessions->setIcon(QIcon::fromTheme("document-properties")); + sessions->addAction(&_restorSessionAction); + sessions->addAction(&_importSessionAction); + sessions->addAction(&_exportSessionAction); + file->addAction(&_quitAction); + + /* Filter menu */ + filter = menuBar()->addMenu("Filter"); + filter->addAction(&_importFilterAction); + filter->addAction(&_exportFilterAction); + + auto lamMakeCBAction = [&](QWidgetAction *action, QString name) + { + QWidget *containerWidget = new QWidget(filter); + containerWidget->setLayout(new QHBoxLayout()); + containerWidget->layout()->setContentsMargins(FONT_WIDTH, FONT_HEIGHT/5, + FONT_WIDTH, FONT_HEIGHT/5); + QCheckBox *checkBox = new QCheckBox(name, filter); + checkBox->setChecked(true); + containerWidget->layout()->addWidget(checkBox); + action->setDefaultWidget(containerWidget); + return checkBox; + }; + + /* + * 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; + + cbf2g = lamMakeCBAction(&_graphFilterSyncAction, + "Apply filters to Graph"); + + connect(cbf2g, &QCheckBox::stateChanged, + this, &KsMainWindow::_graphFilterSync); + + cbf2l = lamMakeCBAction(&_listFilterSyncAction, + "Apply filters to List"); + + connect(cbf2l, &QCheckBox::stateChanged, + this, &KsMainWindow::_listFilterSync); + + filter->addAction(&_graphFilterSyncAction); + filter->addAction(&_listFilterSyncAction); + filter->addAction(&_showEventsAction); + filter->addAction(&_showTasksAction); + filter->addAction(&_hideTasksAction); + filter->addAction(&_advanceFilterAction); + filter->addAction(&_clearAllFilters); + + /* Plot menu */ + plots = menuBar()->addMenu("Plots"); + plots->addAction(&_cpuSelectAction); + plots->addAction(&_taskSelectAction); + + /* Tools menu */ + tools = menuBar()->addMenu("Tools"); + tools->addAction(&_pluginsAction); + tools->addSeparator(); + tools->addAction(&_colorAction); + tools->addAction(&_fullScreenModeAction); + + /* Help menu */ + help = menuBar()->addMenu("Help"); + help->addAction(&_aboutAction); + help->addAction(&_contentsAction); +} + +void KsMainWindow::_open() +{ + QString fileName = + QFileDialog::getOpenFileName(this, + "Open File", + KS_DIR, + "trace-cmd files (*.dat);;All files (*)"); + + if (!fileName.isEmpty()) + loadDataFile(fileName); +} + +void KsMainWindow::_restorSession() +{ + QString file = KS_CONF_DIR; + file += "/lastsession.json"; + + loadSession(file); + _graph.updateGeom(); +} + +void KsMainWindow::_importSession() +{ + QString fileName = + QFileDialog::getOpenFileName(this, + "Import Session", + KS_DIR, + "Kernel Shark Config files (*.json);;"); + + if (fileName.isEmpty()) + return; + + loadSession(fileName); + _graph.updateGeom(); +} + +void KsMainWindow::_updateSession() +{ + kshark_context *kshark_ctx(nullptr); + + if (!kshark_instance(&kshark_ctx)) + return; + + _session.saveGraphs(*_graph.glPtr()); + _session.saveVisModel(_graph.glPtr()->model()->histo()); + _session.saveFilters(kshark_ctx); + _session.saveDualMarker(&_mState); + _session.saveTable(_view); + _session.saveColorScheme(); + _session.savePlugins(_plugins); +} + +void KsMainWindow::_exportSession() +{ + QString fileName = + QFileDialog::getSaveFileName(this, + "Export Filter", + KS_DIR, + "Kernel Shark Config files (*.json);;"); + + if (fileName.isEmpty()) + return; + + if (!fileName.endsWith(".json")) { + fileName += ".json"; + if (QFileInfo(fileName).exists()) { + QString msg("A file "); + QMessageBox msgBox; + + msg += fileName; + msg += " already exists."; + msgBox.setText(msg); + msgBox.setInformativeText("Do you want to replace it?"); + + msgBox.setStandardButtons(QMessageBox::Save | + QMessageBox::Cancel); + + msgBox.setDefaultButton(QMessageBox::Cancel); + + if (msgBox.exec() == QMessageBox::Cancel) + return; + } + } + + _updateSession(); + _session.exportToFile(fileName); +} + +void KsMainWindow::_importFilter() +{ + kshark_context *kshark_ctx(nullptr); + kshark_config_doc *conf; + QString fileName; + + if (!kshark_instance(&kshark_ctx)) + return; + + fileName = QFileDialog::getOpenFileName(this, "Import Filter", KS_DIR, + "Kernel Shark Config files (*.json);;"); + + if (fileName.isEmpty()) + return; + + conf = kshark_open_config_file(fileName.toStdString().c_str(), + "kshark.config.filter"); + if (!conf) + return; + + kshark_import_all_event_filters(kshark_ctx, conf); + kshark_free_config_doc(conf); + + kshark_filter_entries(kshark_ctx, _data.rows(), _data.size()); + emit _data.updateWidgets(&_data); +} + +void KsMainWindow::_exportFilter() +{ + kshark_context *kshark_ctx(nullptr); + kshark_config_doc *conf(nullptr); + QString fileName; + + if (!kshark_instance(&kshark_ctx)) + return; + + fileName = QFileDialog::getSaveFileName(this, "Export Filter", KS_DIR, + "Kernel Shark Config files (*.json);;"); + + if (fileName.isEmpty()) + return; + + if (!fileName.endsWith(".json")) + fileName += ".json"; + + kshark_export_all_event_filters(kshark_ctx, &conf); + kshark_save_config_file(fileName.toStdString().c_str(), conf); + kshark_free_config_doc(conf); +} + +void KsMainWindow::_listFilterSync(int state) +{ + KsUtils::listFilterSync(state); + _data.update(); +} + +void KsMainWindow::_graphFilterSync(int state) +{ + KsUtils::graphFilterSync(state); + _data.update(); +} + +void KsMainWindow::_showEvents() +{ + kshark_context *kshark_ctx(nullptr); + KsCheckBoxWidget *events_cb; + KsCheckBoxDialog *dialog; + + if (!kshark_instance(&kshark_ctx)) + return; + + events_cb = new KsEventsCheckBoxWidget(_data.tep(), this); + dialog = new KsCheckBoxDialog(events_cb, this); + + if (!kshark_ctx->show_event_filter || + !kshark_ctx->show_event_filter->count) { + events_cb->setDefault(true); + } else { + /* + * The event filter contains IDs. Make this visible in the + * CheckBox Widget. + */ + tep_event_format **events = + tep_list_events(_data.tep(), TEP_EVENT_SORT_SYSTEM); + int nEvts = tep_get_events_count(_data.tep()); + QVector v(nEvts, false); + + for (int i = 0; i < nEvts; ++i) { + if (tracecmd_filter_id_find(kshark_ctx->show_event_filter, + events[i]->id)) + v[i] = true; + } + + events_cb->set(v); + } + + connect(dialog, &KsCheckBoxDialog::apply, + &_data, &KsDataStore::applyPosEventFilter); + + dialog->show(); +} + +void KsMainWindow::_showTasks() +{ + kshark_context *kshark_ctx(nullptr); + KsCheckBoxWidget *tasks_cbd; + KsCheckBoxDialog *dialog; + + if (!kshark_instance(&kshark_ctx)) + return; + + tasks_cbd = new KsTasksCheckBoxWidget(_data.tep(), true, this); + dialog = new KsCheckBoxDialog(tasks_cbd, this); + + if (!kshark_ctx->show_task_filter || + !kshark_ctx->show_task_filter->count) { + tasks_cbd->setDefault(true); + } else { + QVector pids = KsUtils::getPidList(); + int nPids = pids.count(); + QVector v(nPids, false); + + for (int i = 0; i < nPids; ++i) { + if (tracecmd_filter_id_find(kshark_ctx->show_task_filter, + pids[i])) + v[i] = true; + } + + tasks_cbd->set(v); + } + + connect(dialog, &KsCheckBoxDialog::apply, + &_data, &KsDataStore::applyPosTaskFilter); + + dialog->show(); +} + +void KsMainWindow::_hideTasks() +{ + kshark_context *kshark_ctx(nullptr); + KsCheckBoxWidget *tasks_cbd; + KsCheckBoxDialog *dialog; + + if (!kshark_instance(&kshark_ctx)) + return; + + tasks_cbd = new KsTasksCheckBoxWidget(_data.tep(), false, this); + dialog = new KsCheckBoxDialog(tasks_cbd, this); + + if (!kshark_ctx->hide_task_filter || + !kshark_ctx->hide_task_filter->count) { + tasks_cbd->setDefault(false); + } else { + QVector pids = KsUtils::getPidList(); + int nPids = pids.count(); + QVector v(nPids, false); + + for (int i = 0; i < nPids; ++i) { + if (tracecmd_filter_id_find(kshark_ctx->hide_task_filter, + pids[i])) + v[i] = true; + } + + tasks_cbd->set(v); + } + + connect(dialog, &KsCheckBoxDialog::apply, + &_data, &KsDataStore::applyNegTaskFilter); + + dialog->show(); +} + +void KsMainWindow::_advancedFiltering() +{ + KsAdvFilteringDialog *dialog; + + if (!_data.tep()) { + QErrorMessage *em = new QErrorMessage(this); + QString text("Unable to open Advanced filtering dialog."); + + text += " Tracing data has to be loaded first."; + + em->showMessage(text, "advancedFiltering"); + qCritical() << "ERROR: " << text; + + return; + } + + dialog = new KsAdvFilteringDialog(this); + connect(dialog, &KsAdvFilteringDialog::dataReload, + &_data, &KsDataStore::reload); + + dialog->show(); +} + +void KsMainWindow::_clearFilters() +{ + _data.clearAllFilters(); +} + +void KsMainWindow::_cpuSelect() +{ + KsCheckBoxWidget *cpus_cbd = new KsCPUCheckBoxWidget(_data.tep(), this); + KsCheckBoxDialog *dialog = new KsCheckBoxDialog(cpus_cbd, this); + + if(_data.tep()) { + int nCPUs = tep_get_cpus(_data.tep()); + if (nCPUs == _graph.glPtr()->cpuGraphCount()) { + cpus_cbd->setDefault(true); + } else { + QVector v(nCPUs, false); + + for (auto const &cpu: _graph.glPtr()->_cpuList) + v[cpu] = true; + + cpus_cbd->set(v); + } + } + + connect(dialog, &KsCheckBoxDialog::apply, + &_graph, &KsTraceGraph::cpuReDraw); + + dialog->show(); +} + +void KsMainWindow::_taskSelect() +{ + KsCheckBoxWidget *tasks_cbd = new KsTasksCheckBoxWidget(_data.tep(), + true, + this); + KsCheckBoxDialog *dialog = new KsCheckBoxDialog(tasks_cbd, this); + QVector pids = KsUtils::getPidList(); + int nPids = pids.count(); + + if (nPids == _graph.glPtr()->taskGraphCount()) { + tasks_cbd->setDefault(true); + } else { + QVector v(nPids, false); + for (int i = 0; i < nPids; ++i) { + for (auto const &pid: _graph.glPtr()->_taskList) { + if (pids[i] == pid) { + v[i] = true; + break; + } + } + } + + tasks_cbd->set(v); + } + + connect(dialog, &KsCheckBoxDialog::apply, + &_graph, &KsTraceGraph::taskReDraw); + + dialog->show(); +} + +void KsMainWindow::_pluginSelect() +{ + KsCheckBoxWidget *plugin_cbd; + KsCheckBoxDialog *dialog; + QVector registeredPlugins; + QStringList plugins; + + plugins << _plugins._ksPluginList << _plugins._userPluginList; + + registeredPlugins << _plugins._registeredKsPlugins + << _plugins._registeredUserPlugins; + + plugin_cbd = new KsPluginCheckBoxWidget(plugins, this); + plugin_cbd->set(registeredPlugins); + + dialog = new KsCheckBoxDialog(plugin_cbd, this); + + connect(dialog, &KsCheckBoxDialog::apply, + &_plugins, &KsPluginManager::updatePlugins); + + dialog->show(); +} + +void KsMainWindow::_setColorPhase(int f) +{ + KsPlot::Color::setRainbowFrequency(f / 100.); + _graph.glPtr()->model()->update(); +} + +void KsMainWindow::_fullScreenMode() +{ + if (_isFullScreen) { + _fullScreenModeAction.setText("Full Screen Mode"); + _fullScreenModeAction.setIcon(QIcon::fromTheme("view-fullscreen")); + showNormal(); + _isFullScreen = false; + } else { + _fullScreenModeAction.setText("Exit Full Screen Mode"); + _fullScreenModeAction.setIcon(QIcon::fromTheme("view-restore")); + showFullScreen(); + _isFullScreen = true; + } +} + +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("https://www.google.bg/search?q=kernelshark", + QUrl::TolerantMode)); +} + +/** Load trace data for file. */ +void KsMainWindow::loadDataFile(const QString& fileName) +{ + char buff[FILENAME_MAX]; + QString pbLabel("Loading "); + bool loadDone = false; + struct stat st; + int ret; + + 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, true); + + return; + } + + qInfo() << "Loading " << fileName; + + _mState.reset(); + _view.reset(); + _graph.reset(); + + if (fileName.size() < 40) { + pbLabel += fileName; + } else { + pbLabel += "..."; + pbLabel += fileName.mid(fileName.size() - 37, 37); + } + + setWindowTitle("Kernel Shark"); + KsProgressBar pb(pbLabel); + QApplication::processEvents(); + + auto lamLoadJob = [&](KsDataStore *d) { + d->loadDataFile(fileName); + loadDone = true; + }; + std::thread tload(lamLoadJob, &_data); + + 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); + } + + tload.join(); + + if (!_data.size()) { + QString text("File "); + + text.append(fileName); + text.append(" contains no data."); + _error(text, "loadDataErr2", true, true); + + return; + } + + pb.setValue(165); + _view.loadData(&_data); + + pb.setValue(180); + _graph.loadData(&_data); + pb.setValue(195); + setWindowTitle("Kernel Shark (" + fileName + ")"); + + if (realpath(fileName.toStdString().c_str(), buff)) { + QString path(buff); + _session.saveDataFile(path); + } +} + +void KsMainWindow::_error(const QString &text, const QString &errCode, + bool resize, bool unloadPlugins) +{ + QErrorMessage *em = new QErrorMessage(this); + + if (resize) + _resizeEmpty(); + + if (unloadPlugins) + _plugins.unloadAll(); + + em->showMessage(text, errCode); + qCritical() << "ERROR: " << text; +} + +/** + * @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); + 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, true); + + return; + } + + _session.importFromFile(fileName); + _session.loadPlugins(kshark_ctx, &_plugins); + + QString dataFile(_session.getDataFile(kshark_ctx)); + if (dataFile.isEmpty()) { + QString text("Unable to open trace data file for session "); + + text.append(fileName); + text.append("\n"); + _error(text, "loadSessErr1", true, true); + + return; + } + + loadDataFile(dataFile); + if (!_data.tep()) { + _plugins.unloadAll(); + return; + } + + KsProgressBar pb("Loading session settings ..."); + pb.setValue(10); + + _session.loadGraphs(&_graph); + pb.setValue(20); + + _session.loadFilters(kshark_ctx, &_data); + pb.setValue(130); + + _session.loadSplitterSize(&_splitter); + _session.loadMainWindowSize(this); + this->show(); + pb.setValue(140); + + _session.loadDualMarker(&_mState, &_graph); + _session.loadVisModel(_graph.glPtr()->model()); + _mState.updateMarkers(_data, _graph.glPtr()); + pb.setValue(170); + + _session.loadTable(&_view); + _colorPhaseSlider.setValue(_session.getColorScheme() * 100); +} + +void KsMainWindow::_splitterMoved(int pos, int index) +{ + _session.saveSplitterSize(_splitter); +} diff --git a/kernel-shark-qt/src/KsMainWindow.hpp b/kernel-shark-qt/src/KsMainWindow.hpp new file mode 100644 index 0000000..6e3f864 --- /dev/null +++ b/kernel-shark-qt/src/KsMainWindow.hpp @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsMainWindow.hpp + * @brief KernelShark GUI main window. + */ + +#ifndef _KS_MAINWINDOW_H +#define _KS_MAINWINDOW_H + +// Qt +#include + +// KernelShark +#include "KsTraceViewer.hpp" +#include "KsTraceGraph.hpp" +#include "KsSession.hpp" +#include "KsUtils.hpp" + +/** + * The KsMainWindow class provides Main window for the KernelShark GUI. + */ +class KsMainWindow : public QMainWindow +{ + Q_OBJECT +public: + explicit KsMainWindow(QWidget *parent = nullptr); + + ~KsMainWindow(); + + void loadDataFile(const QString &fileName); + + void loadSession(const QString &fileName); + + /** + * @brief + * + * @param plugin: can be the name of the plugin or the plugin's library + * file (including absolute or relative path). + */ + void registerPlugin(const QString &plugin) + { + _plugins.registerPlugin(plugin); + } + + /** + * @brief + * + * @param plugin: can be the name of the plugin or the plugin's library + * file (including absolute path). + */ + void unregisterPlugin(const QString &plugin) + { + _plugins.unregisterPlugin(plugin); + } + + void resizeEvent(QResizeEvent* event); + +private: + QSplitter _splitter; + + /** GUI session. */ + KsSession _session; + + /** Data Manager. */ + KsDataStore _data; + + /** Widget for reading and searching in the trace data. */ + KsTraceViewer _view; + + /** Widget for graphical visualization of the trace data. */ + KsTraceGraph _graph; + + /** Dual Marker State Machine. */ + KsDualMarkerSM _mState; + + /** Plugin manager. */ + KsPluginManager _plugins; + + // File menu. + QAction _openAction; + + QAction _restorSessionAction; + + QAction _importSessionAction; + + QAction _exportSessionAction; + + QAction _quitAction; + + // Filter menu. + QAction _importFilterAction; + + QAction _exportFilterAction; + + QWidgetAction _graphFilterSyncAction; + + QWidgetAction _listFilterSyncAction; + + QAction _showEventsAction; + + QAction _showTasksAction; + + QAction _hideTasksAction; + + QAction _advanceFilterAction; + + QAction _clearAllFilters; + + // Plots menu. + QAction _cpuSelectAction; + + QAction _taskSelectAction; + + // Tools menu. + QAction _pluginsAction; + + QWidgetAction _colorAction; + + QWidget _colSlider; + + QSlider _colorPhaseSlider; + + QAction _fullScreenModeAction; + + bool _isFullScreen; + + // Help menu. + QAction _aboutAction; + + QAction _contentsAction; + + void _open(); + + void _restorSession(); + + void _importSession(); + + void _exportSession(); + + void _importFilter(); + + void _exportFilter(); + + void _listFilterSync(int state); + + void _graphFilterSync(int state); + + void _showEvents(); + + void _showTasks(); + + void _hideTasks(); + + void _advancedFiltering(); + + void _clearFilters(); + + void _cpuSelect(); + + void _taskSelect(); + + void _pluginSelect(); + + void _setColorPhase(int); + + void _fullScreenMode(); + + void _aboutInfo(); + + void _contents(); + + void _splitterMoved(int pos, int index); + + void _createActions(); + + void _createMenus(); + + void _updateSession(); + + inline void _resizeEmpty() {resize(SCREEN_WIDTH * .5, FONT_HEIGHT * 3);} + + void _error(const QString &text, const QString &errCode, + bool resize, bool unloadPlugins); +}; + +#endif // _KS_MAINWINDOW_H From patchwork Tue Oct 16 15:53:10 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759583 Return-Path: Received: from mail-eopbgr710064.outbound.protection.outlook.com ([40.107.71.64]:54000 "EHLO NAM05-BY2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727146AbeJPXoe (ORCPT ); Tue, 16 Oct 2018 19:44:34 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 11/23] kernel-shark-qt: Add KernelShark GUI executable. Date: Tue, 16 Oct 2018 15:53:10 +0000 Message-ID: <20181016155232.5257-12-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 3048 From: Yordan Karadzhov (VMware) This patch adds the main executable of the KernelShark GUI. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 4 ++ kernel-shark-qt/src/kernelshark.cpp | 93 +++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 kernel-shark-qt/src/kernelshark.cpp diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index d8daada..b51980f 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -63,6 +63,10 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) set_target_properties(kshark-gui PROPERTIES SUFFIX ".so.${KS_VERSION_STRING}") + message(STATUS "kernelshark") + add_executable(kernelshark kernelshark.cpp) + target_link_libraries(kernelshark kshark-gui) + endif (Qt5Widgets_FOUND AND Qt5Network_FOUND) add_subdirectory(plugins) diff --git a/kernel-shark-qt/src/kernelshark.cpp b/kernel-shark-qt/src/kernelshark.cpp new file mode 100644 index 0000000..2ec91de --- /dev/null +++ b/kernel-shark-qt/src/kernelshark.cpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +// C +#include +#include + +// Qt +#include + +// KernelShark +#include "KsCmakeDef.hpp" +#include "KsMainWindow.hpp" + +#define default_input_file (char*)"trace.dat" + +static char *input_file; + +void usage(const char *prog) +{ + printf("Usage: %s\n", prog); + printf(" -h Display this help message\n"); + printf(" -v Display version and exit\n"); + printf(" -i input_file, default is %s\n", default_input_file); + printf(" -p register plugin, use plugin name, absolute or relative path\n"); + printf(" -u unregister plugin, use plugin name or absolute path\n"); + printf(" -s import a session\n"); +} + +int main(int argc, char **argv) +{ + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication a(argc, argv); + + KsMainWindow ks; + + int c; + bool fromSession = false; + + while ((c = getopt(argc, argv, "hvi:p:u:s:")) != -1) { + switch(c) { + case 'h': + usage(argv[0]); + return 0; + + case 'v': + printf("%s - %s\n", basename(argv[0]), KS_VERSION_STRING); + return 0; + + case 'i': + input_file = optarg; + break; + + case 'p': + ks.registerPlugin(QString(optarg)); + break; + + case 'u': + ks.unregisterPlugin(QString(optarg)); + break; + + case 's': + ks.loadSession(QString(optarg)); + fromSession = true; + + default: + break; + } + } + + if (!fromSession) { + if ((argc - optind) >= 1) { + if (input_file) + usage(argv[0]); + input_file = argv[optind]; + } + + if (!input_file) { + struct stat st; + if (stat(default_input_file, &st) == 0) + input_file = default_input_file; + } + + if (input_file) + ks.loadDataFile(QString(input_file)); + } + + ks.show(); + return a.exec(); +} From patchwork Tue Oct 16 15:53:11 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759587 Return-Path: Received: from mail-eopbgr710064.outbound.protection.outlook.com ([40.107.71.64]:54000 "EHLO NAM05-BY2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727006AbeJPXoh (ORCPT ); Tue, 16 Oct 2018 19:44:37 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" Subject: [PATCH v2 12/23] kernel-shark-qt: Add "File exists" dialog. Date: Tue, 16 Oct 2018 15:53:11 +0000 Message-ID: <20181016155232.5257-13-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 3456 A helper function for launching a "File exists" dialog is added to KsWidgetsLib. This function asks the user, before overwriting an existing file. The "File exists" dialog function is used by the KsMainWindow widget when saving the configuration of the filters and sessions. Signed-off-by: Yordan Karadzhov --- kernel-shark-qt/src/KsMainWindow.cpp | 22 +++++++-------------- kernel-shark-qt/src/KsWidgetsLib.cpp | 29 ++++++++++++++++++++++++++++ kernel-shark-qt/src/KsWidgetsLib.hpp | 7 +++++++ 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/kernel-shark-qt/src/KsMainWindow.cpp b/kernel-shark-qt/src/KsMainWindow.cpp index d30f752..b9fb587 100644 --- a/kernel-shark-qt/src/KsMainWindow.cpp +++ b/kernel-shark-qt/src/KsMainWindow.cpp @@ -397,20 +397,7 @@ void KsMainWindow::_exportSession() if (!fileName.endsWith(".json")) { fileName += ".json"; if (QFileInfo(fileName).exists()) { - QString msg("A file "); - QMessageBox msgBox; - - msg += fileName; - msg += " already exists."; - msgBox.setText(msg); - msgBox.setInformativeText("Do you want to replace it?"); - - msgBox.setStandardButtons(QMessageBox::Save | - QMessageBox::Cancel); - - msgBox.setDefaultButton(QMessageBox::Cancel); - - if (msgBox.exec() == QMessageBox::Cancel) + if (!KsWidgetsLib::fileExistsDialog(fileName)) return; } } @@ -461,8 +448,13 @@ void KsMainWindow::_exportFilter() if (fileName.isEmpty()) return; - if (!fileName.endsWith(".json")) + if (!fileName.endsWith(".json")) { fileName += ".json"; + if (QFileInfo(fileName).exists()) { + if (!KsWidgetsLib::fileExistsDialog(fileName)) + return; + } + } kshark_export_all_event_filters(kshark_ctx, &conf); kshark_save_config_file(fileName.toStdString().c_str(), conf); diff --git a/kernel-shark-qt/src/KsWidgetsLib.cpp b/kernel-shark-qt/src/KsWidgetsLib.cpp index f7fca09..b4b62a4 100644 --- a/kernel-shark-qt/src/KsWidgetsLib.cpp +++ b/kernel-shark-qt/src/KsWidgetsLib.cpp @@ -76,6 +76,35 @@ KsMessageDialog::KsMessageDialog(QString message, QWidget *parent) this->setLayout(&_layout); } +namespace KsWidgetsLib +{ + +/** + * @brief Launch a File exists dialog. Use this function to ask the user + * before overwriting an existing file. + * + * @param fileName: the name of the file. + * + * @returns True if the user wants to overwrite the file. Otherwise + */ +bool fileExistsDialog(QString fileName) +{ + QString msg("A file "); + QMessageBox msgBox; + + msg += fileName; + msg += " already exists."; + msgBox.setText(msg); + msgBox.setInformativeText("Do you want to replace it?"); + + msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + + return (msgBox.exec() == QMessageBox::Save); +} + +}; // KsWidgetsLib + /** * @brief Create KsCheckBoxWidget. * diff --git a/kernel-shark-qt/src/KsWidgetsLib.hpp b/kernel-shark-qt/src/KsWidgetsLib.hpp index b9ba35a..89c196a 100644 --- a/kernel-shark-qt/src/KsWidgetsLib.hpp +++ b/kernel-shark-qt/src/KsWidgetsLib.hpp @@ -66,6 +66,13 @@ public: /** The width of the KsMessageDialog widget. */ #define KS_MSG_DIALOG_WIDTH (SCREEN_WIDTH / 10) +namespace KsWidgetsLib +{ + +bool fileExistsDialog(QString fileName); + +}; // KsWidgetsLib + /** * The KsCheckBoxWidget class is the base class of all CheckBox widget used * by KernelShark. From patchwork Tue Oct 16 15:53:12 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759593 Return-Path: Received: from mail-sn1nam01on0073.outbound.protection.outlook.com ([104.47.32.73]:27872 "EHLO NAM01-SN1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727246AbeJPXoX (ORCPT ); Tue, 16 Oct 2018 19:44:23 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" Subject: [PATCH v2 13/23] kernel-shark-qt: Fix the glitches in the preemption time visualization Date: Tue, 16 Oct 2018 15:53:12 +0000 Message-ID: <20181016155232.5257-14-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 7735 This problem was reported by Steven Rostedt. The reason for having the problem was my wrong assumption that for a given task the sched_switch event is always the last record before the task is preempted. The patch changes two things: It modifies the "match" functions used to search for sched events, making these functions to trigger only on sched_switch/_wakeup events. This eliminates the flakiness due to the fact that sometimes the sched_switch can happend to be in the same bin with some of the trailing events from the same task. This also has the side effect of simplifying the code. It introduces a second pass over the data, which is task-specific and gets executed only the first time the task is plotted. This second pass edits the "pid" field of the last trailing event making it equal to the "next pid" field of the sched_switch event. Signed-off-by: Yordan Karadzhov --- kernel-shark-qt/src/plugins/SchedEvents.cpp | 126 ++++++++++++++------ kernel-shark-qt/src/plugins/sched_events.c | 13 +- 2 files changed, 94 insertions(+), 45 deletions(-) diff --git a/kernel-shark-qt/src/plugins/SchedEvents.cpp b/kernel-shark-qt/src/plugins/SchedEvents.cpp index 713feb4..7f75baa 100644 --- a/kernel-shark-qt/src/plugins/SchedEvents.cpp +++ b/kernel-shark-qt/src/plugins/SchedEvents.cpp @@ -16,6 +16,7 @@ // C++ 11 #include +#include // KernelShark #include "libkshark.h" @@ -29,25 +30,12 @@ #define PLUGIN_MAX_ENTRIES_PER_BIN 500 +#define KS_TASK_COLLECTION_MARGIN 25 + //! @endcond extern struct plugin_sched_context *plugin_sched_context_handler; -static int plugin_get_wakeup_pid(kshark_context *kshark_ctx, - plugin_sched_context *plugin_ctx, - const struct kshark_entry *e) -{ - struct tep_record *record; - unsigned long long val; - - record = kshark_read_at(kshark_ctx, e->offset); - tep_read_number_field(plugin_ctx->sched_wakeup_pid_field, - record->data, &val); - free_record(record); - - return val; -} - /** Sched Event identifier. */ enum class SchedEvent { /** Sched Switch Event. */ @@ -139,23 +127,16 @@ static void pluginDraw(plugin_sched_context *plugin_ctx, ksmodel_get_entry_back(histo, bin, false, plugin_wakeup_match_pid, pid, col, nullptr); - int wakeup_pid; - - if (entryB && - plugin_ctx->sched_wakeup_event && - entryB->event_id == plugin_ctx->sched_wakeup_event->id) { - wakeup_pid = - plugin_get_wakeup_pid(kshark_ctx, plugin_ctx, entryB); - if (wakeup_pid == pid) { - /* - * entryB is a sched_wakeup_event. Open a - * green box here. - */ - openBox(graph->getBin(bin)._base); - /* Green */ - rec->_color = KsPlot::Color(0, 255, 0); - } + if (entryB) { + /* + * entryB is a sched_wakeup_event. Open a + * green box here. + */ + openBox(graph->getBin(bin)._base); + + /* Green */ + rec->_color = KsPlot::Color(0, 255, 0); } }; @@ -171,10 +152,7 @@ static void pluginDraw(plugin_sched_context *plugin_ctx, plugin_switch_match_pid, pid, col, nullptr); - if (entryB && - entryB->pid != pid && - plugin_ctx->sched_switch_event && - entryB->event_id == plugin_ctx->sched_switch_event->id) { + if (entryB && entryB->pid != pid) { /* * entryB is a sched_switch_event. Open a * red box here. @@ -215,6 +193,67 @@ static void pluginDraw(plugin_sched_context *plugin_ctx, return; } +static std::unordered_set secondPassDone; + +/* + * Ideally, the sched_switch has to be the last trace event recorded before the + * task is preempted. Because of this, when the data is loaded (the first pass), + * the "pid" field of the sched_switch entries gets edited by this plugin to be + * equal to the "next pid" of the sched_switch event. However, in reality the + * sched_switch event may be followed by some trailing events from the same task + * (printk events for example). This has the effect of extending the graph of + * the task outside of the actual duration of the task. The "second pass" over + * the data is used to fix this problem. It takes advantage of the "next" field + * of the entry (this field is set during the first pass) to search for trailing + * events after the "sched_switch". + */ +static void secondPass(kshark_entry **data, + kshark_entry_collection *col, + int pid) +{ + const kshark_entry *e; + kshark_entry *last; + int first, n; + ssize_t index; + + /* Loop over the intervals of the data collection. */ + for (size_t i = 0; i < col->size; ++i) { + first = col->break_points[i]; + n = first - col->resume_points[i]; + + kshark_entry_request *req = + kshark_entry_request_alloc(first, n, + plugin_switch_match_pid, pid, + false, + KS_GRAPH_VIEW_FILTER_MASK); + + e = kshark_get_entry_back(req, data, &index); + free(req); + + if (!e || index < 0) { + /* No sched_switch event in this interval. */ + continue; + } + + /* Find the very last trailing event. */ + for (last = data[index]; last->next; last = last->next) { + if (last->next->pid != pid) { + /* + * This is the last trailing event. Change the + * "pid" to be equal to the "next pid" of the + * sched_switch event and leave a sign that you + * edited this entry. + */ + last->pid = data[index]->pid; + last->visible &= ~KS_PLUGIN_UNTOUCHED_MASK; + break; + } + } + } + + secondPassDone.insert(pid); +} + /** * @brief Plugin's draw function. * @@ -246,8 +285,25 @@ void plugin_draw(kshark_cpp_argv *argv_c, int pid, int draw_action) */ col = kshark_find_data_collection(kshark_ctx->collections, kshark_match_pid, pid); + if (!col) { + /* + * If a data collection for this task does not exist, + * register a new one. + */ + kshark_entry **data = argvCpp->_histo->data; + int size = argvCpp->_histo->data_size; + col = kshark_register_data_collection(kshark_ctx, + data, size, + kshark_match_pid, pid, + KS_TASK_COLLECTION_MARGIN); + } try { + if (secondPassDone.find(pid) == secondPassDone.end()) { + /* The second pass for this task is not done yet. */ + secondPass(argvCpp->_histo->data, col, pid); + } + pluginDraw(plugin_ctx, kshark_ctx, argvCpp->_histo, col, SchedEvent::Switch, pid, diff --git a/kernel-shark-qt/src/plugins/sched_events.c b/kernel-shark-qt/src/plugins/sched_events.c index 13f3c4a..80ac214 100644 --- a/kernel-shark-qt/src/plugins/sched_events.c +++ b/kernel-shark-qt/src/plugins/sched_events.c @@ -167,9 +167,6 @@ bool plugin_wakeup_match_pid(struct kshark_context *kshark_ctx, unsigned long long val; int wakeup_pid = -1; - if (e->pid == pid) - return true; - plugin_ctx = plugin_sched_context_handler; if (!plugin_ctx) return false; @@ -221,23 +218,19 @@ bool plugin_switch_match_pid(struct kshark_context *kshark_ctx, int pid) { struct plugin_sched_context *plugin_ctx; - struct tep_record *record = NULL; int switch_pid = -1; - if (e->pid == pid) - return true; - plugin_ctx = plugin_sched_context_handler; if (plugin_ctx->sched_switch_event && e->event_id == plugin_ctx->sched_switch_event->id) { - record = kshark_read_at(kshark_ctx, e->offset); + struct tep_record *record; + record = kshark_read_at(kshark_ctx, e->offset); switch_pid = tep_data_pid(plugin_ctx->pevent, record); + free_record(record); } - free_record(record); - if (switch_pid >= 0 && switch_pid == pid) return true; From patchwork Tue Oct 16 15:53:13 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759573 Return-Path: Received: from mail-sn1nam01on0073.outbound.protection.outlook.com ([104.47.32.73]:27872 "EHLO NAM01-SN1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726778AbeJPXoY (ORCPT ); Tue, 16 Oct 2018 19:44:24 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 14/23] kernel-shark-qt: Add dialog for of trace data recording Date: Tue, 16 Oct 2018 15:53:13 +0000 Message-ID: <20181016155232.5257-15-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 21325 From: Yordan Karadzhov (VMware) This patch defines a dialog to be used for trace data recording. The dialog has two main areas: a Control panel and a terminal-like widget for monitoring the recording process. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 2 + kernel-shark-qt/src/KsCaptureDialog.cpp | 562 ++++++++++++++++++++++++ kernel-shark-qt/src/KsCaptureDialog.hpp | 185 ++++++++ 3 files changed, 749 insertions(+) create mode 100644 kernel-shark-qt/src/KsCaptureDialog.cpp create mode 100644 kernel-shark-qt/src/KsCaptureDialog.hpp diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index b51980f..3c9e1bf 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -39,6 +39,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) KsTraceGraph.hpp KsTraceViewer.hpp KsMainWindow.hpp + KsCaptureDialog.hpp KsAdvFilteringDialog.hpp) QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr}) @@ -52,6 +53,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) KsTraceGraph.cpp KsTraceViewer.cpp KsMainWindow.cpp + KsCaptureDialog.cpp KsAdvFilteringDialog.cpp) target_link_libraries(kshark-gui kshark-plot diff --git a/kernel-shark-qt/src/KsCaptureDialog.cpp b/kernel-shark-qt/src/KsCaptureDialog.cpp new file mode 100644 index 0000000..ee1abc3 --- /dev/null +++ b/kernel-shark-qt/src/KsCaptureDialog.cpp @@ -0,0 +1,562 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsCaptureDialog.cpp + * @brief Dialog for trace data recording. + */ + +// Qt +#include + +// KernelShark +#include "libkshark.h" +#include "KsUtils.hpp" +#include "KsCmakeDef.hpp" +#include "KsCaptureDialog.hpp" + +static inline tep_handle *local_events() +{ + return tracecmd_local_events(tracecmd_get_tracing_dir()); +} + +/** @brief Create KsCaptureControl widget. */ +KsCaptureControl::KsCaptureControl(QWidget *parent) +: QWidget(parent), + _localTEP(local_events()), + _eventsWidget(_localTEP, this), + _pluginsLabel("Plugin: ", this), + _outputLabel("Output file: ", this), + _commandLabel("Command: ", this), + _outputLineEdit("trace.dat", this), + _commandLineEdit("sleep 0.1", this), + _settingsToolBar(this), + _controlToolBar(this), + _pluginsComboBox(this), + _importSettingsButton("Import Settings", this), + _exportSettingsButton("Export Settings", this), + _outputBrowseButton("Browse", this), + _commandCheckBox("Display output", this), + _captureButton("Capture", &_controlToolBar), + _applyButton("Apply", &_controlToolBar), + _closeButton("Close", &_controlToolBar) +{ + QStringList pluginList = _getPlugins(); + int row(0); + + auto lamAddLine = [&] { + QFrame* line = new QFrame(); + + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + _topLayout.addWidget(line); + }; + + if (pluginList.count() == 0) { + /* + * No plugins have been found. Most likely this is because + * the process has no Root privileges. + */ + QString message("Error: No events or plugins found.\n"); + message += "Root privileges are required."; + QLabel *errorLabel = new QLabel(message); + + errorLabel->setStyleSheet("QLabel {color : red;}"); + _topLayout.addWidget(errorLabel); + + lamAddLine(); + } + + pluginList.prepend("nop"); + + _settingsToolBar.addWidget(&_importSettingsButton); + _settingsToolBar.addSeparator(); + _settingsToolBar.addWidget(&_exportSettingsButton); + _topLayout.addWidget(&_settingsToolBar); + + lamAddLine(); + + _eventsWidget.setDefault(false); + _eventsWidget.setMinimumHeight(25 * FONT_HEIGHT); + _topLayout.addWidget(&_eventsWidget); + + _pluginsLabel.adjustSize(); + _execLayout.addWidget(&_pluginsLabel, row, 0); + + _pluginsComboBox.addItems(pluginList); + _execLayout.addWidget(&_pluginsComboBox, row++, 1); + + _outputLabel.adjustSize(); + _execLayout.addWidget(&_outputLabel, row, 0); + _outputLineEdit.setFixedWidth(FONT_WIDTH * 30); + _execLayout.addWidget(&_outputLineEdit, row, 1); + _outputBrowseButton.adjustSize(); + _execLayout.addWidget(&_outputBrowseButton, row++, 2); + + _commandLabel.adjustSize(); + _commandLabel.setFixedWidth(_outputLabel.width()); + _execLayout.addWidget(&_commandLabel, row, 0); + _commandLineEdit.setFixedWidth(FONT_WIDTH * 30); + _execLayout.addWidget(&_commandLineEdit, row, 1); + _commandCheckBox.setCheckState(Qt::Unchecked); + _commandCheckBox.adjustSize(); + _execLayout.addWidget(&_commandCheckBox, row++, 2); + + _topLayout.addLayout(&_execLayout); + + lamAddLine(); + + _captureButton.setFixedWidth(STRING_WIDTH("_Capture_") + FONT_WIDTH * 2); + _applyButton.setFixedWidth(_captureButton.width()); + _closeButton.setFixedWidth(_captureButton.width()); + + _controlToolBar.addWidget(&_captureButton); + _controlToolBar.addWidget(&_applyButton); + _controlToolBar.addWidget(&_closeButton); + _topLayout.addWidget(&_controlToolBar); + + setLayout(&_topLayout); + + connect(&_importSettingsButton, &QPushButton::pressed, + this, &KsCaptureControl::_importSettings); + + connect(&_exportSettingsButton, &QPushButton::pressed, + this, &KsCaptureControl::_exportSettings); + + connect(&_outputBrowseButton, &QPushButton::pressed, + this, &KsCaptureControl::_browse); + + connect(&_applyButton, &QPushButton::pressed, + this, &KsCaptureControl::_apply); +} + +/** + * Use the settings of the Control panel to generate a list of command line + * arguments for trace-cmd. + */ +QStringList KsCaptureControl::getArgs() +{ + QStringList argv; + + argv << "record"; + argv << "-p" << _pluginsComboBox.currentText(); + + if (_eventsWidget.all()) { + argv << "-e" << "all"; + } else { + QVector evtIds = _eventsWidget.getCheckedIds(); + tep_event_format *event; + + for (auto const &id: evtIds) { + event = tep_find_event(_localTEP, id); + if (!event) + continue; + + argv << "-e" + QString(event->system) + + ":" + QString(event->name); + } + } + + argv << "-o" << outputFileName(); + argv << _commandLineEdit.text().split(" "); + + return argv; +} + +QStringList KsCaptureControl::_getPlugins() +{ + QStringList pluginList; + char **all_plugins; + + all_plugins = tracecmd_local_plugins(tracecmd_get_tracing_dir()); + + if (!all_plugins) + return pluginList; + + for (int i = 0; all_plugins[i]; ++i) { + /* + * TODO plugin selection here. + * printf("plugin %i %s\n", i, all_plugins[i]); + */ + pluginList << all_plugins[i]; + free(all_plugins[i]); + } + + free (all_plugins); + qSort(pluginList); + + return pluginList; +} + +void KsCaptureControl::_importSettings() +{ + int nEvts = tep_get_events_count(_localTEP); + kshark_config_doc *conf, *jevents, *temp; + QVector v(nEvts, false); + tracecmd_filter_id *eventHash; + tep_event_format **events; + QString fileName; + + + /** Get all available events. */ + events = tep_list_events(_localTEP, TEP_EVENT_SORT_SYSTEM); + + /* Get the configuration document. */ + fileName = QFileDialog::getOpenFileName(this, + "Import from Filter", + KS_DIR, + "Kernel Shark Config files (*.json);;"); + + if (fileName.isEmpty()) + return; + + conf = kshark_open_config_file(fileName.toStdString().c_str(), + "kshark.config.record"); + if (!conf) + return; + + /* + * Load the hash table of selected events from the configuration + * document. + */ + jevents = kshark_config_alloc(KS_CONFIG_JSON); + if (!kshark_config_doc_get(conf, "Events", jevents)) + return; + + eventHash = tracecmd_filter_id_hash_alloc(); + kshark_import_event_filter(_localTEP, eventHash, "Events", jevents); + for (int i = 0; i < nEvts; ++i) { + if (tracecmd_filter_id_find(eventHash, events[i]->id)) + v[i] = true; + } + + _eventsWidget.set(v); + tracecmd_filter_id_hash_free(eventHash); + + /** Get all available plugins. */ + temp = kshark_string_config_alloc(); + + if (kshark_config_doc_get(conf, "Plugin", temp)) + _pluginsComboBox.setCurrentText(KS_C_STR_CAST(temp->conf_doc)); + + if (kshark_config_doc_get(conf, "Output", temp)) + _outputLineEdit.setText(KS_C_STR_CAST(temp->conf_doc)); + + if (kshark_config_doc_get(conf, "Command", temp)) + _commandLineEdit.setText(KS_C_STR_CAST(temp->conf_doc)); +} + +void KsCaptureControl::_exportSettings() +{ + kshark_config_doc *conf, *events; + json_object *jplugin; + QString plugin, out, comm; + QVector ids; + QString fileName = + QFileDialog::getSaveFileName(this, + "Export to File", + KS_DIR, + "Kernel Shark Config files (*.json);;"); + + if (fileName.isEmpty()) + return; + + if (!fileName.endsWith(".json")) { + fileName += ".json"; + if (QFileInfo(fileName).exists()) { + if (!KsWidgetsLib::fileExistsDialog(fileName)) + return; + } + } + + /* Create a configuration document. */ + conf = kshark_record_config_new(KS_CONFIG_JSON); + events = kshark_filter_config_new(KS_CONFIG_JSON); + + /* + * Use the tracecmd_filter_id to save all selected events in the + * configuration file. + */ + ids = _eventsWidget.getCheckedIds(); + tracecmd_filter_id *eventHash = tracecmd_filter_id_hash_alloc(); + for (auto const &id: ids) + tracecmd_filter_id_add(eventHash, id); + + kshark_export_event_filter(_localTEP, eventHash, "Events", events); + kshark_config_doc_add(conf, "Events", events); + + tracecmd_filter_id_hash_free(eventHash); + + /* Save the plugin. */ + plugin = _pluginsComboBox.currentText(); + jplugin = json_object_new_string(plugin.toStdString().c_str()); + kshark_config_doc_add(conf, "Plugin", kshark_json_to_conf(jplugin)); + + /* Save the output file. */ + out = outputFileName(); + json_object *jout = json_object_new_string(out.toStdString().c_str()); + kshark_config_doc_add(conf, "Output", kshark_json_to_conf(jout)); + + /* Save the command. */ + comm = _commandLineEdit.text(); + json_object *jcomm = json_object_new_string(comm.toStdString().c_str()); + kshark_config_doc_add(conf, "Command", kshark_json_to_conf(jcomm)); + + kshark_save_config_file(fileName.toStdString().c_str(), conf); +} + +void KsCaptureControl::_browse() +{ + QString fileName = + QFileDialog::getSaveFileName(this, + "Save File", + KS_DIR, + "trace-cmd files (*.dat);;All files (*)"); + + if (!fileName.isEmpty()) + _outputLineEdit.setText(fileName); +} + +void KsCaptureControl::_apply() +{ + emit argsReady(getArgs().join(" ")); +} + +/** @brief Create KsCaptureMonitor widget. */ +KsCaptureMonitor::KsCaptureMonitor(QWidget *parent) +: QWidget(parent), + _mergedChannels(false), + _argsModified(false), + _panel(this), + _name("Output display", this), + _space("max size ", this), + _readOnlyCB("read only", this), + _maxLinNumEdit(QString("%1").arg(KS_CAP_MON_MAX_LINE_NUM), this), + _consolOutput("", this) +{ + _panel.setMaximumHeight(FONT_HEIGHT * 1.75); + _panel.addWidget(&_name); + + _space.setAlignment(Qt::AlignRight); + _panel.addWidget(&_space); + + _maxLinNumEdit.setFixedWidth(FONT_WIDTH * 7); + _panel.addWidget(&_maxLinNumEdit); + _panel.addSeparator(); + _readOnlyCB.setCheckState(Qt::Checked); + _panel.addWidget(&_readOnlyCB); + _layout.addWidget(&_panel); + + _consolOutput.setStyleSheet("QLabel {background-color : white;}"); + _consolOutput.setMinimumWidth(FONT_WIDTH * 60); + _consolOutput.setMinimumHeight(FONT_HEIGHT * 10); + _consolOutput.setMaximumBlockCount(KS_CAP_MON_MAX_LINE_NUM); + + _space.setMinimumWidth(FONT_WIDTH * 50 - _name.width() - _readOnlyCB.width()); + _consolOutput.setReadOnly(true); + _layout.addWidget(&_consolOutput); + + this->setLayout(&_layout); + + connect(&_maxLinNumEdit, &QLineEdit::textChanged, + this, &KsCaptureMonitor::_maxLineNumber); + + connect(&_readOnlyCB, &QCheckBox::stateChanged, + this, &KsCaptureMonitor::_readOnly); + + connect(&_consolOutput, &QPlainTextEdit::textChanged, + this, &KsCaptureMonitor::_argVModified); + + this->show(); +} + +void KsCaptureMonitor::_maxLineNumber(const QString &test) +{ + bool ok; + int max = test.toInt(&ok); + + if (ok) + _consolOutput.setMaximumBlockCount(max); +} + +void KsCaptureMonitor::_readOnly(int state) +{ + if (state == Qt::Checked) + _consolOutput.setReadOnly(true); + else + _consolOutput.setReadOnly(false); +} + +void KsCaptureMonitor::_argsReady(const QString &args) +{ + _name.setText("Capture options:"); + _consolOutput.setPlainText(args); + _argsModified = false; +} + +void KsCaptureMonitor::_argVModified() +{ + _argsModified = true; +} + +void KsCaptureMonitor::_printAllStandardError() +{ + QProcess *_capture = (QProcess*) sender(); + + _consolOutput.moveCursor(QTextCursor::End); + _consolOutput.insertPlainText(_capture->readAllStandardError()); + _consolOutput.moveCursor(QTextCursor::End); + QCoreApplication::processEvents(); +} + +void KsCaptureMonitor::_printAllStandardOutput() +{ + QProcess *_capture = (QProcess*) sender(); + + if (!_mergedChannels) + return; + + _consolOutput.appendPlainText(_capture->readAllStandardOutput()); + QCoreApplication::processEvents(); +} + +/** + * Connect the Capture monitor widget to the signals of the recording process. + */ +void KsCaptureMonitor::connectMe(QProcess *proc, KsCaptureControl *ctrl) +{ + connect(proc, &QProcess::started, + this, &KsCaptureMonitor::_captureStarted); + + /* Using the old Signal-Slot syntax because QProcess::finished has overloads. */ + connect(proc, SIGNAL(finished(int, QProcess::ExitStatus)), + this, SLOT(_captureFinished(int, QProcess::ExitStatus))); + + connect(proc, &QProcess::readyReadStandardError, + this, &KsCaptureMonitor::_printAllStandardError); + + connect(proc, &QProcess::readyReadStandardOutput, + this, &KsCaptureMonitor::_printAllStandardOutput); + + connect(ctrl, &KsCaptureControl::argsReady, + this, &KsCaptureMonitor::_argsReady); +} + +void KsCaptureMonitor::_captureStarted() +{ + _name.setText("Terminal output:"); + _readOnlyCB.setCheckState(Qt::Checked); + + QCoreApplication::processEvents(); +} + +void KsCaptureMonitor::_captureFinished(int exit, QProcess::ExitStatus status) +{ + QProcess *_capture = (QProcess *)sender(); + + if (exit != 0 || status != QProcess::NormalExit) { + QString errMessage("Capture process failed: "); + + errMessage += _capture->errorString(); + _consolOutput.appendPlainText(errMessage); + + QCoreApplication::processEvents(); + } +} + +/** Print a message. */ +void KsCaptureMonitor::print(const QString &message) +{ + _consolOutput.appendPlainText(message); +} + +/** @brief Create KsCaptureDialog widget. */ +KsCaptureDialog::KsCaptureDialog(QWidget *parent) +: QWidget(parent), + _captureCtrl(this), + _captureMon(this), + _captureProc(this) +{ + QString captureExe(TRACECMD_BIN_DIR); + + this->setWindowTitle("Capture"); + _layout.addWidget(&_captureCtrl); + _layout.addWidget(&_captureMon); + this->setLayout(&_layout); + + connect(&_captureCtrl._commandCheckBox, &QCheckBox::stateChanged, + this, &KsCaptureDialog::_setChannelMode); + + connect(&_captureCtrl._captureButton, &QPushButton::pressed, + this, &KsCaptureDialog::_capture); + + connect(&_captureCtrl._closeButton, &QPushButton::pressed, + this, &KsCaptureDialog::close); + + captureExe += "/trace-cmd"; + _captureProc.setProgram(captureExe); + + _captureMon.connectMe(&_captureProc, &_captureCtrl); +} + +void KsCaptureDialog::_capture() +{ + QStringList argv; + int argc; + + if(_captureMon._argsModified) { + argv = _captureMon.text().split(" "); + } else { + argv = _captureCtrl.getArgs(); + } + + _captureMon.print("\n"); + _captureMon.print(QString("trace-cmd " + argv.join(" ") + "\n")); + _captureProc.setArguments(argv); + _captureProc.start(); + _captureProc.waitForFinished(); + + argc = argv.count(); + for (int i = 0; i < argc; ++i) { + if (argv[i] == "-o") { + _sendOpenReq(argv[i + 1]); + break; + } + } + + /* Reset the _argsModified flag. */ + _captureMon._argsModified = false; +} + +void KsCaptureDialog::_setChannelMode(int state) +{ + if (state > 0) { + _captureMon._mergedChannels = true; + } else { + _captureMon._mergedChannels = false; + } +} + +void KsCaptureDialog::_sendOpenReq(const QString &fileName) +{ + QLocalSocket *socket = new QLocalSocket(this); + + socket->connectToServer("KSCapture", QIODevice::WriteOnly); + if (socket->waitForConnected()) { + QByteArray block; + QDataStream out(&block, QIODevice::WriteOnly); + const QString message = fileName; + + out << quint32(message.size()); + out << message; + + socket->write(block); + socket->flush(); + socket->disconnectFromServer(); + } else { + _captureMon.print(socket->errorString()); + } +} diff --git a/kernel-shark-qt/src/KsCaptureDialog.hpp b/kernel-shark-qt/src/KsCaptureDialog.hpp new file mode 100644 index 0000000..d65f475 --- /dev/null +++ b/kernel-shark-qt/src/KsCaptureDialog.hpp @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsCaptureDialog.hpp + * @brief Dialog for trace data recording. + */ + +#ifndef _KS_CAPTURE_H +#define _KS_CAPTURE_H + +// Qt +#include + +// KernelShark +#include "KsWidgetsLib.hpp" + +/** + * The KsCaptureControl class provides a control panel for the KernelShark + * Capture dialog. + */ +class KsCaptureControl : public QWidget +{ + Q_OBJECT +public: + explicit KsCaptureControl(QWidget *parent = 0); + + QStringList getArgs(); + + /** Get the name of the tracing data output file. */ + QString outputFileName() const {return _outputLineEdit.text();} + + /** Set the name of the tracing data output file. */ + void setOutputFileName(const QString &f) {_outputLineEdit.setText(f);} + +signals: + /** This signal is emitted when the "Apply" button is pressed. */ + void argsReady(const QString &args); + +private: + tep_handle *_localTEP; + + KsEventsCheckBoxWidget _eventsWidget; + + QVBoxLayout _topLayout; + + QGridLayout _execLayout; + + QLabel _pluginsLabel, _outputLabel, _commandLabel; + + QLineEdit _outputLineEdit, _commandLineEdit; + + QToolBar _settingsToolBar, _controlToolBar; + + QComboBox _pluginsComboBox; + + QPushButton _importSettingsButton, _exportSettingsButton; + + QPushButton _outputBrowseButton; + + QStringList _getPlugins(); + + void _importSettings(); + + void _exportSettings(); + + void _browse(); + + void _apply(); + +public: + /** + * A Check box used to indicate if the output of the command needs to + * be shown by the KsCaptureMonitor widget. + */ + QCheckBox _commandCheckBox; + + /** Capture button for the control panel. */ + QPushButton _captureButton; + + /** Apply button for the control panel. */ + QPushButton _applyButton; + + /** Close button for the control panel. */ + QPushButton _closeButton; +}; + +/** + * The KsCaptureMonitor class provides a terminal-like widget for monitoring + * the tracing data recording process. + */ +class KsCaptureMonitor : public QWidget +{ + Q_OBJECT +public: + explicit KsCaptureMonitor(QWidget *parent = 0); + + /** Get the text shown by the widget. */ + QString text() const {return _consolOutput.toPlainText();} + + /** Clear the text shown by the widget. */ + void clear() {_consolOutput.clear();} + + void print(const QString &message); + + void connectMe(QProcess *proc, KsCaptureControl *ctrl); + + /** A flag indicating if the stdout and stderr channels are _merged. */ + bool _mergedChannels; + + /** + * A flag indicating, if the list of the command line arguments for trace-cmd + * has been edited by the user. + */ + bool _argsModified; + +private: + QVBoxLayout _layout; + + QToolBar _panel; + + QLabel _name, _space; + + QCheckBox _readOnlyCB; + + QLineEdit _maxLinNumEdit; + + QPlainTextEdit _consolOutput; + + void _argsReady(const QString &test); + + void _maxLineNumber(const QString &test); + + void _readOnly(int); + + void _argVModified(); + + void _captureStarted(); + + void _printAllStandardError(); + + void _printAllStandardOutput(); + +private slots: + void _captureFinished(int, QProcess::ExitStatus); +}; + +/** Default number of lines shown by the KsCaptureMonitor widget. */ +#define KS_CAP_MON_MAX_LINE_NUM 200 + +/** + * The KsCaptureDialog class provides a dialog for recording of tracing data. + */ +class KsCaptureDialog : public QWidget +{ + Q_OBJECT +public: + explicit KsCaptureDialog(QWidget *parent = 0); + + /** Set the name of the tracing data output file. */ + void setOutputFileName(const QString &f) + { + _captureCtrl.setOutputFileName(f); + } + +private: + QHBoxLayout _layout; + + KsCaptureControl _captureCtrl; + + KsCaptureMonitor _captureMon; + + QProcess _captureProc; + + void _capture(); + + void _setChannelMode(int state); + + void _sendOpenReq(const QString &fileName); +}; + +#endif // _KS_CAPTURE_H From patchwork Tue Oct 16 15:53:14 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759591 Return-Path: Received: from mail-bn3nam01on0067.outbound.protection.outlook.com ([104.47.33.67]:34715 "EHLO NAM01-BN3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726778AbeJPXoW (ORCPT ); Tue, 16 Oct 2018 19:44:22 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 15/23] kernel-shark-qt: Add kshark-record executable Date: Tue, 16 Oct 2018 15:53:14 +0000 Message-ID: <20181016155232.5257-16-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 1774 From: Yordan Karadzhov (VMware) kshark-record provides a simple GUI (dialog) used to generate command line arguments for trace-cmd and to record trace data. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 4 ++++ kernel-shark-qt/src/kshark-record.cpp | 29 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 kernel-shark-qt/src/kshark-record.cpp diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 3c9e1bf..3b47ce1 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -69,6 +69,10 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) add_executable(kernelshark kernelshark.cpp) target_link_libraries(kernelshark kshark-gui) + message(STATUS "kshark-record") + add_executable(kshark-record kshark-record.cpp) + target_link_libraries(kshark-record kshark-gui) + endif (Qt5Widgets_FOUND AND Qt5Network_FOUND) add_subdirectory(plugins) diff --git a/kernel-shark-qt/src/kshark-record.cpp b/kernel-shark-qt/src/kshark-record.cpp new file mode 100644 index 0000000..7d10469 --- /dev/null +++ b/kernel-shark-qt/src/kshark-record.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (C) 2018 VMware Inc, Yordan Karadzhov + */ + +// C +#include + +// KernelShark +#include "KsCaptureDialog.hpp" + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + KsCaptureDialog cd; + + int c; + while ((c = getopt(argc, argv, "o:")) != -1) { + switch(c) { + case 'o': + cd.setOutputFileName(QString(optarg)); + break; + } + } + + cd.show(); + return a.exec(); +} From patchwork Tue Oct 16 15:53:16 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759597 Return-Path: Received: from mail-bn3nam01on0067.outbound.protection.outlook.com ([104.47.33.67]:34715 "EHLO NAM01-BN3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727242AbeJPXoZ (ORCPT ); Tue, 16 Oct 2018 19:44:25 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 16/23] kernel-shark-qt: Instruct CMake to search for "pkexec" Date: Tue, 16 Oct 2018 15:53:16 +0000 Message-ID: <20181016155232.5257-17-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 1220 From: Yordan Karadzhov (VMware) CMake will search for "pkexec" and if it is found will add a line to the CMake-generated header file. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/build/deff.h.cmake | 3 +++ kernel-shark-qt/src/CMakeLists.txt | 2 ++ 2 files changed, 5 insertions(+) diff --git a/kernel-shark-qt/build/deff.h.cmake b/kernel-shark-qt/build/deff.h.cmake index d1a1bb7..80d624c 100644 --- a/kernel-shark-qt/build/deff.h.cmake +++ b/kernel-shark-qt/build/deff.h.cmake @@ -20,6 +20,9 @@ /** Location of the trace-cmd executable. */ #cmakedefine TRACECMD_BIN_DIR "@TRACECMD_BIN_DIR@" +/** "pkexec" executable. */ +#cmakedefine DO_AS_ROOT "@DO_AS_ROOT@" + #ifdef __cplusplus #include diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 3b47ce1..6819f86 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -77,5 +77,7 @@ endif (Qt5Widgets_FOUND AND Qt5Network_FOUND) add_subdirectory(plugins) +find_program(DO_AS_ROOT pkexec) + configure_file( ${KS_DIR}/build/deff.h.cmake ${KS_DIR}/src/KsCmakeDef.hpp) From patchwork Tue Oct 16 15:53:17 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759577 Return-Path: Received: from mail-bn3nam01on0067.outbound.protection.outlook.com ([104.47.33.67]:34715 "EHLO NAM01-BN3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727254AbeJPXo1 (ORCPT ); Tue, 16 Oct 2018 19:44:27 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" Subject: [PATCH v2 17/23] kernel-shark-qt: Add PolicyKit Configuration for kshark-record Date: Tue, 16 Oct 2018 15:53:17 +0000 Message-ID: <20181016155232.5257-18-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 1399 The PolicyKit Policy Configuration will allow the kshark-record executable to be started as Root via pkexec. Signed-off-by: Yordan Karadzhov --- .../org.freedesktop.kshark-record.policy | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 kernel-shark-qt/org.freedesktop.kshark-record.policy diff --git a/kernel-shark-qt/org.freedesktop.kshark-record.policy b/kernel-shark-qt/org.freedesktop.kshark-record.policy new file mode 100644 index 0000000..dc73817 --- /dev/null +++ b/kernel-shark-qt/org.freedesktop.kshark-record.policy @@ -0,0 +1,18 @@ + + + + + + Authentication is required to run KernelShark Record + + auth_admin + auth_admin + auth_admin + + /usr/local/bin/kshark-record + true + + + From patchwork Tue Oct 16 15:53:19 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759595 Return-Path: Received: from mail-bn3nam01on0067.outbound.protection.outlook.com ([104.47.33.67]:34715 "EHLO NAM01-BN3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726778AbeJPXo2 (ORCPT ); Tue, 16 Oct 2018 19:44:28 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 19/23] kernel-shark-qt: Add kernelshark.desktop file Date: Tue, 16 Oct 2018 15:53:19 +0000 Message-ID: <20181016155232.5257-20-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 1705 From: Yordan Karadzhov (VMware) Instruct CMake to generate kernelshark.desktop. This file is used to add the KernelShark application in the desktop menus. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/CMakeLists.txt | 3 +++ kernel-shark-qt/build/cmake_clean.sh | 1 + kernel-shark-qt/build/ks.desktop.cmake | 9 +++++++++ 3 files changed, 13 insertions(+) create mode 100644 kernel-shark-qt/build/ks.desktop.cmake diff --git a/kernel-shark-qt/CMakeLists.txt b/kernel-shark-qt/CMakeLists.txt index 0ccb61e..278241b 100644 --- a/kernel-shark-qt/CMakeLists.txt +++ b/kernel-shark-qt/CMakeLists.txt @@ -75,4 +75,7 @@ if (_DOXYGEN_DOC AND DOXYGEN_FOUND) endif () +configure_file( ${KS_DIR}/build/ks.desktop.cmake + ${KS_DIR}/kernelshark.desktop) + message("") diff --git a/kernel-shark-qt/build/cmake_clean.sh b/kernel-shark-qt/build/cmake_clean.sh index 4f984db..ea04dc0 100755 --- a/kernel-shark-qt/build/cmake_clean.sh +++ b/kernel-shark-qt/build/cmake_clean.sh @@ -6,6 +6,7 @@ rm -rf CMakeFiles/ rm -rf src/ rm -rf examples/ rm -f ../lib/* +rm ../kernelshark.desktop rm -f ../src/KsCmakeDef.hpp rm -f CMakeDoxyfile.in rm -f CMakeDoxygenDefaults.cmake diff --git a/kernel-shark-qt/build/ks.desktop.cmake b/kernel-shark-qt/build/ks.desktop.cmake new file mode 100644 index 0000000..0b947f1 --- /dev/null +++ b/kernel-shark-qt/build/ks.desktop.cmake @@ -0,0 +1,9 @@ +[Desktop Entry] +Version=@KS_VERSION_STRING@ +Type=Application +Name=Kernel Shark +GenericName=Kernel Shark +Comment= +Exec=/usr/local/bin/kernelshark +Icon=@KS_DIR@/icons/ksharkicon.png +Terminal=false From patchwork Tue Oct 16 15:53:21 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759601 Return-Path: Received: from mail-cys01nam02on0072.outbound.protection.outlook.com ([104.47.37.72]:24316 "EHLO NAM02-CY1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726986AbeJPXpD (ORCPT ); Tue, 16 Oct 2018 19:45:03 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 20/23] kernel-shark-qt: Add make install Date: Tue, 16 Oct 2018 15:53:21 +0000 Message-ID: <20181016155232.5257-21-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 3047 From: Yordan Karadzhov (VMware) Define CMake installation rules for kernel-shark-qt project. Add a script for uninstalling. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/CMakeLists.txt | 3 +++ kernel-shark-qt/build/cmake_uninstall.sh | 17 +++++++++++++++++ kernel-shark-qt/src/CMakeLists.txt | 10 ++++++++++ kernel-shark-qt/src/plugins/CMakeLists.txt | 3 +++ 4 files changed, 33 insertions(+) create mode 100755 kernel-shark-qt/build/cmake_uninstall.sh diff --git a/kernel-shark-qt/CMakeLists.txt b/kernel-shark-qt/CMakeLists.txt index 278241b..767bec9 100644 --- a/kernel-shark-qt/CMakeLists.txt +++ b/kernel-shark-qt/CMakeLists.txt @@ -46,6 +46,9 @@ if (NOT _DEBUG) endif (NOT _DEBUG) +SET(CMAKE_INSTALL_RPATH "/usr/local/lib/kshark/") +SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + include_directories(${KS_DIR}/src/ ${KS_DIR}/build/src/ ${JSONC_INCLUDE_DIR} diff --git a/kernel-shark-qt/build/cmake_uninstall.sh b/kernel-shark-qt/build/cmake_uninstall.sh new file mode 100755 index 0000000..50c163b --- /dev/null +++ b/kernel-shark-qt/build/cmake_uninstall.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +CYAN='\033[0;36m' +PURPLE='\033[0;35m' +NC='\033[0m' # No Color + +if [[ $EUID -ne 0 ]]; then + echo -e "${PURPLE}Permission denied${NC}" 1>&2 + exit 100 +fi + +if [ -e install_manifest.txt ] +then + echo -e "${CYAN}Uninstall the project...${NC}" + xargs rm -v < install_manifest.txt + rm -f install_manifest.txt +fi diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 6819f86..2592094 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -73,6 +73,16 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) add_executable(kshark-record kshark-record.cpp) target_link_libraries(kshark-record kshark-gui) + install(TARGETS kernelshark kshark-record kshark kshark-plot kshark-gui + RUNTIME DESTINATION /usr/local/bin/ + LIBRARY DESTINATION /usr/local/lib/kshark/) + + install(FILES "${KS_DIR}/kernelshark.desktop" + DESTINATION /usr/share/applications/) + + install(FILES "${KS_DIR}/org.freedesktop.kshark-record.policy" + DESTINATION /usr/share/polkit-1/actions/) + endif (Qt5Widgets_FOUND AND Qt5Network_FOUND) add_subdirectory(plugins) diff --git a/kernel-shark-qt/src/plugins/CMakeLists.txt b/kernel-shark-qt/src/plugins/CMakeLists.txt index 88fd93c..2d7251d 100644 --- a/kernel-shark-qt/src/plugins/CMakeLists.txt +++ b/kernel-shark-qt/src/plugins/CMakeLists.txt @@ -24,4 +24,7 @@ BUILD_PLUGIN(NAME sched_events list(APPEND PLUGIN_LIST "sched_events default") # This plugin will be loaded by default # list(APPEND PLUGIN_LIST "sched_events") # This plugin isn't loaded by default +install(TARGETS sched_events + LIBRARY DESTINATION /usr/local/lib/kshark/) + set(PLUGINS ${PLUGIN_LIST} PARENT_SCOPE) From patchwork Tue Oct 16 15:53:22 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759607 Return-Path: Received: from mail-cys01nam02on0072.outbound.protection.outlook.com ([104.47.37.72]:24316 "EHLO NAM02-CY1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727263AbeJPXq4 (ORCPT ); Tue, 16 Oct 2018 19:46:56 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" Subject: [PATCH v2 21/23] kernel-shark-qt: Add Record dialog to KS GUI. Date: Tue, 16 Oct 2018 15:53:22 +0000 Message-ID: <20181016155232.5257-22-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 7180 A Record dialog is added to the Tools menu of the KernelShark GUI. The Record dialog can be used only after installing the project (sudo make install). Signed-off-by: Yordan Karadzhov --- kernel-shark-qt/src/KsMainWindow.cpp | 128 +++++++++++++++++++++++++++ kernel-shark-qt/src/KsMainWindow.hpp | 22 +++++ 2 files changed, 150 insertions(+) diff --git a/kernel-shark-qt/src/KsMainWindow.cpp b/kernel-shark-qt/src/KsMainWindow.cpp index b9fb587..b3cbc3b 100644 --- a/kernel-shark-qt/src/KsMainWindow.cpp +++ b/kernel-shark-qt/src/KsMainWindow.cpp @@ -28,6 +28,7 @@ #include "libkshark.h" #include "KsCmakeDef.hpp" #include "KsMainWindow.hpp" +#include "KsCaptureDialog.hpp" #include "KsAdvFilteringDialog.hpp" /** Create KernelShark Main window. */ @@ -39,6 +40,8 @@ KsMainWindow::KsMainWindow(QWidget *parent) _graph(this), _mState(this), _plugins(this), + _capture(this), + _captureLocalServer(this), _openAction("Open", this), _restorSessionAction("Restor Last Session", this), _importSessionAction("Import Session", this), @@ -56,6 +59,7 @@ KsMainWindow::KsMainWindow(QWidget *parent) _cpuSelectAction("CPUs", this), _taskSelectAction("Tasks", this), _pluginsAction("Plugins", this), + _captureAction("Record", this), _colorAction(this), _colSlider(this), _colorPhaseSlider(Qt::Horizontal, this), @@ -67,6 +71,7 @@ KsMainWindow::KsMainWindow(QWidget *parent) setWindowTitle("Kernel Shark"); _createActions(); _createMenus(); + _initCapture(); _splitter.addWidget(&_graph); _splitter.addWidget(&_view); @@ -215,6 +220,13 @@ void KsMainWindow::_createActions() connect(&_pluginsAction, &QAction::triggered, this, &KsMainWindow::_pluginSelect); + _captureAction.setIcon(QIcon::fromTheme("media-record")); + _captureAction.setShortcut(tr("Ctrl+R")); + _captureAction.setStatusTip("Capture trace data"); + + connect(&_captureAction, &QAction::triggered, + this, &KsMainWindow::_record); + _colorPhaseSlider.setMinimum(20); _colorPhaseSlider.setMaximum(180); _colorPhaseSlider.setValue(KsPlot::Color::getRainbowFrequency() * 100); @@ -321,6 +333,7 @@ void KsMainWindow::_createMenus() /* Tools menu */ tools = menuBar()->addMenu("Tools"); tools->addAction(&_pluginsAction); + tools->addAction(&_captureAction); tools->addSeparator(); tools->addAction(&_colorAction); tools->addAction(&_fullScreenModeAction); @@ -690,6 +703,29 @@ void KsMainWindow::_pluginSelect() dialog->show(); } +void KsMainWindow::_record() +{ +#ifndef DO_AS_ROOT + + QErrorMessage *em = new QErrorMessage(this); + QString message; + + message = "Record is currently not supported."; + message += " Install \"pkexec\" and then do:
"; + message += " cd build
sudo ./cmake_uninstall.sh
"; + message += " ./cmake_clean.sh
cmake ..
make
"; + message += " sudo make install"; + + em->showMessage(message); + qCritical() << "ERROR: " << message; + + return; + +#endif + + _capture.start(); +} + void KsMainWindow::_setColorPhase(int f) { KsPlot::Color::setRainbowFrequency(f / 100.); @@ -895,6 +931,98 @@ void KsMainWindow::loadSession(const QString &fileName) _colorPhaseSlider.setValue(_session.getColorScheme() * 100); } +void KsMainWindow::_initCapture() +{ +#ifdef DO_AS_ROOT + + _capture.setProgram("kshark-su-record"); + + connect(&_capture, &QProcess::started, + this, &KsMainWindow::_captureStarted); + + /* + * Using the old Signal-Slot syntax because QProcess::finished has + * overloads. + */ + connect(&_capture, SIGNAL(finished(int, QProcess::ExitStatus)), + this, SLOT(_captureFinished(int, QProcess::ExitStatus))); + + connect(&_capture, &QProcess::errorOccurred, + this, &KsMainWindow::_captureError); + + connect(&_captureLocalServer, &QLocalServer::newConnection, + this, &KsMainWindow::_readSocket); + +#endif +} + +void KsMainWindow::_captureStarted() +{ + _captureLocalServer.listen("KSCapture"); +} + +void KsMainWindow::_captureFinished(int exit, QProcess::ExitStatus st) +{ + QProcess *capture = (QProcess *)sender(); + + _captureLocalServer.close(); + + if (exit != 0 || st != QProcess::NormalExit) { + QString message = "Capture process failed:
"; + + message += capture->errorString(); + message += "
Try doing:
sudo make install"; + + _error(message, "captureFinishedErr", false, false); + } +} + +void KsMainWindow::_captureError(QProcess::ProcessError error) +{ + QProcess *capture = (QProcess *)sender(); + QString message = "Capture process failed:
"; + + message += capture->errorString(); + message += "
Try doing:
sudo make install"; + + _error(message, "captureFinishedErr", false, false); +} + +void KsMainWindow::_readSocket() +{ + QLocalSocket *socket; + quint32 blockSize; + QString fileName; + + auto lamSocketError = [&](QString message) + { + message = "ERROR from Local Server: " + message; + _error(message, "readSocketErr", false, 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); diff --git a/kernel-shark-qt/src/KsMainWindow.hpp b/kernel-shark-qt/src/KsMainWindow.hpp index 6e3f864..0e14c80 100644 --- a/kernel-shark-qt/src/KsMainWindow.hpp +++ b/kernel-shark-qt/src/KsMainWindow.hpp @@ -14,6 +14,7 @@ // Qt #include +#include // KernelShark #include "KsTraceViewer.hpp" @@ -81,6 +82,12 @@ private: /** Plugin manager. */ KsPluginManager _plugins; + /** The process used to record trace data. */ + QProcess _capture; + + /** Local Server used for comunucation with the Capture process. */ + QLocalServer _captureLocalServer; + // File menu. QAction _openAction; @@ -119,6 +126,8 @@ private: // Tools menu. QAction _pluginsAction; + QAction _captureAction; + QWidgetAction _colorAction; QWidget _colSlider; @@ -166,6 +175,8 @@ private: void _pluginSelect(); + void _record(); + void _setColorPhase(int); void _fullScreenMode(); @@ -174,18 +185,29 @@ private: void _contents(); + void _captureStarted(); + + void _captureError(QProcess::ProcessError error); + + void _readSocket(); + void _splitterMoved(int pos, int index); void _createActions(); void _createMenus(); + void _initCapture(); + void _updateSession(); inline void _resizeEmpty() {resize(SCREEN_WIDTH * .5, FONT_HEIGHT * 3);} void _error(const QString &text, const QString &errCode, bool resize, bool unloadPlugins); + +private slots: + void _captureFinished(int, QProcess::ExitStatus); }; #endif // _KS_MAINWINDOW_H From patchwork Tue Oct 16 15:53:23 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759603 Return-Path: Received: from mail-bn3nam01on0082.outbound.protection.outlook.com ([104.47.33.82]:21632 "EHLO NAM01-BN3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726778AbeJPXph (ORCPT ); Tue, 16 Oct 2018 19:45:37 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 22/23] kernel-shark-qt: Workaround for running as Root on Wayland Date: Tue, 16 Oct 2018 15:53:23 +0000 Message-ID: <20181016155232.5257-23-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 1603 From: Yordan Karadzhov (VMware) The Record dialog requires Root privileges in order to be able to collect tracing data. However graphical applications cannot be run as root on Wayland. The problem is worked around by wrapping the kshark-record executable in a shell script which checks the XDG_SESSION_TYPE and in the case of Wayland, allows the root user to access the running X server. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/bin/kshark-su-record | 8 ++++++++ kernel-shark-qt/src/CMakeLists.txt | 3 +++ 2 files changed, 11 insertions(+) create mode 100755 kernel-shark-qt/bin/kshark-su-record diff --git a/kernel-shark-qt/bin/kshark-su-record b/kernel-shark-qt/bin/kshark-su-record new file mode 100755 index 0000000..ee839a2 --- /dev/null +++ b/kernel-shark-qt/bin/kshark-su-record @@ -0,0 +1,8 @@ +#!/bin/bash + +if [ $XDG_SESSION_TYPE = "wayland" ] +then + xhost +si:localuser:root &>/dev/null +fi + +pkexec kshark-record -o ${PWD}/trace.dat diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 2592094..ef0aa71 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -83,6 +83,9 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND) install(FILES "${KS_DIR}/org.freedesktop.kshark-record.policy" DESTINATION /usr/share/polkit-1/actions/) + install(PROGRAMS "${KS_DIR}/bin/kshark-su-record" + DESTINATION /usr/local/bin/) + endif (Qt5Widgets_FOUND AND Qt5Network_FOUND) add_subdirectory(plugins) From patchwork Tue Oct 16 15:53:24 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759605 Return-Path: Received: from mail-bn3nam01on0082.outbound.protection.outlook.com ([104.47.33.82]:21632 "EHLO NAM01-BN3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726986AbeJPXpi (ORCPT ); Tue, 16 Oct 2018 19:45:38 -0400 From: Yordan Karadzhov To: "rostedt@goodmis.org" CC: "linux-trace-devel@vger.kernel.org" , Yordan Karadzhov Subject: [PATCH v2 23/23] kernel-shark-qt: Version 0.9.0 Date: Tue, 16 Oct 2018 15:53:24 +0000 Message-ID: <20181016155232.5257-24-ykaradzhov@vmware.com> References: <20181016155232.5257-1-ykaradzhov@vmware.com> In-Reply-To: <20181016155232.5257-1-ykaradzhov@vmware.com> Content-Language: en-US MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: From: Yordan Karadzhov (VMware) Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel-shark-qt/CMakeLists.txt b/kernel-shark-qt/CMakeLists.txt index 767bec9..d92bc3d 100644 --- a/kernel-shark-qt/CMakeLists.txt +++ b/kernel-shark-qt/CMakeLists.txt @@ -5,8 +5,8 @@ cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR) project(kernel-shark-qt) set(KS_VERSION_MAJOR 0) -set(KS_VERSION_MINOR 7) -set(KS_VERSION_PATCH 1) +set(KS_VERSION_MINOR 9) +set(KS_VERSION_PATCH 0) set(KS_VERSION_STRING ${KS_VERSION_MAJOR}.${KS_VERSION_MINOR}.${KS_VERSION_PATCH}) message("\n project: Kernel Shark: (version: ${KS_VERSION_STRING})\n")