From patchwork Fri Oct 12 16:13: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: 10759541 Return-Path: Received: from mail-co1nam03on0041.outbound.protection.outlook.com ([104.47.40.41]:22067 "EHLO NAM03-CO1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1728821AbeJLXrD (ORCPT ); Fri, 12 Oct 2018 19:47:03 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH 01/10] kernel-shark-qt: Add Dual Marker for KernelShark GUI. Date: Fri, 12 Oct 2018 19:13:09 +0300 Message-Id: <20181012161318.5302-2-ykaradzhov@vmware.com> In-Reply-To: <20181012161318.5302-1-ykaradzhov@vmware.com> References: <20181012161318.5302-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 13969 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 Fri Oct 12 16:13: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: 10759545 Return-Path: Received: from mail-co1nam03on0041.outbound.protection.outlook.com ([104.47.40.41]:22067 "EHLO NAM03-CO1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1728537AbeJLXrV (ORCPT ); Fri, 12 Oct 2018 19:47:21 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH 02/10] kernel-shark-qt: Add model for showing trace data in a text format. Date: Fri, 12 Oct 2018 19:13:10 +0300 Message-Id: <20181012161318.5302-3-ykaradzhov@vmware.com> In-Reply-To: <20181012161318.5302-1-ykaradzhov@vmware.com> References: <20181012161318.5302-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 15426 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 Fri Oct 12 16:13: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: 10759543 Return-Path: Received: from mail-eopbgr680080.outbound.protection.outlook.com ([40.107.68.80]:21041 "EHLO NAM04-BN3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1728800AbeJLXrR (ORCPT ); Fri, 12 Oct 2018 19:47:17 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH 03/10] kernel-shark-qt: Add Trace Viewer widget. Date: Fri, 12 Oct 2018 19:13:11 +0300 Message-Id: <20181012161318.5302-4-ykaradzhov@vmware.com> In-Reply-To: <20181012161318.5302-1-ykaradzhov@vmware.com> References: <20181012161318.5302-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 22539 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 | 655 ++++++++++++++++++++++++++ kernel-shark-qt/src/KsTraceViewer.hpp | 144 ++++++ 3 files changed, 803 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..6533098 --- /dev/null +++ b/kernel-shark-qt/src/KsTraceViewer.cpp @@ -0,0 +1,655 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +// 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. +} + +bool notHaveCond(QString searchText, QString itemText) +{ + return !itemText.contains(searchText, Qt::CaseInsensitive); +} + +bool containsCond(QString searchText, QString itemText) +{ + return itemText.contains(searchText, Qt::CaseInsensitive); +} + +bool matchCond(QString searchText, QString itemText) +{ + if (itemText.compare(searchText, Qt::CaseInsensitive) == 0) + return true; + + return false; +} + +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..664e97e --- /dev/null +++ b/kernel-shark-qt/src/KsTraceViewer.hpp @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +#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 Fri Oct 12 16:13: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: 10759547 Return-Path: Received: from mail-eopbgr680056.outbound.protection.outlook.com ([40.107.68.56]:3297 "EHLO NAM04-BN3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1728800AbeJLXrX (ORCPT ); Fri, 12 Oct 2018 19:47:23 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH 04/10] kernel-shark-qt: Add visualization (graph) model Date: Fri, 12 Oct 2018 19:13:12 +0300 Message-Id: <20181012161318.5302-5-ykaradzhov@vmware.com> In-Reply-To: <20181012161318.5302-1-ykaradzhov@vmware.com> References: <20181012161318.5302-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 6194 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 Fri Oct 12 16:13: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: 10759549 Return-Path: Received: from mail-co1nam03on0041.outbound.protection.outlook.com ([104.47.40.41]:22067 "EHLO NAM03-CO1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1728821AbeJLXr1 (ORCPT ); Fri, 12 Oct 2018 19:47:27 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH 05/10] kernel-shark-qt: Add widget for OpenGL rendering Date: Fri, 12 Oct 2018 19:13:13 +0300 Message-Id: <20181012161318.5302-6-ykaradzhov@vmware.com> In-Reply-To: <20181012161318.5302-1-ykaradzhov@vmware.com> References: <20181012161318.5302-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 30270 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 Fri Oct 12 16:13: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: 10759551 Return-Path: Received: from mail-eopbgr680056.outbound.protection.outlook.com ([40.107.68.56]:3297 "EHLO NAM04-BN3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1728989AbeJLXr2 (ORCPT ); Fri, 12 Oct 2018 19:47:28 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH 06/10] kernel-shark-qt: Add Trace Graph widget. Date: Fri, 12 Oct 2018 19:13:14 +0300 Message-Id: <20181012161318.5302-7-ykaradzhov@vmware.com> In-Reply-To: <20181012161318.5302-1-ykaradzhov@vmware.com> References: <20181012161318.5302-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 23253 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..6994644 --- /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. + */ + +// 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..b57b256 --- /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. + */ +#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 Fri Oct 12 16:13:15 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759553 Return-Path: Received: from mail-co1nam03on0041.outbound.protection.outlook.com ([104.47.40.41]:22067 "EHLO NAM03-CO1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1728800AbeJLXrd (ORCPT ); Fri, 12 Oct 2018 19:47:33 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH 07/10] kernel-shark-qt: Add dialog for Advanced filtering. Date: Fri, 12 Oct 2018 19:13:15 +0300 Message-Id: <20181012161318.5302-8-ykaradzhov@vmware.com> In-Reply-To: <20181012161318.5302-1-ykaradzhov@vmware.com> References: <20181012161318.5302-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 14984 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 Fri Oct 12 16:13: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: 10759555 Return-Path: Received: from mail-eopbgr680056.outbound.protection.outlook.com ([40.107.68.56]:3297 "EHLO NAM04-BN3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1728821AbeJLXre (ORCPT ); Fri, 12 Oct 2018 19:47:34 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH 08/10] kernel-shark-qt: Add a manager class for GUI sessions. Date: Fri, 12 Oct 2018 19:13:16 +0300 Message-Id: <20181012161318.5302-9-ykaradzhov@vmware.com> In-Reply-To: <20181012161318.5302-1-ykaradzhov@vmware.com> References: <20181012161318.5302-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 19346 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..979bb3f --- /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 histo: Input location for the model descriptor. + */ +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 Fri Oct 12 16:13: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: 10759557 Return-Path: Received: from mail-co1nam03on0041.outbound.protection.outlook.com ([104.47.40.41]:22067 "EHLO NAM03-CO1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1728989AbeJLXri (ORCPT ); Fri, 12 Oct 2018 19:47:38 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH 09/10] kernel-shark-qt: Add Main Window widget gir the KernelShark GUI. Date: Fri, 12 Oct 2018 19:13:17 +0300 Message-Id: <20181012161318.5302-10-ykaradzhov@vmware.com> In-Reply-To: <20181012161318.5302-1-ykaradzhov@vmware.com> References: <20181012161318.5302-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 29133 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..38e47e6 --- /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 KsSession.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..cee68db --- /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 KsSession.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 Fri Oct 12 16:13:18 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759559 Return-Path: Received: from mail-eopbgr680056.outbound.protection.outlook.com ([40.107.68.56]:3297 "EHLO NAM04-BN3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1728800AbeJLXrj (ORCPT ); Fri, 12 Oct 2018 19:47:39 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH 10/10] kernel-shark-qt: Add KernelShark GUI executable. Date: Fri, 12 Oct 2018 19:13:18 +0300 Message-Id: <20181012161318.5302-11-ykaradzhov@vmware.com> In-Reply-To: <20181012161318.5302-1-ykaradzhov@vmware.com> References: <20181012161318.5302-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 3012 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(); +}