linux-trace-devel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/23]  Add Qt-based GUI for KernelShark
@ 2018-10-16 15:52 Yordan Karadzhov
  2018-10-16 15:52 ` [PATCH v2 01/23] kernel-shark-qt: Fix a simple bug in KsDataStore::_freeData() Yordan Karadzhov
                   ` (21 more replies)
  0 siblings, 22 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:52 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel

In this series of patches the major components of the Qt-based
KernelShark GUI are introduced one by one in a sequence which follows
there internal dependence. The series ends with the fully functional
KernelShark GUI application.


Yordan Karadzhov (5):
  kernel-shark-qt: Fix a simple bug in KsDataStore::_freeData()
  kernel-shark-qt: Add "File exists" dialog.
  kernel-shark-qt: Fix the glitches in the preemption time visualization
  kernel-shark-qt: Add PolicyKit Configuration for kshark-record
  kernel-shark-qt: Add Record dialog to KS GUI.

Yordan Karadzhov (VMware) (18):
  kernel-shark-qt: Add Dual Marker for KernelShark GUI.
  kernel-shark-qt: Add model for showing trace data in a text format.
  kernel-shark-qt: Add Trace Viewer widget.
  kernel-shark-qt: Add visualization (graph) model
  kernel-shark-qt: Add widget for OpenGL rendering
  kernel-shark-qt: Add Trace Graph widget.
  kernel-shark-qt: Add dialog for Advanced filtering.
  kernel-shark-qt: Add a manager class for GUI sessions.
  kernel-shark-qt: Add Main Window widget for the KernelShark GUI.
  kernel-shark-qt: Add KernelShark GUI executable.
  kernel-shark-qt: Add dialog for of trace data recording
  kernel-shark-qt: Add kshark-record executable
  kernel-shark-qt: Instruct CMake to search for "pkexec"
  kernel-shark-qt: Add KernelShark icon
  kernel-shark-qt: Add kernelshark.desktop file
  kernel-shark-qt: Add make install
  kernel-shark-qt: Workaround for running as Root on Wayland
  kernel-shark-qt: Version 0.9.0

 kernel-shark-qt/CMakeLists.txt                |   15 +-
 kernel-shark-qt/bin/kshark-su-record          |    8 +
 kernel-shark-qt/build/cmake_clean.sh          |    1 +
 kernel-shark-qt/build/cmake_uninstall.sh      |   17 +
 kernel-shark-qt/build/deff.h.cmake            |    3 +
 kernel-shark-qt/build/ks.desktop.cmake        |    9 +
 kernel-shark-qt/icons/ksharkicon.png          |  Bin 0 -> 80474 bytes
 .../org.freedesktop.kshark-record.policy      |   18 +
 kernel-shark-qt/src/CMakeLists.txt            |   44 +-
 kernel-shark-qt/src/KsAdvFilteringDialog.cpp  |  440 +++++++
 kernel-shark-qt/src/KsAdvFilteringDialog.hpp  |   91 ++
 kernel-shark-qt/src/KsCaptureDialog.cpp       |  562 +++++++++
 kernel-shark-qt/src/KsCaptureDialog.hpp       |  185 +++
 kernel-shark-qt/src/KsDualMarker.cpp          |  336 ++++++
 kernel-shark-qt/src/KsDualMarker.hpp          |  190 +++
 kernel-shark-qt/src/KsGLWidget.cpp            |  913 +++++++++++++++
 kernel-shark-qt/src/KsGLWidget.hpp            |  220 ++++
 kernel-shark-qt/src/KsMainWindow.cpp          | 1029 +++++++++++++++++
 kernel-shark-qt/src/KsMainWindow.hpp          |  213 ++++
 kernel-shark-qt/src/KsModels.cpp              |  485 ++++++++
 kernel-shark-qt/src/KsModels.hpp              |  289 +++++
 kernel-shark-qt/src/KsSession.cpp             |  574 +++++++++
 kernel-shark-qt/src/KsSession.hpp             |  100 ++
 kernel-shark-qt/src/KsTraceGraph.cpp          |  690 +++++++++++
 kernel-shark-qt/src/KsTraceGraph.hpp          |  137 +++
 kernel-shark-qt/src/KsTraceViewer.cpp         |  657 +++++++++++
 kernel-shark-qt/src/KsTraceViewer.hpp         |  149 +++
 kernel-shark-qt/src/KsUtils.cpp               |    2 +
 kernel-shark-qt/src/KsWidgetsLib.cpp          |   29 +
 kernel-shark-qt/src/KsWidgetsLib.hpp          |    7 +
 kernel-shark-qt/src/kernelshark.cpp           |   93 ++
 kernel-shark-qt/src/kshark-record.cpp         |   29 +
 kernel-shark-qt/src/plugins/CMakeLists.txt    |    3 +
 kernel-shark-qt/src/plugins/SchedEvents.cpp   |  126 +-
 kernel-shark-qt/src/plugins/sched_events.c    |   13 +-
 35 files changed, 7628 insertions(+), 49 deletions(-)
 create mode 100755 kernel-shark-qt/bin/kshark-su-record
 create mode 100755 kernel-shark-qt/build/cmake_uninstall.sh
 create mode 100644 kernel-shark-qt/build/ks.desktop.cmake
 create mode 100644 kernel-shark-qt/icons/ksharkicon.png
 create mode 100644 kernel-shark-qt/org.freedesktop.kshark-record.policy
 create mode 100644 kernel-shark-qt/src/KsAdvFilteringDialog.cpp
 create mode 100644 kernel-shark-qt/src/KsAdvFilteringDialog.hpp
 create mode 100644 kernel-shark-qt/src/KsCaptureDialog.cpp
 create mode 100644 kernel-shark-qt/src/KsCaptureDialog.hpp
 create mode 100644 kernel-shark-qt/src/KsDualMarker.cpp
 create mode 100644 kernel-shark-qt/src/KsDualMarker.hpp
 create mode 100644 kernel-shark-qt/src/KsGLWidget.cpp
 create mode 100644 kernel-shark-qt/src/KsGLWidget.hpp
 create mode 100644 kernel-shark-qt/src/KsMainWindow.cpp
 create mode 100644 kernel-shark-qt/src/KsMainWindow.hpp
 create mode 100644 kernel-shark-qt/src/KsModels.cpp
 create mode 100644 kernel-shark-qt/src/KsModels.hpp
 create mode 100644 kernel-shark-qt/src/KsSession.cpp
 create mode 100644 kernel-shark-qt/src/KsSession.hpp
 create mode 100644 kernel-shark-qt/src/KsTraceGraph.cpp
 create mode 100644 kernel-shark-qt/src/KsTraceGraph.hpp
 create mode 100644 kernel-shark-qt/src/KsTraceViewer.cpp
 create mode 100644 kernel-shark-qt/src/KsTraceViewer.hpp
 create mode 100644 kernel-shark-qt/src/kernelshark.cpp
 create mode 100644 kernel-shark-qt/src/kshark-record.cpp

-- 
2.17.1

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH v2 01/23] kernel-shark-qt: Fix a simple bug in KsDataStore::_freeData()
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
@ 2018-10-16 15:52 ` Yordan Karadzhov
  2018-10-16 15:52 ` [PATCH v2 02/23] kernel-shark-qt: Add Dual Marker for KernelShark GUI Yordan Karadzhov
                   ` (20 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:52 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel

After freeing the memory used to store the tracing data, this
function has to set the _dataSize field to zero.

Signed-off-by: Yordan Karadzhov <ykaradzhov@vmware.com>
---
 kernel-shark-qt/src/KsUtils.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/kernel-shark-qt/src/KsUtils.cpp b/kernel-shark-qt/src/KsUtils.cpp
index 13b648e..5e4c9c8 100644
--- a/kernel-shark-qt/src/KsUtils.cpp
+++ b/kernel-shark-qt/src/KsUtils.cpp
@@ -144,6 +144,8 @@ void KsDataStore::_freeData()
 		free(_rows);
 		_rows = nullptr;
 	}
+
+	_dataSize = 0;
 }
 
 /** Reload the trace data. */
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 02/23] kernel-shark-qt: Add Dual Marker for KernelShark GUI.
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
  2018-10-16 15:52 ` [PATCH v2 01/23] kernel-shark-qt: Fix a simple bug in KsDataStore::_freeData() Yordan Karadzhov
@ 2018-10-16 15:52 ` Yordan Karadzhov
  2018-10-19  2:03   ` Steven Rostedt
  2018-10-19  2:05   ` Steven Rostedt
  2018-10-16 15:52 ` [PATCH v2 03/23] kernel-shark-qt: Add model for showing trace data in a text format Yordan Karadzhov
                   ` (19 subsequent siblings)
  21 siblings, 2 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:52 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

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) <y.karadz@gmail.com>
---
 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 <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @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 <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsDualMarker.hpp
+ *  @brief   KernelShark Dual Marker.
+ */
+
+#ifndef _KS_DUAL_MARKER_H
+#define _KS_DUAL_MARKER_H
+
+// Qt
+#include <QtWidgets>
+
+// 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
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 03/23] kernel-shark-qt: Add model for showing trace data in a text format.
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
  2018-10-16 15:52 ` [PATCH v2 01/23] kernel-shark-qt: Fix a simple bug in KsDataStore::_freeData() Yordan Karadzhov
  2018-10-16 15:52 ` [PATCH v2 02/23] kernel-shark-qt: Add Dual Marker for KernelShark GUI Yordan Karadzhov
@ 2018-10-16 15:52 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 04/23] kernel-shark-qt: Add Trace Viewer widget Yordan Karadzhov
                   ` (18 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:52 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

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) <y.karadz@gmail.com>
---
 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 <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @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<int> *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<std::mutex> 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<int> *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<int> KsFilterProxyModel::searchMap(int column,
+					 const QString &searchText,
+					 condition_func cond,
+					 int first,
+					 int last,
+					 bool notify)
+{
+	QList<int> 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<size_t> *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 <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsModels.hpp
+ *  @brief   Models for data representation.
+ */
+
+#ifndef _KS_MODELS_H
+#define _KS_MODELS_H
+
+// C++11
+#include <mutex>
+#include <condition_variable>
+
+// Qt
+#include <QAbstractTableModel>
+#include <QSortFilterProxyModel>
+#include <QProgressBar>
+#include <QLabel>
+#include <QColor>
+
+// 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<size_t> *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<int> *matchList,
+		      QProgressBar *pb = nullptr,
+		      QLabel *l = nullptr);
+
+	QList<int> 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<int> *matchList,
+		     int first, int last,
+		     QProgressBar *pb,
+		     QLabel *l,
+		     bool notify);
+};
+
+#endif // _KS_MODELS_H
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 04/23] kernel-shark-qt: Add Trace Viewer widget.
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (2 preceding siblings ...)
  2018-10-16 15:52 ` [PATCH v2 03/23] kernel-shark-qt: Add model for showing trace data in a text format Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-19  2:20   ` Steven Rostedt
  2018-10-19  2:24   ` Steven Rostedt
  2018-10-16 15:53 ` [PATCH v2 05/23] kernel-shark-qt: Add visualization (graph) model Yordan Karadzhov
                   ` (17 subsequent siblings)
  21 siblings, 2 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

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) <y.karadz@gmail.com>
---
 kernel-shark-qt/src/CMakeLists.txt    |   6 +-
 kernel-shark-qt/src/KsTraceViewer.cpp | 657 ++++++++++++++++++++++++++
 kernel-shark-qt/src/KsTraceViewer.hpp | 149 ++++++
 3 files changed, 810 insertions(+), 2 deletions(-)
 create mode 100644 kernel-shark-qt/src/KsTraceViewer.cpp
 create mode 100644 kernel-shark-qt/src/KsTraceViewer.hpp

diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
index 3fd518b..3f40930 100644
--- a/kernel-shark-qt/src/CMakeLists.txt
+++ b/kernel-shark-qt/src/CMakeLists.txt
@@ -34,14 +34,16 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
     set (ks-guiLib_hdr  KsUtils.hpp
                         KsModels.hpp
                         KsDualMarker.hpp
-                        KsWidgetsLib.hpp)
+                        KsWidgetsLib.hpp
+                        KsTraceViewer.hpp)
 
     QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr})
 
     add_library(kshark-gui  SHARED  ${ks-guiLib_hdr_moc}    KsUtils.cpp
                                                             KsModels.cpp
                                                             KsDualMarker.cpp
-                                                            KsWidgetsLib.cpp)
+                                                            KsWidgetsLib.cpp
+                                                            KsTraceViewer.cpp)
 
     target_link_libraries(kshark-gui kshark-plot
                                      ${CMAKE_DL_LIBS}
diff --git a/kernel-shark-qt/src/KsTraceViewer.cpp b/kernel-shark-qt/src/KsTraceViewer.cpp
new file mode 100644
index 0000000..7f12365
--- /dev/null
+++ b/kernel-shark-qt/src/KsTraceViewer.cpp
@@ -0,0 +1,657 @@
+// SPDX-License-Identifier: LGPL-2.1
+
+/*
+ * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsTraceViewer.cpp
+ *  @brief   KernelShark Trace Viewer widget.
+ */
+
+// C++11
+#include <thread>
+#include <future>
+
+// KernelShark
+#include "KsTraceViewer.hpp"
+#include "KsWidgetsLib.hpp"
+
+/** Create a default (empty) Trace viewer widget. */
+KsTraceViewer::KsTraceViewer(QWidget *parent)
+: QWidget(parent),
+  _view(this),
+  _model(this),
+  _proxyModel(this),
+  _tableHeader(_model.header()),
+  _toolbar(this),
+  _labelSearch("Search: Column", this),
+  _labelGrFollows("Graph follows  ", this),
+  _columnComboBox(this),
+  _selectComboBox(this),
+  _searchLineEdit(this),
+  _prevButton("Prev", this),
+  _nextButton("Next", this),
+  _searchStopButton(QIcon::fromTheme("process-stop"), "", this),
+  _pbAction(nullptr),
+  _graphFollowsCheckBox(this),
+  _searchProgBar(this),
+  _searchCountLabel("", this),
+  _searchDone(false),
+  _graphFollows(true),
+  _mState(nullptr),
+  _data(nullptr)
+{
+	int bWidth;
+
+	this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
+
+	/* Make a search toolbar. */
+	_toolbar.setOrientation(Qt::Horizontal);
+	_toolbar.setMaximumHeight(FONT_HEIGHT * 1.75);
+
+	/* On the toolbar make two Combo boxes for the search settings. */
+	_toolbar.addWidget(&_labelSearch);
+	_columnComboBox.addItems(_tableHeader);
+
+	/*
+	 * Using the old Signal-Slot syntax because
+	 * QComboBox::currentIndexChanged has overloads.
+	 */
+	connect(&_columnComboBox,	SIGNAL(currentIndexChanged(int)),
+		this,			SLOT(_searchEdit(int)));
+
+	_toolbar.addWidget(&_columnComboBox);
+
+	_selectComboBox.addItem("contains");
+	_selectComboBox.addItem("full match");
+	_selectComboBox.addItem("does not have");
+
+	/*
+	 * Using the old Signal-Slot syntax because
+	 * QComboBox::currentIndexChanged has overloads.
+	 */
+	connect(&_selectComboBox,	SIGNAL(currentIndexChanged(int)),
+		this,			SLOT(_searchEdit(int)));
+
+	_toolbar.addWidget(&_selectComboBox);
+
+	/* On the toolbar, make a Line edit field for search. */
+	_searchLineEdit.setMaximumWidth(FONT_WIDTH * 20);
+
+	connect(&_searchLineEdit,	&QLineEdit::returnPressed,
+		this,			&KsTraceViewer::_search);
+
+	connect(&_searchLineEdit,	&QLineEdit::textEdited,
+		this,			&KsTraceViewer::_searchEditText);
+
+	_toolbar.addWidget(&_searchLineEdit);
+	_toolbar.addSeparator();
+
+	/* On the toolbar, add Prev & Next buttons. */
+	bWidth = FONT_WIDTH * 6;
+
+	_nextButton.setFixedWidth(bWidth);
+	_toolbar.addWidget(&_nextButton);
+	connect(&_nextButton,	&QPushButton::pressed,
+		this,		&KsTraceViewer::_next);
+
+	_prevButton.setFixedWidth(bWidth);
+	_toolbar.addWidget(&_prevButton);
+	connect(&_prevButton,	&QPushButton::pressed,
+		this,		&KsTraceViewer::_prev);
+
+	_toolbar.addSeparator();
+	_searchProgBar.setMaximumWidth(FONT_WIDTH * 10);
+	_searchProgBar.setRange(0, 200);
+	_pbAction = _toolbar.addWidget(&_searchProgBar);
+	_pbAction->setVisible(false);
+	_toolbar.addWidget(&_searchCountLabel);
+	_searchStopAction = _toolbar.addWidget(&_searchStopButton);
+	_searchStopAction->setVisible(false);
+	connect(&_searchStopButton,	&QPushButton::pressed,
+		this,			&KsTraceViewer::_searchStop);
+
+	/*
+	 * On the toolbar, make a Check box for connecting the search pannel
+	 * to the Graph widget.
+	 */
+	_toolbar.addSeparator();
+	_toolbar.addWidget(&_graphFollowsCheckBox);
+	_toolbar.addWidget(&_labelGrFollows);
+	_graphFollowsCheckBox.setCheckState(Qt::Checked);
+	connect(&_graphFollowsCheckBox,	&QCheckBox::stateChanged,
+		this,			&KsTraceViewer::_graphFollowsChanged);
+
+	/* Initialize the trace viewer. */
+	_view.horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
+	_view.verticalHeader()->setVisible(false);
+	_view.setEditTriggers(QAbstractItemView::NoEditTriggers);
+	_view.setSelectionBehavior(QAbstractItemView::SelectRows);
+	_view.setSelectionMode(QAbstractItemView::SingleSelection);
+	_view.verticalHeader()->setDefaultSectionSize(FONT_HEIGHT * 1.25);
+
+	 _proxyModel.setSource(&_model);
+	_view.setModel(&_proxyModel);
+	connect(&_proxyModel, &QAbstractItemModel::modelReset,
+		this, &KsTraceViewer::_searchReset);
+
+	_view.setContextMenuPolicy(Qt::CustomContextMenu);
+	connect(&_view,	&QTableView::customContextMenuRequested,
+		this,	&KsTraceViewer::_onCustomContextMenu);
+
+	connect(&_view,	&QTableView::clicked,
+		this,	&KsTraceViewer::_clicked);
+
+	/* Set the layout. */
+	_layout.addWidget(&_toolbar);
+	_layout.addWidget(&_view);
+	this->setLayout(&_layout);
+}
+
+/**
+ * @brief Load and show trace data.
+ *
+ * @param data: Input location for the KsDataStore object.
+ *	  KsDataStore::loadDataFile() must be called first.
+ */
+void KsTraceViewer::loadData(KsDataStore *data)
+{
+	_data = data;
+	_model.reset();
+	_proxyModel.fill(data);
+	_model.fill(data);
+	this->_resizeToContents();
+
+	this->setMinimumHeight(SCREEN_HEIGHT / 5);
+}
+
+/** Connect the QTableView widget and the State machine of the Dual marker. */
+void KsTraceViewer::setMarkerSM(KsDualMarkerSM *m)
+{
+	QString styleSheetA, styleSheetB;
+
+	_mState = m;
+	_model.setColors(_mState->markerA()._color,
+			 _mState->markerB()._color);
+
+	/*
+	 * Assign a property to State A of the Dual marker state machine. When
+	 * the marker is in State A the background color of the selected row
+	 * will be the same as the color of Marker A.
+	 */
+	styleSheetA = "selection-background-color : " +
+		      _mState->markerA()._color.name() + ";";
+
+	_mState->stateAPtr()->assignProperty(&_view, "styleSheet",
+						     styleSheetA);
+
+	/*
+	 * Assign a property to State B. When the marker is in State B the
+	 * background color of the selected row will be the same as the color
+	 * of Marker B.
+	 */
+	styleSheetB = "selection-background-color : " +
+		      _mState->markerB()._color.name() + ";";
+
+	_mState->stateBPtr()->assignProperty(&_view, "styleSheet",
+						     styleSheetB);
+}
+
+/** Reset (empty) the table. */
+void KsTraceViewer::reset()
+{
+	this->setMinimumHeight(FONT_HEIGHT * 10);
+	_model.reset();
+	_resizeToContents();
+}
+
+void KsTraceViewer::_searchReset()
+{
+	_searchProgBar.setValue(0);
+	_searchCountLabel.setText("");
+	_proxyModel.searchProgress();
+	_searchDone = false;
+}
+
+/** Get the index of the first (top) visible row. */
+size_t  KsTraceViewer::getTopRow() const
+{
+	return _view.indexAt(_view.rect().topLeft()).row();
+}
+
+/** Position given row at the top of the table. */
+void  KsTraceViewer::setTopRow(size_t r)
+{
+	_view.scrollTo(_proxyModel.index(r, 0),
+		       QAbstractItemView::PositionAtTop);
+}
+
+/** Update the content of the table. */
+void KsTraceViewer::update(KsDataStore *data)
+{
+	/* The Proxy model has to be updated first! */
+	_proxyModel.fill(data);
+	_model.update(data);
+	_data = data;
+	if (_mState->activeMarker()._isSet)
+		showRow(_mState->activeMarker()._pos, true);
+}
+
+void KsTraceViewer::_onCustomContextMenu(const QPoint &point)
+{
+	QModelIndex i = _view.indexAt(point);
+
+	if (i.isValid()) {
+		/*
+		 * Use the index of the proxy model to retrieve the value
+		 * of the row number in the source model.
+		 */
+		size_t row = _proxyModel.mapRowFromSource(i.row());
+		KsQuickEntryMenu menu(_data, row, this);
+
+		connect(&menu,	&KsQuickEntryMenu::plotTask,
+			this,	&KsTraceViewer::plotTask);
+
+		menu.exec(mapToGlobal(point));
+	}
+}
+
+void KsTraceViewer::_searchEdit(int index)
+{
+	_searchReset(); // The search has been modified.
+}
+
+void KsTraceViewer::_searchEditText(const QString &text)
+{
+	_searchReset(); // The search has been modified.
+}
+
+void KsTraceViewer::_graphFollowsChanged(int state)
+{
+	_graphFollows = (bool) state;
+
+	if (_graphFollows && _searchDone)
+		emit select(*_it); // Send a signal to the Graph widget.
+}
+
+static bool notHaveCond(QString searchText, QString itemText)
+{
+	return !itemText.contains(searchText, Qt::CaseInsensitive);
+}
+
+static bool containsCond(QString searchText, QString itemText)
+{
+	return itemText.contains(searchText, Qt::CaseInsensitive);
+}
+
+static bool matchCond(QString searchText, QString itemText)
+{
+	return (itemText.compare(searchText, Qt::CaseInsensitive) == 0);
+}
+
+void KsTraceViewer::_search()
+{
+	/* Disable the user input until the search is done. */
+	_searchLineEdit.setReadOnly(true);
+	if (!_searchDone) {
+		int xColumn, xSelect;
+		QString xText;
+
+		/*
+		 * The search is not done. This means that the search settings
+		 * have been modified since the last time we searched.
+		 */
+		_matchList.clear();
+		xText = _searchLineEdit.text();
+		xColumn = _columnComboBox.currentIndex();
+		xSelect = _selectComboBox.currentIndex();
+
+		switch (xSelect) {
+			case Condition::Containes:
+				_searchItems(xColumn, xText, &containsCond);
+				break;
+
+			case Condition::Match:
+				_searchItems(xColumn, xText, &matchCond);
+				break;
+
+			case Condition::NotHave:
+				_searchItems(xColumn, xText, &notHaveCond);
+				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<QPair<int, int>> ranges(nThreads);
+	std::vector<std::future<QList<int>>> maps;
+	int i(0), nRows(_proxyModel.rowCount({}));
+	int delta(nRows / nThreads);
+
+	auto lamSearchMap = [&] (const QPair<int, int> &range,
+				 bool notify) {
+		return _proxyModel.searchMap(column, searchText, cond,
+					     range.first, range.second,
+					     notify);
+	};
+
+	auto lamSearchReduce = [&] (QList<int> &resultList,
+				  const QList<int> &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<std::mutex> lk(_proxyModel._mutex);
+		_proxyModel._pbCond.wait(lk);
+		_searchProgBar.setValue(_proxyModel.searchProgress());
+		QApplication::processEvents();
+	}
+
+	for (auto &m: maps)
+		lamSearchReduce(_matchList, m.get());
+}
diff --git a/kernel-shark-qt/src/KsTraceViewer.hpp b/kernel-shark-qt/src/KsTraceViewer.hpp
new file mode 100644
index 0000000..2a85e4f
--- /dev/null
+++ b/kernel-shark-qt/src/KsTraceViewer.hpp
@@ -0,0 +1,149 @@
+/* SPDX-License-Identifier: LGPL-2.1 */
+
+/*
+ * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsTraceViewer.hpp
+ *  @brief   KernelShark Trace Viewer widget.
+ */
+
+#ifndef _KS_TRACEVIEW_H
+#define _KS_TRACEVIEW_H
+
+// Qt
+#include <QTableView>
+
+// 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<int>		_matchList;
+
+	QList<int>::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
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 05/23] kernel-shark-qt: Add visualization (graph) model
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (3 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 04/23] kernel-shark-qt: Add Trace Viewer widget Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 06/23] kernel-shark-qt: Add widget for OpenGL rendering Yordan Karadzhov
                   ` (16 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

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) <y.karadz@gmail.com>
---
 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
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 06/23] kernel-shark-qt: Add widget for OpenGL rendering
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (4 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 05/23] kernel-shark-qt: Add visualization (graph) model Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-19  2:33   ` Steven Rostedt
  2018-10-16 15:53 ` [PATCH v2 07/23] kernel-shark-qt: Add Trace Graph widget Yordan Karadzhov
                   ` (15 subsequent siblings)
  21 siblings, 1 reply; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

This patch defines the widget for rendering OpenGL graphics used
to plot trace graphs.

Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
---
 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 <ykaradzhov@vmware.com>
+ */
+
+ /**
+ *  @file    KsGLWidget.cpp
+ *  @brief   OpenGL widget for plotting trace graphs.
+ */
+
+// OpenGL
+#include <GL/glut.h>
+#include <GL/gl.h>
+
+// 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<int> cpuList, QVector<int> 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<int> cpuList, QVector<int> 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 <ykaradzhov@vmware.com>
+ */
+
+ /**
+ *  @file    KsGLWidget.hpp
+ *  @brief   OpenGL widget for plotting trace graphs.
+ */
+
+#ifndef _KS_GLWIDGET_H
+#define _KS_GLWIDGET_H
+
+// Qt
+#include <QRubberBand>
+
+// 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<int>	_cpuList;
+
+	/** Tasks to be plotted. */
+	QVector<int>	_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<KsPlot::Graph*>	_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<int> cpuMask, QVector<int> taskMask);
+
+	KsPlot::Graph *_newCPUGraph(int cpu);
+
+	KsPlot::Graph *_newTaskGraph(int pid);
+
+	void _makePluginShapes(QVector<int> cpuMask, QVector<int> 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
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 07/23] kernel-shark-qt: Add Trace Graph widget.
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (5 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 06/23] kernel-shark-qt: Add widget for OpenGL rendering Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-19  2:38   ` Steven Rostedt
  2018-10-16 15:53 ` [PATCH v2 08/23] kernel-shark-qt: Add dialog for Advanced filtering Yordan Karadzhov
                   ` (14 subsequent siblings)
  21 siblings, 1 reply; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

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) <y.karadz@gmail.com>
---
 kernel-shark-qt/src/CMakeLists.txt   |   2 +
 kernel-shark-qt/src/KsTraceGraph.cpp | 690 +++++++++++++++++++++++++++
 kernel-shark-qt/src/KsTraceGraph.hpp | 137 ++++++
 3 files changed, 829 insertions(+)
 create mode 100644 kernel-shark-qt/src/KsTraceGraph.cpp
 create mode 100644 kernel-shark-qt/src/KsTraceGraph.hpp

diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
index 2ca5187..d406866 100644
--- a/kernel-shark-qt/src/CMakeLists.txt
+++ b/kernel-shark-qt/src/CMakeLists.txt
@@ -36,6 +36,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
                         KsGLWidget.hpp
                         KsDualMarker.hpp
                         KsWidgetsLib.hpp
+                        KsTraceGraph.hpp
                         KsTraceViewer.hpp)
 
     QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr})
@@ -45,6 +46,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
                                                             KsGLWidget.cpp
                                                             KsDualMarker.cpp
                                                             KsWidgetsLib.cpp
+                                                            KsTraceGraph.cpp
                                                             KsTraceViewer.cpp)
 
     target_link_libraries(kshark-gui kshark-plot
diff --git a/kernel-shark-qt/src/KsTraceGraph.cpp b/kernel-shark-qt/src/KsTraceGraph.cpp
new file mode 100644
index 0000000..21a09d0
--- /dev/null
+++ b/kernel-shark-qt/src/KsTraceGraph.cpp
@@ -0,0 +1,690 @@
+// SPDX-License-Identifier: LGPL-2.1
+
+/*
+ * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsTraceGraph.cpp
+ *  @brief   KernelShark Trace Graph widget.
+ */
+
+// KernelShark
+#include "KsUtils.hpp"
+#include "KsDualMarker.hpp"
+#include "KsTraceGraph.hpp"
+
+/** Create a default (empty) Trace graph widget. */
+KsTraceGraph::KsTraceGraph(QWidget *parent)
+: QWidget(parent),
+  _pointerBar(this),
+  _navigationBar(this),
+  _zoomInButton("+", this),
+  _quickZoomInButton("++", this),
+  _zoomOutButton("-", this),
+  _quickZoomOutButton("- -", this),
+  _scrollLeftButton("<", this),
+  _scrollRightButton(">", this),
+  _labelP1("Pointer: ", this),
+  _labelP2("", this),
+  _labelI1("", this),
+  _labelI2("", this),
+  _labelI3("", this),
+  _labelI4("", this),
+  _labelI5("", this),
+  _scrollArea(this),
+  _drawWindow(&_scrollArea),
+  _legendWindow(&_drawWindow),
+  _legendAxisX(&_drawWindow),
+  _labelXMin("", &_legendAxisX),
+  _labelXMid("", &_legendAxisX),
+  _labelXMax("", &_legendAxisX),
+  _glWindow(&_drawWindow),
+  _mState(nullptr),
+  _data(nullptr),
+  _keyPressed(false)
+{
+	auto lamMakeNavButton = [&](QPushButton *b) {
+		b->setMaximumWidth(FONT_WIDTH * 5);
+
+		connect(b,	&QPushButton::released,
+			this,	&KsTraceGraph::_stopUpdating);
+		_navigationBar.addWidget(b);
+	};
+
+	_pointerBar.setMaximumHeight(FONT_HEIGHT * 1.75);
+	_pointerBar.setOrientation(Qt::Horizontal);
+
+	_navigationBar.setMaximumHeight(FONT_HEIGHT * 1.75);
+	_navigationBar.setMinimumWidth(FONT_WIDTH * 90);
+	_navigationBar.setOrientation(Qt::Horizontal);
+
+	_pointerBar.addWidget(&_labelP1);
+	_labelP2.setFrameStyle(QFrame::Panel | QFrame::Sunken);
+	_labelP2.setStyleSheet("QLabel { background-color : white;}");
+	_labelP2.setTextInteractionFlags(Qt::TextSelectableByMouse);
+	_labelP2.setFixedWidth(FONT_WIDTH * 16);
+	_pointerBar.addWidget(&_labelP2);
+	_pointerBar.addSeparator();
+
+	_labelI1.setStyleSheet("QLabel {color : blue;}");
+	_labelI2.setStyleSheet("QLabel {color : green;}");
+	_labelI3.setStyleSheet("QLabel {color : red;}");
+	_labelI4.setStyleSheet("QLabel {color : blue;}");
+	_labelI5.setStyleSheet("QLabel {color : green;}");
+
+	_pointerBar.addWidget(&_labelI1);
+	_pointerBar.addSeparator();
+	_pointerBar.addWidget(&_labelI2);
+	_pointerBar.addSeparator();
+	_pointerBar.addWidget(&_labelI3);
+	_pointerBar.addSeparator();
+	_pointerBar.addWidget(&_labelI4);
+	_pointerBar.addSeparator();
+	_pointerBar.addWidget(&_labelI5);
+
+	_legendAxisX.setFixedHeight(FONT_HEIGHT * 1.5);
+	_legendAxisX.setLayout(new QHBoxLayout);
+	_legendAxisX.layout()->setSpacing(0);
+	_legendAxisX.layout()->setContentsMargins(0, 0, FONT_WIDTH, 0);
+
+	_labelXMin.setAlignment(Qt::AlignLeft);
+	_labelXMid.setAlignment(Qt::AlignHCenter);
+	_labelXMax.setAlignment(Qt::AlignRight);
+
+	_legendAxisX.layout()->addWidget(&_labelXMin);
+	_legendAxisX.layout()->addWidget(&_labelXMid);
+	_legendAxisX.layout()->addWidget(&_labelXMax);
+	_drawWindow.setMinimumSize(100, 100);
+	_drawWindow.setStyleSheet("QWidget {background-color : white;}");
+
+	_drawLayout.setContentsMargins(0, 0, 0, 0);
+	_drawLayout.setSpacing(0);
+	_drawLayout.addWidget(&_legendAxisX, 0, 1);
+	_drawLayout.addWidget(&_legendWindow, 1, 0);
+	_drawLayout.addWidget(&_glWindow, 1, 1);
+	_drawWindow.setLayout(&_drawLayout);
+
+	_drawWindow.installEventFilter(this);
+
+	connect(&_glWindow,	&KsGLWidget::select,
+		this,		&KsTraceGraph::markEntry);
+
+	connect(&_glWindow,	&KsGLWidget::found,
+		this,		&KsTraceGraph::_setPointerInfo);
+
+	connect(&_glWindow,	&KsGLWidget::notFound,
+		this,		&KsTraceGraph::_resetPointer);
+
+	connect(&_glWindow,	&KsGLWidget::zoomIn,
+		this,		&KsTraceGraph::_zoomIn);
+
+	connect(&_glWindow,	&KsGLWidget::zoomOut,
+		this,		&KsTraceGraph::_zoomOut);
+
+	connect(&_glWindow,	&KsGLWidget::scrollLeft,
+		this,		&KsTraceGraph::_scrollLeft);
+
+	connect(&_glWindow,	&KsGLWidget::scrollRight,
+		this,		&KsTraceGraph::_scrollRight);
+
+	connect(&_glWindow,	&KsGLWidget::stopUpdating,
+		this,		&KsTraceGraph::_stopUpdating);
+
+	connect(_glWindow.model(),	&KsGraphModel::modelReset,
+		this,			&KsTraceGraph::_updateTimeLegends);
+
+	_scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+	_scrollArea.setWidget(&_drawWindow);
+
+	lamMakeNavButton(&_scrollLeftButton);
+	connect(&_scrollLeftButton,	&QPushButton::pressed,
+		this,			&KsTraceGraph::_scrollLeft);
+
+	lamMakeNavButton(&_zoomInButton);
+	connect(&_zoomInButton,		&QPushButton::pressed,
+		this,			&KsTraceGraph::_zoomIn);
+
+	lamMakeNavButton(&_zoomOutButton);
+	connect(&_zoomOutButton,	&QPushButton::pressed,
+		this,			&KsTraceGraph::_zoomOut);
+
+	lamMakeNavButton(&_scrollRightButton);
+	connect(&_scrollRightButton,	&QPushButton::pressed,
+		this,			&KsTraceGraph::_scrollRight);
+
+	_navigationBar.addSeparator();
+
+	lamMakeNavButton(&_quickZoomInButton);
+	connect(&_quickZoomInButton,	&QPushButton::pressed,
+		this,			&KsTraceGraph::_quickZoomIn);
+
+	lamMakeNavButton(&_quickZoomOutButton);
+	connect(&_quickZoomOutButton,	&QPushButton::pressed,
+		this,			&KsTraceGraph::_quickZoomOut);
+
+	_layout.addWidget(&_pointerBar);
+	_layout.addWidget(&_navigationBar);
+	_layout.addWidget(&_scrollArea);
+	this->setLayout(&_layout);
+	updateGeom();
+}
+
+/**
+ * @brief Load and show trace data.
+ *
+ * @param data: Input location for the KsDataStore object.
+ *	  KsDataStore::loadDataFile() must be called first.
+ */
+void KsTraceGraph::loadData(KsDataStore *data)
+{
+	_data = data;
+	_glWindow.loadData(data);
+	_updateGraphLegends();
+	updateGeom();
+}
+
+/** Connect the KsGLWidget widget and the State machine of the Dual marker. */
+void KsTraceGraph::setMarkerSM(KsDualMarkerSM *m)
+{
+	_mState = m;
+	_navigationBar.addSeparator();
+	_mState->placeInToolBar(&_navigationBar);
+	_glWindow.setMarkerSM(m);
+}
+
+/** Reset (empty) the widget. */
+void KsTraceGraph::reset()
+{
+	/* Clear the all graph lists and update. */
+	_glWindow._cpuList = {};
+	_glWindow._taskList = {};
+
+	_labelP2.setText("");
+	for (auto l1: {&_labelI1, &_labelI2, &_labelI3, &_labelI4, &_labelI5})
+		l1->setText("");
+
+	_glWindow.model()->reset();
+	_selfUpdate();
+	for (auto l2: {&_labelXMin, &_labelXMid, &_labelXMax})
+		l2->setText("");
+}
+
+void KsTraceGraph::_selfUpdate()
+{
+	_updateGraphLegends();
+	_updateTimeLegends();
+	_markerReDraw();
+	updateGeom();
+}
+
+void KsTraceGraph::_zoomIn()
+{
+	_updateGraphs(GraphActions::ZoomIn);
+}
+
+void KsTraceGraph::_zoomOut()
+{
+	_updateGraphs(GraphActions::ZoomOut);
+}
+
+void KsTraceGraph::_quickZoomIn()
+{
+	/* Bin size will be 100 ns. */
+	_glWindow.model()->quickZoomIn(100);
+	if (_mState->activeMarker()._isSet &&
+	    _mState->activeMarker().isVisible()) {
+		/*
+		 * Use the position of the active marker as
+		 * a focus point of the zoom.
+		 */
+		uint64_t ts = _mState->activeMarker()._ts;
+		_glWindow.model()->jumpTo(ts);
+	}
+}
+
+void KsTraceGraph::_quickZoomOut()
+{
+	_glWindow.model()->quickZoomOut();
+}
+
+void KsTraceGraph::_scrollLeft()
+{
+	_updateGraphs(GraphActions::ScrollLeft);
+}
+
+void KsTraceGraph::_scrollRight()
+{
+	_updateGraphs(GraphActions::ScrollRight);
+}
+
+void KsTraceGraph::_stopUpdating()
+{
+	/*
+	 * The user is no longer pressing the action button. Reset the
+	 * "Key Pressed" flag. This will stop the ongoing user action.
+	 */
+	_keyPressed = false;
+}
+
+void KsTraceGraph::_resetPointer(uint64_t ts, int cpu, int pid)
+{
+	uint64_t sec, usec;
+	QString pointer;
+
+	kshark_convert_nano(ts, &sec, &usec);
+	pointer.sprintf("%lu.%lu", sec, usec);
+	_labelP2.setText(pointer);
+
+	if (pid > 0 && cpu >= 0) {
+		struct kshark_context *kshark_ctx(NULL);
+
+		if (!kshark_instance(&kshark_ctx))
+			return;
+
+		QString comm(tep_data_comm_from_pid(kshark_ctx->pevent, pid));
+		comm.append("-");
+		comm.append(QString("%1").arg(pid));
+		_labelI1.setText(comm);
+		_labelI2.setText(QString("CPU %1").arg(cpu));
+	} else {
+		_labelI1.setText("");
+		_labelI2.setText("");
+	}
+
+	for (auto const &l: {&_labelI3, &_labelI4, &_labelI5}) {
+		l->setText("");
+	}
+}
+
+void KsTraceGraph::_setPointerInfo(size_t i)
+{
+	kshark_entry *e = _data->rows()[i];
+	QString event(kshark_get_event_name_easy(e));
+	QString lat(kshark_get_latency_easy(e));
+	QString info(kshark_get_info_easy(e));
+	QString comm(kshark_get_task_easy(e));
+	QString pointer, elidedText;
+	int labelWidth, width;
+	uint64_t sec, usec;
+
+	kshark_convert_nano(e->ts, &sec, &usec);
+	pointer.sprintf("%lu.%lu", sec, usec);
+	_labelP2.setText(pointer);
+
+	comm.append("-");
+	comm.append(QString("%1").arg(kshark_get_pid_easy(e)));
+
+	_labelI1.setText(comm);
+	_labelI2.setText(QString("CPU %1").arg(e->cpu));
+	_labelI3.setText(lat);
+	_labelI4.setText(event);
+	_labelI5.setText(info);
+	QCoreApplication::processEvents();
+
+	labelWidth =
+		_pointerBar.geometry().right() - _labelI4.geometry().right();
+	if (labelWidth > STRING_WIDTH(info) + FONT_WIDTH * 5)
+		return;
+
+	/*
+	 * The Info string is too long and cannot be displayed on the toolbar.
+	 * Try to fit the text in the available space.
+	 */
+	QFontMetrics metrix(_labelI5.font());
+	width = labelWidth - FONT_WIDTH * 3;
+	elidedText = metrix.elidedText(info, Qt::ElideRight, width);
+
+	while(labelWidth < STRING_WIDTH(elidedText) + FONT_WIDTH * 5) {
+		width -= FONT_WIDTH * 3;
+		elidedText = metrix.elidedText(info, Qt::ElideRight, width);
+	}
+
+	_labelI5.setText(elidedText);
+	_labelI5.setVisible(true);
+	QCoreApplication::processEvents();
+}
+
+/**
+ * @brief Use the active marker to select particular entry.
+ *
+ * @param row: The index of the entry to be selected by the marker.
+ */
+void KsTraceGraph::markEntry(size_t row)
+{
+	int graph, cpuGrId, taskGrId;
+
+	_glWindow.findGraphIds(*_data->rows()[row], &cpuGrId, &taskGrId);
+
+	/*
+	 * If a Task graph has been found, this Task graph will be
+	 * visible. If no Task graph has been found, make visible
+	 * the corresponding CPU graph.
+	 */
+	if (taskGrId >= 0)
+		graph = taskGrId;
+	else
+		graph = cpuGrId;
+
+	_scrollArea.ensureVisible(0,
+				  _legendAxisX.height() +
+				  _glWindow.vMargin() +
+				  KS_GRAPH_HEIGHT / 2 +
+				  graph*(KS_GRAPH_HEIGHT + _glWindow.vSpacing()),
+				  50,
+				  KS_GRAPH_HEIGHT / 2 + _glWindow.vSpacing() / 2);
+
+	_glWindow.model()->jumpTo(_data->rows()[row]->ts);
+	_mState->activeMarker().set(*_data,
+				    _glWindow.model()->histo(),
+				    row, cpuGrId, taskGrId);
+
+	_mState->updateMarkers(*_data, &_glWindow);
+}
+
+void KsTraceGraph::_markerReDraw()
+{
+	int cpuGrId, taskGrId;
+	size_t row;
+
+	if (_mState->markerA()._isSet) {
+		row = _mState->markerA()._pos;
+		_glWindow.findGraphIds(*_data->rows()[row], &cpuGrId, &taskGrId);
+		_mState->markerA().set(*_data,
+				       _glWindow.model()->histo(),
+				       row, cpuGrId, taskGrId);
+	}
+
+	if (_mState->markerB()._isSet) {
+		row = _mState->markerB()._pos;
+		_glWindow.findGraphIds(*_data->rows()[row], &cpuGrId, &taskGrId);
+		_mState->markerB().set(*_data,
+				       _glWindow.model()->histo(),
+				       row, cpuGrId, taskGrId);
+	}
+}
+
+/**
+ * @brief Redreaw all CPU graphs.
+ *
+ * @param v: CPU ids to be plotted.
+ */
+void KsTraceGraph::cpuReDraw(QVector<int> v)
+{
+	_glWindow._cpuList = v;
+	_selfUpdate();
+}
+
+/**
+ * @brief Redreaw all Task graphs.
+ *
+ * @param v: Process ids of the tasks to be plotted.
+ */
+void KsTraceGraph::taskReDraw(QVector<int> v)
+{
+	_glWindow._taskList = v;
+	_selfUpdate();
+}
+
+/** Add (and plot) a CPU graph to the existing list of CPU graphs. */
+void KsTraceGraph::addCPUPlot(int cpu)
+{
+	if (_glWindow._cpuList.contains(cpu))
+		return;
+
+	_glWindow._cpuList.append(cpu);
+	_selfUpdate();
+}
+
+/** Add (and plot) a Task graph to the existing list of Task graphs. */
+void KsTraceGraph::addTaskPlot(int pid)
+{
+	if (_glWindow._taskList.contains(pid))
+		return;
+
+	_glWindow._taskList.append(pid);
+	_selfUpdate();
+}
+
+/** Update the content of all graphs. */
+void KsTraceGraph::update(KsDataStore *data)
+{
+	_glWindow.model()->update(data);
+	_selfUpdate();
+}
+
+/** Update the geometry of the widget. */
+void KsTraceGraph::updateGeom()
+{
+	int saWidth, saHeight, dwWidth, hMin;
+
+	/* Set the size of the Scroll Area. */
+	saWidth = width() - _layout.contentsMargins().left() -
+			    _layout.contentsMargins().right();
+
+	saHeight = height() - _pointerBar.height() -
+			      _navigationBar.height() -
+			      _layout.spacing() * 2 -
+			      _layout.contentsMargins().top() -
+			      _layout.contentsMargins().bottom();
+
+	_scrollArea.resize(saWidth, saHeight);
+
+	/*
+	 * Calculate the width of the Draw Window, taking into account the size
+	 * of the scroll bar.
+	 */
+	dwWidth = _scrollArea.width();
+	if (_glWindow.height() + _legendAxisX.height() > _scrollArea.height())
+		dwWidth -=
+			qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent);
+
+	/*
+	 * Set the height of the Draw window according to the number of
+	 * plotted graphs.
+	 */
+	_drawWindow.resize(dwWidth,
+			   _glWindow.height() + _legendAxisX.height());
+
+	/* Set the minimum height of the Graph widget. */
+	hMin = _drawWindow.height() +
+	       _pointerBar.height() +
+	       _navigationBar.height() +
+	       _layout.contentsMargins().top() +
+	       _layout.contentsMargins().bottom();
+
+	if (hMin > KS_GRAPH_HEIGHT * 8)
+		hMin = KS_GRAPH_HEIGHT * 8;
+
+	setMinimumHeight(hMin);
+
+	/*
+	 * Now use the height of the Draw Window to fix the maximum height
+	 * of the Graph widget.
+	 */
+	setMaximumHeight(_drawWindow.height() +
+			 _pointerBar.height() +
+			 _navigationBar.height() +
+			 _layout.spacing() * 2 +
+			 _layout.contentsMargins().top() +
+			 _layout.contentsMargins().bottom() +
+			 2);  /* Just a little bit of extra space. This will
+			       * allow the scroll bar to disappear when the
+			       * widget is extended to maximum.
+			       */
+}
+
+void KsTraceGraph::_updateGraphLegends()
+{
+	QString graphLegends, graphName;
+	QVBoxLayout *layout;
+	int width = 0;
+
+	if (_legendWindow.layout()) {
+		/*
+		 * Remove and delete the existing layout of the legend window.
+		 */
+		QLayoutItem *child;
+		while ((child = _legendWindow.layout()->takeAt(0)) != 0) {
+			delete child->widget();
+			delete child;
+		}
+
+		delete _legendWindow.layout();
+	}
+
+	layout = new QVBoxLayout;
+	layout->setContentsMargins(FONT_WIDTH, 0, 0, 0);
+	layout->setSpacing(_glWindow.vSpacing());
+	layout->setAlignment(Qt::AlignTop);
+	layout->addSpacing(_glWindow.vMargin());
+
+	auto lamMakeName = [&]() {
+		QLabel *name = new QLabel(graphName);
+
+		if (width < STRING_WIDTH(graphName))
+			width = STRING_WIDTH(graphName);
+
+		name->setAlignment(Qt::AlignBottom);
+		name->setStyleSheet("QLabel {background-color : white;}");
+		name->setFixedHeight(KS_GRAPH_HEIGHT);
+		layout->addWidget(name);
+	};
+
+	for (auto const &cpu: _glWindow._cpuList) {
+		graphName = QString("CPU %1").arg(cpu);
+		lamMakeName();
+	}
+
+	for (auto const &pid: _glWindow._taskList) {
+		graphName = QString(tep_data_comm_from_pid(_data->tep(),
+							   pid));
+		graphName.append(QString("-%1").arg(pid));
+		lamMakeName();
+	}
+
+	_legendWindow.setLayout(layout);
+	_legendWindow.setMaximumWidth(width + FONT_WIDTH);
+}
+
+void KsTraceGraph::_updateTimeLegends()
+{
+	uint64_t sec, usec, tsMid;
+	QString tMin, tMid, tMax;
+
+	kshark_convert_nano(_glWindow.model()->histo()->min, &sec, &usec);
+	tMin.sprintf("%lu.%lu", sec, usec);
+	_labelXMin.setText(tMin);
+
+	tsMid = (_glWindow.model()->histo()->min +
+		 _glWindow.model()->histo()->max) / 2;
+	kshark_convert_nano(tsMid, &sec, &usec);
+	tMid.sprintf("%lu.%lu", sec, usec);
+	_labelXMid.setText(tMid);
+
+	kshark_convert_nano(_glWindow.model()->histo()->max, &sec, &usec);
+	tMax.sprintf("%lu.%lu", sec, usec);
+	_labelXMax.setText(tMax);
+}
+
+/**
+ * Reimplemented event handler used to update the geometry of the widget on
+ * resize events.
+ */
+void KsTraceGraph::resizeEvent(QResizeEvent* event)
+{
+	updateGeom();
+}
+
+/**
+ * Reimplemented event handler (overriding a virtual function from QObject)
+ * used to detect the position of the mouse with respect to the Draw window and
+ * according to this position to grab / release the focus of the keyboard. The
+ * function has nothing to do with the filtering of the trace events.
+ */
+bool KsTraceGraph::eventFilter(QObject* obj, QEvent* evt)
+{
+	if (obj == &_drawWindow && evt->type() == QEvent::Enter)
+		_glWindow.setFocus();
+
+	if (obj == &_drawWindow && evt->type() == QEvent::Leave)
+		_glWindow.clearFocus();
+
+	return QWidget::eventFilter(obj, evt);
+}
+
+void KsTraceGraph::_updateGraphs(GraphActions action)
+{
+	double k;
+	int bin;
+
+	/*
+	 * Set the "Key Pressed" flag. The flag will stay set as long as the user
+	 * keeps the corresponding action button pressed.
+	 */
+	_keyPressed = true;
+
+	/* Initialize the zooming factor with a small value. */
+	k = .01;
+	while (_keyPressed) {
+		switch (action) {
+		case GraphActions::ZoomIn:
+			if (_mState->activeMarker()._isSet &&
+			    _mState->activeMarker().isVisible()) {
+				/*
+				 * Use the position of the active marker as
+				 * a focus point of the zoom.
+				 */
+				bin = _mState->activeMarker()._bin;
+				_glWindow.model()->zoomIn(k, bin);
+			} else {
+				/*
+				 * The default focus point is the center of the
+				 * range interval of the model.
+				 */
+				_glWindow.model()->zoomIn(k);
+			}
+
+			break;
+
+		case GraphActions::ZoomOut:
+			if (_mState->activeMarker()._isSet &&
+			    _mState->activeMarker().isVisible()) {
+				/*
+				 * Use the position of the active marker as
+				 * a focus point of the zoom.
+				 */
+				bin = _mState->activeMarker()._bin;
+				_glWindow.model()->zoomOut(k, bin);
+			} else {
+				/*
+				 * The default focus point is the center of the
+				 * range interval of the model.
+				 */
+				_glWindow.model()->zoomOut(k);
+			}
+
+			break;
+
+		case GraphActions::ScrollLeft:
+			_glWindow.model()->shiftBackward(10);
+			break;
+
+		case GraphActions::ScrollRight:
+			_glWindow.model()->shiftForward(10);
+			break;
+		}
+
+		/*
+		 * As long as the action button is pressed, the zooming factor
+		 * will grow smoothly until it reaches a maximum value. This
+		 * will have a visible effect of an accelerating zoom.
+		 */
+		if (k < .25)
+			k  *= 1.02;
+
+		_mState->updateMarkers(*_data, &_glWindow);
+		_updateTimeLegends();
+		QCoreApplication::processEvents();
+	}
+}
diff --git a/kernel-shark-qt/src/KsTraceGraph.hpp b/kernel-shark-qt/src/KsTraceGraph.hpp
new file mode 100644
index 0000000..395cc1b
--- /dev/null
+++ b/kernel-shark-qt/src/KsTraceGraph.hpp
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: LGPL-2.1 */
+
+/*
+ * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsTraceGraph.hpp
+ *  @brief   KernelShark Trace Graph widget.
+ */
+#ifndef _KS_TRACEGRAPH_H
+#define _KS_TRACEGRAPH_H
+
+// KernelShark
+#include "KsGLWidget.hpp"
+
+/**
+ * Scroll Area class, needed in order to reimplemented the handler for mouse
+ * wheel events.
+ */
+class KsGraphScrollArea : public QScrollArea {
+public:
+	/** Create a default Scroll Area. */
+	explicit KsGraphScrollArea(QWidget *parent = nullptr)
+	: QScrollArea(parent) {}
+
+	/**
+	 * Reimplemented handler for mouse wheel events. All mouse wheel
+	 * events will be ignored.
+	 */
+	void wheelEvent(QWheelEvent *evt) {evt->ignore();}
+};
+
+/**
+ * The KsTraceViewer class provides a widget for interactive visualization of
+ * trace data shown as time-series.
+ */
+class KsTraceGraph : public QWidget
+{
+	Q_OBJECT
+public:
+	explicit KsTraceGraph(QWidget *parent = nullptr);
+
+	void loadData(KsDataStore *data);
+
+	void setMarkerSM(KsDualMarkerSM *m);
+
+	void reset();
+
+	/** Get the KsGLWidget object. */
+	KsGLWidget *glPtr() {return &_glWindow;}
+
+	void markEntry(size_t);
+
+	void cpuReDraw(QVector<int>);
+
+	void taskReDraw(QVector<int>);
+
+	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
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 08/23] kernel-shark-qt: Add dialog for Advanced filtering.
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (6 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 07/23] kernel-shark-qt: Add Trace Graph widget Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 09/23] kernel-shark-qt: Add a manager class for GUI sessions Yordan Karadzhov
                   ` (13 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

This patch defines a dialog for configuring and using Advanced filtering.

Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
---
 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 <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @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 += " <sys/event>[,<sys/event>] : [!][(]<field><op><val>[)]";
+	descrText += "[&&/|| [(]<field><op><val>[)]]\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<int, QString> 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<int, QString> 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 <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsAdvFilteringDialog.hpp
+ *  @brief   GUI Dialog for Advanced filtering settings.
+ */
+
+#ifndef _KS_ADV_FILTERING_DIALOG_H
+#define _KS_ADV_FILTERING_DIALOG_H
+
+// Qt
+#include <QtWidgets>
+
+// 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<int, QString>	_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
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 09/23] kernel-shark-qt: Add a manager class for GUI sessions.
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (7 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 08/23] kernel-shark-qt: Add dialog for Advanced filtering Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 10/23] kernel-shark-qt: Add Main Window widget for the KernelShark GUI Yordan Karadzhov
                   ` (12 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

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) <y.karadz@gmail.com>
---
 kernel-shark-qt/src/CMakeLists.txt |   1 +
 kernel-shark-qt/src/KsSession.cpp  | 574 +++++++++++++++++++++++++++++
 kernel-shark-qt/src/KsSession.hpp  | 100 +++++
 3 files changed, 675 insertions(+)
 create mode 100644 kernel-shark-qt/src/KsSession.cpp
 create mode 100644 kernel-shark-qt/src/KsSession.hpp

diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
index 26b45f4..192cd12 100644
--- a/kernel-shark-qt/src/CMakeLists.txt
+++ b/kernel-shark-qt/src/CMakeLists.txt
@@ -44,6 +44,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
 
     add_library(kshark-gui  SHARED  ${ks-guiLib_hdr_moc}    KsUtils.cpp
                                                             KsModels.cpp
+                                                            KsSession.cpp
                                                             KsGLWidget.cpp
                                                             KsDualMarker.cpp
                                                             KsWidgetsLib.cpp
diff --git a/kernel-shark-qt/src/KsSession.cpp b/kernel-shark-qt/src/KsSession.cpp
new file mode 100644
index 0000000..96e09f2
--- /dev/null
+++ b/kernel-shark-qt/src/KsSession.cpp
@@ -0,0 +1,574 @@
+// SPDX-License-Identifier: LGPL-2.1
+
+/*
+ * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsSession.cpp
+ *  @brief   KernelShark Session.
+ */
+
+// KernelShark
+#include "libkshark.h"
+#include "KsSession.hpp"
+
+/** Create a KsSession object. */
+KsSession::KsSession()
+{
+	_config = kshark_config_new("kshark.config.session",
+				      KS_CONFIG_JSON);
+}
+
+/** Destroy a KsSession object. */
+KsSession::~KsSession()
+{
+	kshark_free_config_doc(_config);
+}
+
+/** Import a user session from a Json file. */
+void KsSession::importFromFile(QString jfileName)
+{
+	if (_config)
+		kshark_free_config_doc(_config);
+
+	_config = kshark_open_config_file(jfileName.toStdString().c_str(),
+					  "kshark.config.session");
+}
+
+/** Export the current user session from a Json file. */
+void KsSession::exportToFile(QString jfileName)
+{
+	kshark_save_config_file(jfileName.toStdString().c_str(), _config);
+}
+
+/**
+ * @brief Save the state of the visualization model.
+ *
+ * @param histo: Input location for the model descriptor.
+ */
+void KsSession::saveVisModel(kshark_trace_histo *histo)
+{
+	kshark_config_doc *model =
+		kshark_export_model(histo, KS_CONFIG_JSON);
+
+	kshark_config_doc_add(_config, "Model", model);
+}
+
+/**
+ * @brief Load the state of the visualization model.
+ *
+ * @param model: Input location for the KsGraphModel object.
+ */
+void KsSession::loadVisModel(KsGraphModel *model)
+{
+	kshark_config_doc *modelConf = kshark_config_alloc(KS_CONFIG_JSON);
+
+	if (!kshark_config_doc_get(_config, "Model", modelConf))
+		return;
+
+	kshark_import_model(model->histo(), modelConf);
+	model->update();
+}
+
+/** Save the trace data file. */
+void KsSession::saveDataFile(QString fileName)
+{
+	kshark_config_doc *file =
+		kshark_export_trace_file(fileName.toStdString().c_str(),
+					 KS_CONFIG_JSON);
+
+	kshark_config_doc_add(_config, "Data", file);
+}
+
+/** Get the trace data file. */
+QString KsSession::getDataFile(kshark_context *kshark_ctx)
+{
+	kshark_config_doc *file = kshark_config_alloc(KS_CONFIG_JSON);
+	const char *file_str;
+
+	if (!kshark_config_doc_get(_config, "Data", file))
+		return QString();
+
+	file_str = kshark_import_trace_file(kshark_ctx, file);
+	if (file_str)
+		return QString(file_str);
+
+	return QString();
+}
+
+/**
+ * @brief Save the configuration of the filters.
+ *
+ * @param kshark_ctx: Input location for context pointer.
+ */
+void KsSession::saveFilters(kshark_context *kshark_ctx)
+{
+	kshark_config_doc *filters =
+		kshark_export_all_filters(kshark_ctx, KS_CONFIG_JSON);
+
+	kshark_config_doc_add(_config, "Filters", filters);
+}
+
+/**
+ * @brief Load the configuration of the filters and filter the data.
+ *
+ * @param kshark_ctx: Input location for context pointer.
+ * @param data: Input location for KsDataStore object;
+ */
+void KsSession::loadFilters(kshark_context *kshark_ctx, KsDataStore *data)
+{
+	kshark_config_doc *filters = kshark_config_alloc(KS_CONFIG_JSON);
+
+	if (!kshark_config_doc_get(_config, "Filters", filters))
+		return;
+
+	kshark_import_all_filters(kshark_ctx, filters);
+
+	if (kshark_ctx->advanced_event_filter->filters)
+		data->reload();
+	else
+		kshark_filter_entries(kshark_ctx, data->rows(), data->size());
+
+	data->registerCPUCollections();
+
+	emit data->updateWidgets(data);
+}
+
+/**
+ * @brief Save the state of the table.
+ *
+ * @param view: Input location for the KsTraceViewer widget.
+ */
+void KsSession::saveTable(const KsTraceViewer &view) {
+	kshark_config_doc *topRow = kshark_config_alloc(KS_CONFIG_JSON);
+	int64_t r = view.getTopRow();
+
+	topRow->conf_doc = json_object_new_int64(r);
+	kshark_config_doc_add(_config, "ViewTop",topRow);
+}
+
+/**
+ * @brief Load the state of the table.
+ *
+ * @param view: Input location for the KsTraceViewer widget.
+ */
+void KsSession::loadTable(KsTraceViewer *view) {
+	kshark_config_doc *topRow = kshark_config_alloc(KS_CONFIG_JSON);
+	size_t r = 0;
+
+	if (!kshark_config_doc_get(_config, "ViewTop", topRow))
+		return;
+
+	if (_config->format == KS_CONFIG_JSON)
+		r = json_object_get_int64(KS_JSON_CAST(topRow->conf_doc));
+
+	view->setTopRow(r);
+}
+
+/**
+ * @brief Save the KernelShark Main window size.
+ *
+ * @param window: Input location for the KsMainWindow widget.
+ */
+void KsSession::saveMainWindowSize(const QMainWindow &window)
+{
+	kshark_config_doc *windowConf = kshark_config_alloc(KS_CONFIG_JSON);
+	int width = window.width(), height = window.height();
+	json_object *jwindow = json_object_new_array();
+
+	json_object_array_put_idx(jwindow, 0, json_object_new_int(width));
+	json_object_array_put_idx(jwindow, 1, json_object_new_int(height));
+
+	windowConf->conf_doc = jwindow;
+	kshark_config_doc_add(_config, "MainWindow", windowConf);
+}
+
+/**
+ * @brief Load the KernelShark Main window size.
+ *
+ * @param window: Input location for the KsMainWindow widget.
+ */
+void KsSession::loadMainWindowSize(QMainWindow *window)
+{
+	kshark_config_doc *windowConf = kshark_config_alloc(KS_CONFIG_JSON);
+	json_object *jwindow, *jwidth, *jheight;
+	int width, height;
+
+	if (!kshark_config_doc_get(_config, "MainWindow", windowConf))
+		return;
+
+	if (_config->format == KS_CONFIG_JSON) {
+		jwindow = KS_JSON_CAST(windowConf->conf_doc);
+		jwidth = json_object_array_get_idx(jwindow, 0);
+		jheight = json_object_array_get_idx(jwindow, 1);
+
+		width = json_object_get_int(jwidth);
+		height = json_object_get_int(jheight);
+
+		window->resize(width, height);
+	}
+}
+
+/**
+ * @brief Save the state of the Main window spliter.
+ *
+ * @param splitter: Input location for the splitter widget.
+ */
+void KsSession::saveSplitterSize(const QSplitter &splitter)
+{
+	kshark_config_doc *spl = kshark_config_alloc(KS_CONFIG_JSON);
+	json_object *jspl = json_object_new_array();
+	QList<int> 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<int> 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<int> &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<int> KsSession::_getCPUPlots()
+{
+	kshark_config_doc *cpuPlts = kshark_config_alloc(KS_CONFIG_JSON);
+	json_object *jcpus;
+	QVector<int> 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<int> &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<int> KsSession::_getTaskPlots()
+{
+	kshark_config_doc *taskPlts = kshark_config_alloc(KS_CONFIG_JSON);
+	json_object *jtasks;
+	QVector<int> 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<bool> &registeredPlugins = 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 <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsSession.hpp
+ *  @brief   KernelShark Session.
+ */
+
+#ifndef _KS_SESSION_H
+#define _KS_SESSION_H
+
+// Qt
+#include <QtWidgets>
+
+// 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<int> &cpus);
+
+	QVector<int> _getCPUPlots();
+
+	void _saveTaskPlots(const QVector<int> &tasks);
+
+	QVector<int> _getTaskPlots();
+
+	bool _getMarker(const char* name, size_t *pos);
+
+	DualMarkerState _getMarkerState();
+};
+
+#endif
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 10/23] kernel-shark-qt: Add Main Window widget for the KernelShark GUI.
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (8 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 09/23] kernel-shark-qt: Add a manager class for GUI sessions Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 11/23] kernel-shark-qt: Add KernelShark GUI executable Yordan Karadzhov
                   ` (11 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

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) <y.karadz@gmail.com>
---
 kernel-shark-qt/CMakeLists.txt       |   5 +
 kernel-shark-qt/src/CMakeLists.txt   |   2 +
 kernel-shark-qt/src/KsMainWindow.cpp | 909 +++++++++++++++++++++++++++
 kernel-shark-qt/src/KsMainWindow.hpp | 191 ++++++
 4 files changed, 1107 insertions(+)
 create mode 100644 kernel-shark-qt/src/KsMainWindow.cpp
 create mode 100644 kernel-shark-qt/src/KsMainWindow.hpp

diff --git a/kernel-shark-qt/CMakeLists.txt b/kernel-shark-qt/CMakeLists.txt
index 71a021e..0ccb61e 100644
--- a/kernel-shark-qt/CMakeLists.txt
+++ b/kernel-shark-qt/CMakeLists.txt
@@ -12,6 +12,11 @@ message("\n project: Kernel Shark: (version: ${KS_VERSION_STRING})\n")
 
 set(KS_DIR ${CMAKE_SOURCE_DIR})
 
+# Make a directory to hold configuration files. To change this do:
+# cmake .. -DKS_CONF_DIR=/your/preferred/path
+set(KS_CONF_DIR "${KS_DIR}/.ksconf" CACHE STRING "Directory for configuration files.")
+file(MAKE_DIRECTORY ${KS_CONF_DIR})
+
 include(${KS_DIR}/build/FindTraceCmd.cmake)
 include(${KS_DIR}/build/FindJSONC.cmake)
 
diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
index 192cd12..d8daada 100644
--- a/kernel-shark-qt/src/CMakeLists.txt
+++ b/kernel-shark-qt/src/CMakeLists.txt
@@ -38,6 +38,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
                         KsWidgetsLib.hpp
                         KsTraceGraph.hpp
                         KsTraceViewer.hpp
+                        KsMainWindow.hpp
                         KsAdvFilteringDialog.hpp)
 
     QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr})
@@ -50,6 +51,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
                                                             KsWidgetsLib.cpp
                                                             KsTraceGraph.cpp
                                                             KsTraceViewer.cpp
+                                                            KsMainWindow.cpp
                                                             KsAdvFilteringDialog.cpp)
 
     target_link_libraries(kshark-gui kshark-plot
diff --git a/kernel-shark-qt/src/KsMainWindow.cpp b/kernel-shark-qt/src/KsMainWindow.cpp
new file mode 100644
index 0000000..d30f752
--- /dev/null
+++ b/kernel-shark-qt/src/KsMainWindow.cpp
@@ -0,0 +1,909 @@
+// SPDX-License-Identifier: LGPL-2.1
+
+/*
+ * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsMainWindow.cpp
+ *  @brief   KernelShark GUI main window.
+ */
+
+// C
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pwd.h>
+
+// C++11
+#include <thread>
+
+// Qt
+#include <QMenu>
+#include <QFileDialog>
+#include <QMenuBar>
+#include <QLabel>
+#include <QLocalSocket>
+
+// KernelShark
+#include "libkshark.h"
+#include "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<bool> 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<int> pids = KsUtils::getPidList();
+		int nPids = pids.count();
+		QVector<bool> 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<int> pids = KsUtils::getPidList();
+		int nPids = pids.count();
+		QVector<bool> 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<bool> 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<int> pids = KsUtils::getPidList();
+	int nPids = pids.count();
+
+	if (nPids == _graph.glPtr()->taskGraphCount()) {
+		tasks_cbd->setDefault(true);
+	} else {
+		QVector<bool> 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<bool> registeredPlugins;
+	QStringList plugins;
+
+	plugins << _plugins._ksPluginList << _plugins._userPluginList;
+
+	registeredPlugins << _plugins._registeredKsPlugins
+			  << _plugins._registeredUserPlugins;
+
+	plugin_cbd = new KsPluginCheckBoxWidget(plugins, this);
+	plugin_cbd->set(registeredPlugins);
+
+	dialog = new KsCheckBoxDialog(plugin_cbd, this);
+
+	connect(dialog,		&KsCheckBoxDialog::apply,
+		&_plugins,	&KsPluginManager::updatePlugins);
+
+	dialog->show();
+}
+
+void KsMainWindow::_setColorPhase(int f)
+{
+	KsPlot::Color::setRainbowFrequency(f / 100.);
+	_graph.glPtr()->model()->update();
+}
+
+void KsMainWindow::_fullScreenMode()
+{
+	if (_isFullScreen) {
+		_fullScreenModeAction.setText("Full Screen Mode");
+		_fullScreenModeAction.setIcon(QIcon::fromTheme("view-fullscreen"));
+		showNormal();
+		_isFullScreen = false;
+	} else {
+		_fullScreenModeAction.setText("Exit Full Screen Mode");
+		_fullScreenModeAction.setIcon(QIcon::fromTheme("view-restore"));
+		showFullScreen();
+		_isFullScreen = true;
+	}
+}
+
+void KsMainWindow::_aboutInfo()
+{
+	KsMessageDialog *message;
+	QString text;
+
+	text.append(" KernelShark\n\n version: ");
+	text.append(KS_VERSION_STRING);
+	text.append("\n");
+
+	message = new KsMessageDialog(text);
+	message->setWindowTitle("About");
+	message->show();
+}
+
+void KsMainWindow::_contents()
+{
+	QDesktopServices::openUrl(QUrl("https://www.google.bg/search?q=kernelshark",
+				  QUrl::TolerantMode));
+}
+
+/** Load trace data for file. */
+void KsMainWindow::loadDataFile(const QString& fileName)
+{
+	char buff[FILENAME_MAX];
+	QString pbLabel("Loading    ");
+	bool loadDone = false;
+	struct stat st;
+	int ret;
+
+	ret = stat(fileName.toStdString().c_str(), &st);
+	if (ret != 0) {
+		QString text("Unable to find file ");
+
+		text.append(fileName);
+		text.append(".");
+		_error(text, "loadDataErr1", true, true);
+
+		return;
+	}
+
+	qInfo() << "Loading " << fileName;
+
+	_mState.reset();
+	_view.reset();
+	_graph.reset();
+
+	if (fileName.size() < 40) {
+		pbLabel += fileName;
+	} else {
+		pbLabel += "...";
+		pbLabel += fileName.mid(fileName.size() - 37, 37);
+	}
+
+	setWindowTitle("Kernel Shark");
+	KsProgressBar pb(pbLabel);
+	QApplication::processEvents();
+
+	auto lamLoadJob = [&](KsDataStore *d) {
+		d->loadDataFile(fileName);
+		loadDone = true;
+	};
+	std::thread tload(lamLoadJob, &_data);
+
+	for (int i = 0; i < 160; ++i) {
+		/*
+		 * TODO: The way this progress bar gets updated here is a pure
+		 * cheat. See if this can be implemented better.
+		*/
+		if (loadDone)
+			break;
+
+		pb.setValue(i);
+		usleep(150000);
+	}
+
+	tload.join();
+
+	if (!_data.size()) {
+		QString text("File ");
+
+		text.append(fileName);
+		text.append(" contains no data.");
+		_error(text, "loadDataErr2", true, true);
+
+		return;
+	}
+
+	pb.setValue(165);
+	_view.loadData(&_data);
+
+	pb.setValue(180);
+	_graph.loadData(&_data);
+	pb.setValue(195);
+	setWindowTitle("Kernel Shark (" + fileName + ")");
+
+	if (realpath(fileName.toStdString().c_str(), buff)) {
+		QString path(buff);
+		_session.saveDataFile(path);
+	}
+}
+
+void KsMainWindow::_error(const QString &text, const QString &errCode,
+			  bool resize, bool unloadPlugins)
+{
+	QErrorMessage *em = new QErrorMessage(this);
+
+	if (resize)
+		_resizeEmpty();
+
+	if (unloadPlugins)
+		_plugins.unloadAll();
+
+	em->showMessage(text, errCode);
+	qCritical() << "ERROR: " << text;
+}
+
+/**
+ * @brief Load user session.
+ *
+ * @param fileName: Json file containing the description of the session.
+ */
+void KsMainWindow::loadSession(const QString &fileName)
+{
+	kshark_context *kshark_ctx(nullptr);
+	struct stat st;
+	int ret;
+
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	ret = stat(fileName.toStdString().c_str(), &st);
+	if (ret != 0) {
+		QString text("Unable to find session file ");
+
+		text.append(fileName);
+		text.append("\n");
+		_error(text, "loadSessErr0", true, true);
+
+		return;
+	}
+
+	_session.importFromFile(fileName);
+	_session.loadPlugins(kshark_ctx, &_plugins);
+
+	QString dataFile(_session.getDataFile(kshark_ctx));
+	if (dataFile.isEmpty()) {
+		QString text("Unable to open trace data file for session ");
+
+		text.append(fileName);
+		text.append("\n");
+		_error(text, "loadSessErr1", true, true);
+
+		return;
+	}
+
+	loadDataFile(dataFile);
+	if (!_data.tep()) {
+		_plugins.unloadAll();
+		return;
+	}
+
+	KsProgressBar pb("Loading session settings ...");
+	pb.setValue(10);
+
+	_session.loadGraphs(&_graph);
+	pb.setValue(20);
+
+	_session.loadFilters(kshark_ctx, &_data);
+	pb.setValue(130);
+
+	_session.loadSplitterSize(&_splitter);
+	_session.loadMainWindowSize(this);
+	this->show();
+	pb.setValue(140);
+
+	_session.loadDualMarker(&_mState, &_graph);
+	_session.loadVisModel(_graph.glPtr()->model());
+	_mState.updateMarkers(_data, _graph.glPtr());
+	pb.setValue(170);
+
+	_session.loadTable(&_view);
+	_colorPhaseSlider.setValue(_session.getColorScheme() * 100);
+}
+
+void KsMainWindow::_splitterMoved(int pos, int index)
+{
+	_session.saveSplitterSize(_splitter);
+}
diff --git a/kernel-shark-qt/src/KsMainWindow.hpp b/kernel-shark-qt/src/KsMainWindow.hpp
new file mode 100644
index 0000000..6e3f864
--- /dev/null
+++ b/kernel-shark-qt/src/KsMainWindow.hpp
@@ -0,0 +1,191 @@
+/* SPDX-License-Identifier: LGPL-2.1 */
+
+/*
+ * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsMainWindow.hpp
+ *  @brief   KernelShark GUI main window.
+ */
+
+#ifndef _KS_MAINWINDOW_H
+#define _KS_MAINWINDOW_H
+
+// Qt
+#include <QMainWindow>
+
+// 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
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 11/23] kernel-shark-qt: Add KernelShark GUI executable.
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (9 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 10/23] kernel-shark-qt: Add Main Window widget for the KernelShark GUI Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 12/23] kernel-shark-qt: Add "File exists" dialog Yordan Karadzhov
                   ` (10 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

This patch adds the main executable of the KernelShark GUI.

Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
---
 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 <y.karadz@gmail.com>
+ */
+
+// C
+#include <sys/stat.h>
+#include <getopt.h>
+
+// Qt
+#include <QApplication>
+
+// 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();
+}
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 12/23] kernel-shark-qt: Add "File exists" dialog.
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (10 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 11/23] kernel-shark-qt: Add KernelShark GUI executable Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 13/23] kernel-shark-qt: Fix the glitches in the preemption time visualization Yordan Karadzhov
                   ` (9 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel

A helper function for launching a "File exists" dialog is added to
KsWidgetsLib. This function asks the user, before overwriting an existing
file. The "File exists" dialog function is used by the KsMainWindow widget
when saving the configuration of the filters and sessions.

Signed-off-by: Yordan Karadzhov <ykaradzhov@vmware.com>
---
 kernel-shark-qt/src/KsMainWindow.cpp | 22 +++++++--------------
 kernel-shark-qt/src/KsWidgetsLib.cpp | 29 ++++++++++++++++++++++++++++
 kernel-shark-qt/src/KsWidgetsLib.hpp |  7 +++++++
 3 files changed, 43 insertions(+), 15 deletions(-)

diff --git a/kernel-shark-qt/src/KsMainWindow.cpp b/kernel-shark-qt/src/KsMainWindow.cpp
index d30f752..b9fb587 100644
--- a/kernel-shark-qt/src/KsMainWindow.cpp
+++ b/kernel-shark-qt/src/KsMainWindow.cpp
@@ -397,20 +397,7 @@ void KsMainWindow::_exportSession()
 	if (!fileName.endsWith(".json")) {
 		fileName += ".json";
 		if (QFileInfo(fileName).exists()) {
-			QString msg("A file ");
-			QMessageBox msgBox;
-
-			msg += fileName;
-			msg += " already exists.";
-			msgBox.setText(msg);
-			msgBox.setInformativeText("Do you want to replace it?");
-
-			msgBox.setStandardButtons(QMessageBox::Save |
-						  QMessageBox::Cancel);
-
-			msgBox.setDefaultButton(QMessageBox::Cancel);
-
-			if (msgBox.exec() == QMessageBox::Cancel)
+			if (!KsWidgetsLib::fileExistsDialog(fileName))
 				return;
 		}
 	}
@@ -461,8 +448,13 @@ void KsMainWindow::_exportFilter()
 	if (fileName.isEmpty())
 		return;
 
-	if (!fileName.endsWith(".json"))
+	if (!fileName.endsWith(".json")) {
 		fileName += ".json";
+		if (QFileInfo(fileName).exists()) {
+			if (!KsWidgetsLib::fileExistsDialog(fileName))
+				return;
+		}
+	}
 
 	kshark_export_all_event_filters(kshark_ctx, &conf);
 	kshark_save_config_file(fileName.toStdString().c_str(), conf);
diff --git a/kernel-shark-qt/src/KsWidgetsLib.cpp b/kernel-shark-qt/src/KsWidgetsLib.cpp
index f7fca09..b4b62a4 100644
--- a/kernel-shark-qt/src/KsWidgetsLib.cpp
+++ b/kernel-shark-qt/src/KsWidgetsLib.cpp
@@ -76,6 +76,35 @@ KsMessageDialog::KsMessageDialog(QString message, QWidget *parent)
 	this->setLayout(&_layout);
 }
 
+namespace KsWidgetsLib
+{
+
+/**
+ * @brief Launch a File exists dialog. Use this function to ask the user
+ * before overwriting an existing file.
+ *
+ * @param fileName: the name of the file.
+ *
+ * @returns True if the user wants to overwrite the file. Otherwise
+ */
+bool fileExistsDialog(QString fileName)
+{
+	QString msg("A file ");
+	QMessageBox msgBox;
+
+	msg += fileName;
+	msg += " already exists.";
+	msgBox.setText(msg);
+	msgBox.setInformativeText("Do you want to replace it?");
+
+	msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
+	msgBox.setDefaultButton(QMessageBox::Cancel);
+
+	return (msgBox.exec() == QMessageBox::Save);
+}
+
+}; // KsWidgetsLib
+
 /**
  * @brief Create KsCheckBoxWidget.
  *
diff --git a/kernel-shark-qt/src/KsWidgetsLib.hpp b/kernel-shark-qt/src/KsWidgetsLib.hpp
index b9ba35a..89c196a 100644
--- a/kernel-shark-qt/src/KsWidgetsLib.hpp
+++ b/kernel-shark-qt/src/KsWidgetsLib.hpp
@@ -66,6 +66,13 @@ public:
 /** The width of the KsMessageDialog widget. */
 #define KS_MSG_DIALOG_WIDTH  (SCREEN_WIDTH / 10)
 
+namespace KsWidgetsLib
+{
+
+bool fileExistsDialog(QString fileName);
+
+}; // KsWidgetsLib
+
 /**
  * The KsCheckBoxWidget class is the base class of all CheckBox widget used
  * by KernelShark.
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 13/23] kernel-shark-qt: Fix the glitches in the preemption time visualization
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (11 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 12/23] kernel-shark-qt: Add "File exists" dialog Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 14/23] kernel-shark-qt: Add dialog for of trace data recording Yordan Karadzhov
                   ` (8 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel

This problem was reported by Steven Rostedt. The reason for having
the problem was my wrong assumption that for a given task the
sched_switch event is always the last record before the task is
preempted.

The patch changes two things:
   It modifies the "match" functions used to search for sched events,
   making these functions to trigger only on sched_switch/_wakeup events.
   This eliminates the flakiness due to the fact that sometimes the
   sched_switch can happend to be in the same bin with some of the trailing
   events from the same task. This also has the side effect of simplifying
   the code.

   It introduces a second pass over the data, which is task-specific and
   gets executed only the first time the task is plotted. This second pass
   edits the "pid" field of the last trailing event making it equal to the
   "next pid" field of the sched_switch event.

Signed-off-by: Yordan Karadzhov <ykaradzhov@vmware.com>
---
 kernel-shark-qt/src/plugins/SchedEvents.cpp | 126 ++++++++++++++------
 kernel-shark-qt/src/plugins/sched_events.c  |  13 +-
 2 files changed, 94 insertions(+), 45 deletions(-)

diff --git a/kernel-shark-qt/src/plugins/SchedEvents.cpp b/kernel-shark-qt/src/plugins/SchedEvents.cpp
index 713feb4..7f75baa 100644
--- a/kernel-shark-qt/src/plugins/SchedEvents.cpp
+++ b/kernel-shark-qt/src/plugins/SchedEvents.cpp
@@ -16,6 +16,7 @@
 
 // C++ 11
 #include<functional>
+#include<unordered_set>
 
 // KernelShark
 #include "libkshark.h"
@@ -29,25 +30,12 @@
 
 #define PLUGIN_MAX_ENTRIES_PER_BIN 500
 
+#define KS_TASK_COLLECTION_MARGIN 25
+
 //! @endcond
 
 extern struct plugin_sched_context *plugin_sched_context_handler;
 
-static int plugin_get_wakeup_pid(kshark_context *kshark_ctx,
-				 plugin_sched_context *plugin_ctx,
-				 const struct kshark_entry *e)
-{
-	struct tep_record *record;
-	unsigned long long val;
-
-	record = kshark_read_at(kshark_ctx, e->offset);
-	tep_read_number_field(plugin_ctx->sched_wakeup_pid_field,
-			      record->data, &val);
-	free_record(record);
-
-	return val;
-}
-
 /** Sched Event identifier. */
 enum class SchedEvent {
 	/** Sched Switch Event. */
@@ -139,23 +127,16 @@ static void pluginDraw(plugin_sched_context *plugin_ctx,
 			ksmodel_get_entry_back(histo, bin, false,
 					       plugin_wakeup_match_pid, pid,
 					       col, nullptr);
-		int wakeup_pid;
-
-		if (entryB &&
-		    plugin_ctx->sched_wakeup_event &&
-		    entryB->event_id == plugin_ctx->sched_wakeup_event->id) {
-			wakeup_pid =
-				plugin_get_wakeup_pid(kshark_ctx, plugin_ctx, entryB);
-			if (wakeup_pid == pid) {
-				/*
-				 * entryB is a sched_wakeup_event. Open a
-				 * green box here.
-				 */
-				openBox(graph->getBin(bin)._base);
 
-				 /* Green */
-				rec->_color = KsPlot::Color(0, 255, 0);
-			}
+		if (entryB) {
+			/*
+			 * entryB is a sched_wakeup_event. Open a
+			 * green box here.
+			 */
+			openBox(graph->getBin(bin)._base);
+
+			/* Green */
+			rec->_color = KsPlot::Color(0, 255, 0);
 		}
 	};
 
@@ -171,10 +152,7 @@ static void pluginDraw(plugin_sched_context *plugin_ctx,
 					       plugin_switch_match_pid, pid,
 					       col, nullptr);
 
-		if (entryB &&
-		    entryB->pid != pid &&
-		    plugin_ctx->sched_switch_event &&
-		    entryB->event_id == plugin_ctx->sched_switch_event->id) {
+		if (entryB && entryB->pid != pid) {
 			/*
 			 * entryB is a sched_switch_event. Open a
 			 * red box here.
@@ -215,6 +193,67 @@ static void pluginDraw(plugin_sched_context *plugin_ctx,
 	return;
 }
 
+static std::unordered_set<int> secondPassDone;
+
+/*
+ * Ideally, the sched_switch has to be the last trace event recorded before the
+ * task is preempted. Because of this, when the data is loaded (the first pass),
+ * the "pid" field of the sched_switch entries gets edited by this plugin to be
+ * equal to the "next pid" of the sched_switch event. However, in reality the
+ * sched_switch event may be followed by some trailing events from the same task
+ * (printk events for example). This has the effect of extending the graph of
+ * the task outside of the actual duration of the task. The "second pass" over
+ * the data is used to fix this problem. It takes advantage of the "next" field
+ * of the entry (this field is set during the first pass) to search for trailing
+ * events after the "sched_switch".
+ */
+static void secondPass(kshark_entry **data,
+		       kshark_entry_collection *col,
+		       int pid)
+{
+	const kshark_entry *e;
+	kshark_entry *last;
+	int first, n;
+	ssize_t index;
+
+	/* Loop over the intervals of the data collection. */
+	for (size_t i = 0; i < col->size; ++i) {
+		first = col->break_points[i];
+		n = first - col->resume_points[i];
+
+		kshark_entry_request *req =
+			kshark_entry_request_alloc(first, n,
+						   plugin_switch_match_pid, pid,
+						   false,
+						   KS_GRAPH_VIEW_FILTER_MASK);
+
+		e = kshark_get_entry_back(req, data, &index);
+		free(req);
+
+		if (!e || index < 0) {
+			/* No sched_switch event in this interval. */
+			continue;
+		}
+
+		/* Find the very last trailing event. */
+		for (last = data[index]; last->next; last = last->next) {
+			if (last->next->pid != pid) {
+				/*
+				 * This is the last trailing event. Change the
+				 * "pid" to be equal to the "next pid" of the
+				 * sched_switch event and leave a sign that you
+				 * edited this entry.
+				 */
+				last->pid = data[index]->pid;
+				last->visible &= ~KS_PLUGIN_UNTOUCHED_MASK;
+				break;
+			}
+		}
+	}
+
+	secondPassDone.insert(pid);
+}
+
 /**
  * @brief Plugin's draw function.
  *
@@ -246,8 +285,25 @@ void plugin_draw(kshark_cpp_argv *argv_c, int pid, int draw_action)
 	 */
 	col = kshark_find_data_collection(kshark_ctx->collections,
 					  kshark_match_pid, pid);
+	if (!col) {
+		/*
+		 * If a data collection for this task does not exist,
+		 * register a new one.
+		 */
+		kshark_entry **data = argvCpp->_histo->data;
+		int size = argvCpp->_histo->data_size;
+		col = kshark_register_data_collection(kshark_ctx,
+						      data, size,
+						      kshark_match_pid, pid,
+						      KS_TASK_COLLECTION_MARGIN);
+	}
 
 	try {
+		if (secondPassDone.find(pid) == secondPassDone.end()) {
+			/* The second pass for this task is not done yet. */
+			secondPass(argvCpp->_histo->data, col, pid);
+		}
+
 		pluginDraw(plugin_ctx, kshark_ctx,
 			   argvCpp->_histo, col,
 			   SchedEvent::Switch, pid,
diff --git a/kernel-shark-qt/src/plugins/sched_events.c b/kernel-shark-qt/src/plugins/sched_events.c
index 13f3c4a..80ac214 100644
--- a/kernel-shark-qt/src/plugins/sched_events.c
+++ b/kernel-shark-qt/src/plugins/sched_events.c
@@ -167,9 +167,6 @@ bool plugin_wakeup_match_pid(struct kshark_context *kshark_ctx,
 	unsigned long long val;
 	int wakeup_pid = -1;
 
-	if (e->pid == pid)
-		return true;
-
 	plugin_ctx = plugin_sched_context_handler;
 	if (!plugin_ctx)
 		return false;
@@ -221,23 +218,19 @@ bool plugin_switch_match_pid(struct kshark_context *kshark_ctx,
 			     int pid)
 {
 	struct plugin_sched_context *plugin_ctx;
-	struct tep_record *record = NULL;
 	int switch_pid = -1;
 
-	if (e->pid == pid)
-		return true;
-
 	plugin_ctx = plugin_sched_context_handler;
 
 	if (plugin_ctx->sched_switch_event &&
 	    e->event_id == plugin_ctx->sched_switch_event->id) {
-		record = kshark_read_at(kshark_ctx, e->offset);
+		struct tep_record *record;
 
+		record = kshark_read_at(kshark_ctx, e->offset);
 		switch_pid = tep_data_pid(plugin_ctx->pevent, record);
+		free_record(record);
 	}
 
-	free_record(record);
-
 	if (switch_pid >= 0 && switch_pid == pid)
 		return true;
 
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 14/23] kernel-shark-qt: Add dialog for of trace data recording
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (12 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 13/23] kernel-shark-qt: Fix the glitches in the preemption time visualization Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 15/23] kernel-shark-qt: Add kshark-record executable Yordan Karadzhov
                   ` (7 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

This patch defines a dialog to be used for trace data recording.
The dialog has two main areas: a Control panel and a terminal-like
widget for monitoring the recording process.

Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
---
 kernel-shark-qt/src/CMakeLists.txt      |   2 +
 kernel-shark-qt/src/KsCaptureDialog.cpp | 562 ++++++++++++++++++++++++
 kernel-shark-qt/src/KsCaptureDialog.hpp | 185 ++++++++
 3 files changed, 749 insertions(+)
 create mode 100644 kernel-shark-qt/src/KsCaptureDialog.cpp
 create mode 100644 kernel-shark-qt/src/KsCaptureDialog.hpp

diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
index b51980f..3c9e1bf 100644
--- a/kernel-shark-qt/src/CMakeLists.txt
+++ b/kernel-shark-qt/src/CMakeLists.txt
@@ -39,6 +39,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
                         KsTraceGraph.hpp
                         KsTraceViewer.hpp
                         KsMainWindow.hpp
+                        KsCaptureDialog.hpp
                         KsAdvFilteringDialog.hpp)
 
     QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr})
@@ -52,6 +53,7 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
                                                             KsTraceGraph.cpp
                                                             KsTraceViewer.cpp
                                                             KsMainWindow.cpp
+                                                            KsCaptureDialog.cpp
                                                             KsAdvFilteringDialog.cpp)
 
     target_link_libraries(kshark-gui kshark-plot
diff --git a/kernel-shark-qt/src/KsCaptureDialog.cpp b/kernel-shark-qt/src/KsCaptureDialog.cpp
new file mode 100644
index 0000000..ee1abc3
--- /dev/null
+++ b/kernel-shark-qt/src/KsCaptureDialog.cpp
@@ -0,0 +1,562 @@
+// SPDX-License-Identifier: LGPL-2.1
+
+/*
+ * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsCaptureDialog.cpp
+ *  @brief   Dialog for trace data recording.
+ */
+
+// Qt
+#include <QLocalSocket>
+
+// KernelShark
+#include "libkshark.h"
+#include "KsUtils.hpp"
+#include "KsCmakeDef.hpp"
+#include "KsCaptureDialog.hpp"
+
+static inline tep_handle *local_events()
+{
+	return tracecmd_local_events(tracecmd_get_tracing_dir());
+}
+
+/** @brief Create KsCaptureControl widget. */
+KsCaptureControl::KsCaptureControl(QWidget *parent)
+: QWidget(parent),
+  _localTEP(local_events()),
+  _eventsWidget(_localTEP, this),
+  _pluginsLabel("Plugin: ", this),
+  _outputLabel("Output file: ", this),
+  _commandLabel("Command: ", this),
+  _outputLineEdit("trace.dat", this),
+  _commandLineEdit("sleep 0.1", this),
+  _settingsToolBar(this),
+  _controlToolBar(this),
+  _pluginsComboBox(this),
+  _importSettingsButton("Import Settings", this),
+  _exportSettingsButton("Export Settings", this),
+  _outputBrowseButton("Browse", this),
+  _commandCheckBox("Display output", this),
+  _captureButton("Capture", &_controlToolBar),
+  _applyButton("Apply", &_controlToolBar),
+  _closeButton("Close", &_controlToolBar)
+{
+	QStringList pluginList = _getPlugins();
+	int row(0);
+
+	auto lamAddLine = [&] {
+		QFrame* line = new QFrame();
+
+		line->setFrameShape(QFrame::HLine);
+		line->setFrameShadow(QFrame::Sunken);
+		_topLayout.addWidget(line);
+	};
+
+	if (pluginList.count() == 0) {
+		/*
+		 * No plugins have been found. Most likely this is because
+		 * the process has no Root privileges.
+		 */
+		QString message("Error: No events or plugins found.\n");
+		message += "Root privileges are required.";
+		QLabel *errorLabel = new QLabel(message);
+
+		errorLabel->setStyleSheet("QLabel {color : red;}");
+		_topLayout.addWidget(errorLabel);
+
+		lamAddLine();
+	}
+
+	pluginList.prepend("nop");
+
+	_settingsToolBar.addWidget(&_importSettingsButton);
+	_settingsToolBar.addSeparator();
+	_settingsToolBar.addWidget(&_exportSettingsButton);
+	_topLayout.addWidget(&_settingsToolBar);
+
+	lamAddLine();
+
+	_eventsWidget.setDefault(false);
+	_eventsWidget.setMinimumHeight(25 * FONT_HEIGHT);
+	_topLayout.addWidget(&_eventsWidget);
+
+	_pluginsLabel.adjustSize();
+	_execLayout.addWidget(&_pluginsLabel, row, 0);
+
+	_pluginsComboBox.addItems(pluginList);
+	_execLayout.addWidget(&_pluginsComboBox, row++, 1);
+
+	_outputLabel.adjustSize();
+	_execLayout.addWidget(&_outputLabel, row, 0);
+	_outputLineEdit.setFixedWidth(FONT_WIDTH * 30);
+	_execLayout.addWidget(&_outputLineEdit, row, 1);
+	_outputBrowseButton.adjustSize();
+	_execLayout.addWidget(&_outputBrowseButton, row++, 2);
+
+	_commandLabel.adjustSize();
+	_commandLabel.setFixedWidth(_outputLabel.width());
+	_execLayout.addWidget(&_commandLabel, row, 0);
+	_commandLineEdit.setFixedWidth(FONT_WIDTH * 30);
+	_execLayout.addWidget(&_commandLineEdit, row, 1);
+	_commandCheckBox.setCheckState(Qt::Unchecked);
+	_commandCheckBox.adjustSize();
+	_execLayout.addWidget(&_commandCheckBox, row++, 2);
+
+	_topLayout.addLayout(&_execLayout);
+
+	lamAddLine();
+
+	_captureButton.setFixedWidth(STRING_WIDTH("_Capture_") + FONT_WIDTH * 2);
+	_applyButton.setFixedWidth(_captureButton.width());
+	_closeButton.setFixedWidth(_captureButton.width());
+
+	_controlToolBar.addWidget(&_captureButton);
+	_controlToolBar.addWidget(&_applyButton);
+	_controlToolBar.addWidget(&_closeButton);
+	_topLayout.addWidget(&_controlToolBar);
+
+	setLayout(&_topLayout);
+
+	connect(&_importSettingsButton,	&QPushButton::pressed,
+		this,			&KsCaptureControl::_importSettings);
+
+	connect(&_exportSettingsButton,	&QPushButton::pressed,
+		this,			&KsCaptureControl::_exportSettings);
+
+	connect(&_outputBrowseButton,	&QPushButton::pressed,
+		this,			&KsCaptureControl::_browse);
+
+	connect(&_applyButton,		&QPushButton::pressed,
+		this,			&KsCaptureControl::_apply);
+}
+
+/**
+ * Use the settings of the Control panel to generate a list of command line
+ * arguments for trace-cmd.
+ */
+QStringList KsCaptureControl::getArgs()
+{
+	QStringList argv;
+
+	argv << "record";
+	argv << "-p" << _pluginsComboBox.currentText();
+
+	if (_eventsWidget.all()) {
+		argv << "-e" << "all";
+	} else {
+		QVector<int> evtIds = _eventsWidget.getCheckedIds();
+		tep_event_format *event;
+
+		for (auto const &id: evtIds) {
+			event = tep_find_event(_localTEP, id);
+			if (!event)
+				continue;
+
+			argv << "-e" + QString(event->system) +
+				":" + QString(event->name);
+		}
+	}
+
+	argv << "-o" << outputFileName();
+	argv << _commandLineEdit.text().split(" ");
+
+	return argv;
+}
+
+QStringList KsCaptureControl::_getPlugins()
+{
+	QStringList pluginList;
+	char **all_plugins;
+
+	all_plugins = tracecmd_local_plugins(tracecmd_get_tracing_dir());
+
+	if (!all_plugins)
+		return pluginList;
+
+	for (int i = 0; all_plugins[i]; ++i) {
+		/*
+		 * TODO plugin selection here.
+		 * printf("plugin %i %s\n", i, all_plugins[i]);
+		 */
+		pluginList << all_plugins[i];
+		free(all_plugins[i]);
+	}
+
+	free (all_plugins);
+	qSort(pluginList);
+
+	return pluginList;
+}
+
+void KsCaptureControl::_importSettings()
+{
+	int nEvts = tep_get_events_count(_localTEP);
+	kshark_config_doc *conf, *jevents, *temp;
+	QVector<bool> v(nEvts, false);
+	tracecmd_filter_id *eventHash;
+	tep_event_format **events;
+	QString fileName;
+
+
+	/** Get all available events. */
+	events = tep_list_events(_localTEP, TEP_EVENT_SORT_SYSTEM);
+
+	/* Get the configuration document. */
+	fileName = QFileDialog::getOpenFileName(this,
+						"Import from Filter",
+						KS_DIR,
+						"Kernel Shark Config files (*.json);;");
+
+	if (fileName.isEmpty())
+		return;
+
+	conf = kshark_open_config_file(fileName.toStdString().c_str(),
+				       "kshark.config.record");
+	if (!conf)
+		return;
+
+	/*
+	 * Load the hash table of selected events from the configuration
+	 * document.
+	 */
+	jevents = kshark_config_alloc(KS_CONFIG_JSON);
+	if (!kshark_config_doc_get(conf, "Events", jevents))
+		return;
+
+	eventHash = tracecmd_filter_id_hash_alloc();
+	kshark_import_event_filter(_localTEP, eventHash, "Events", jevents);
+	for (int i = 0; i < nEvts; ++i) {
+		if (tracecmd_filter_id_find(eventHash, events[i]->id))
+			v[i] = true;
+	}
+
+	_eventsWidget.set(v);
+	tracecmd_filter_id_hash_free(eventHash);
+
+	/** Get all available plugins. */
+	temp = kshark_string_config_alloc();
+
+	if (kshark_config_doc_get(conf, "Plugin", temp))
+		_pluginsComboBox.setCurrentText(KS_C_STR_CAST(temp->conf_doc));
+
+	if (kshark_config_doc_get(conf, "Output", temp))
+		_outputLineEdit.setText(KS_C_STR_CAST(temp->conf_doc));
+
+	if (kshark_config_doc_get(conf, "Command", temp))
+		_commandLineEdit.setText(KS_C_STR_CAST(temp->conf_doc));
+}
+
+void KsCaptureControl::_exportSettings()
+{
+	kshark_config_doc *conf, *events;
+	json_object *jplugin;
+	QString plugin, out, comm;
+	QVector<int> ids;
+	QString fileName =
+		QFileDialog::getSaveFileName(this,
+					     "Export to File",
+					     KS_DIR,
+					     "Kernel Shark Config files (*.json);;");
+
+	if (fileName.isEmpty())
+		return;
+
+	if (!fileName.endsWith(".json")) {
+		fileName += ".json";
+		if (QFileInfo(fileName).exists()) {
+			if (!KsWidgetsLib::fileExistsDialog(fileName))
+				return;
+		}
+	}
+
+	/* Create a configuration document. */
+	conf = kshark_record_config_new(KS_CONFIG_JSON);
+	events = kshark_filter_config_new(KS_CONFIG_JSON);
+
+	/*
+	 * Use the tracecmd_filter_id to save all selected events in the
+	 * configuration file.
+	 */
+	ids = _eventsWidget.getCheckedIds();
+	tracecmd_filter_id *eventHash = tracecmd_filter_id_hash_alloc();
+	for (auto const &id: ids)
+		tracecmd_filter_id_add(eventHash, id);
+
+	kshark_export_event_filter(_localTEP, eventHash, "Events", events);
+	kshark_config_doc_add(conf, "Events", events);
+
+	tracecmd_filter_id_hash_free(eventHash);
+
+	/* Save the plugin. */
+	plugin = _pluginsComboBox.currentText();
+	jplugin = json_object_new_string(plugin.toStdString().c_str());
+	kshark_config_doc_add(conf, "Plugin", kshark_json_to_conf(jplugin));
+
+	/* Save the output file. */
+	out = outputFileName();
+	json_object *jout = json_object_new_string(out.toStdString().c_str());
+	kshark_config_doc_add(conf, "Output", kshark_json_to_conf(jout));
+
+	/* Save the command. */
+	comm = _commandLineEdit.text();
+	json_object *jcomm = json_object_new_string(comm.toStdString().c_str());
+	kshark_config_doc_add(conf, "Command", kshark_json_to_conf(jcomm));
+
+	kshark_save_config_file(fileName.toStdString().c_str(), conf);
+}
+
+void KsCaptureControl::_browse()
+{
+	QString fileName =
+		QFileDialog::getSaveFileName(this,
+					     "Save File",
+					     KS_DIR,
+					     "trace-cmd files (*.dat);;All files (*)");
+
+	if (!fileName.isEmpty())
+		_outputLineEdit.setText(fileName);
+}
+
+void KsCaptureControl::_apply()
+{
+	emit argsReady(getArgs().join(" "));
+}
+
+/** @brief Create KsCaptureMonitor widget. */
+KsCaptureMonitor::KsCaptureMonitor(QWidget *parent)
+: QWidget(parent),
+  _mergedChannels(false),
+  _argsModified(false),
+  _panel(this),
+  _name("Output display", this),
+  _space("max size ", this),
+  _readOnlyCB("read only", this),
+  _maxLinNumEdit(QString("%1").arg(KS_CAP_MON_MAX_LINE_NUM), this),
+  _consolOutput("", this)
+{
+	_panel.setMaximumHeight(FONT_HEIGHT * 1.75);
+	_panel.addWidget(&_name);
+
+	_space.setAlignment(Qt::AlignRight);
+	_panel.addWidget(&_space);
+
+	_maxLinNumEdit.setFixedWidth(FONT_WIDTH * 7);
+	_panel.addWidget(&_maxLinNumEdit);
+	_panel.addSeparator();
+	_readOnlyCB.setCheckState(Qt::Checked);
+	_panel.addWidget(&_readOnlyCB);
+	_layout.addWidget(&_panel);
+
+	_consolOutput.setStyleSheet("QLabel {background-color : white;}");
+	_consolOutput.setMinimumWidth(FONT_WIDTH * 60);
+	_consolOutput.setMinimumHeight(FONT_HEIGHT * 10);
+	_consolOutput.setMaximumBlockCount(KS_CAP_MON_MAX_LINE_NUM);
+
+	_space.setMinimumWidth(FONT_WIDTH * 50 - _name.width() - _readOnlyCB.width());
+	_consolOutput.setReadOnly(true);
+	_layout.addWidget(&_consolOutput);
+
+	this->setLayout(&_layout);
+
+	connect(&_maxLinNumEdit,	&QLineEdit::textChanged,
+		this,			&KsCaptureMonitor::_maxLineNumber);
+
+	connect(&_readOnlyCB,		&QCheckBox::stateChanged,
+		this,			&KsCaptureMonitor::_readOnly);
+
+	connect(&_consolOutput,		&QPlainTextEdit::textChanged,
+		this,			&KsCaptureMonitor::_argVModified);
+
+	this->show();
+}
+
+void KsCaptureMonitor::_maxLineNumber(const QString &test)
+{
+	bool ok;
+	int max = test.toInt(&ok);
+
+	if (ok)
+		_consolOutput.setMaximumBlockCount(max);
+}
+
+void KsCaptureMonitor::_readOnly(int state)
+{
+	if (state == Qt::Checked)
+		_consolOutput.setReadOnly(true);
+	else
+		_consolOutput.setReadOnly(false);
+}
+
+void KsCaptureMonitor::_argsReady(const QString &args)
+{
+	_name.setText("Capture options:");
+	_consolOutput.setPlainText(args);
+	_argsModified = false;
+}
+
+void KsCaptureMonitor::_argVModified()
+{
+	_argsModified = true;
+}
+
+void KsCaptureMonitor::_printAllStandardError()
+{
+	QProcess *_capture = (QProcess*) sender();
+
+	_consolOutput.moveCursor(QTextCursor::End);
+	_consolOutput.insertPlainText(_capture->readAllStandardError());
+	_consolOutput.moveCursor(QTextCursor::End);
+	QCoreApplication::processEvents();
+}
+
+void KsCaptureMonitor::_printAllStandardOutput()
+{
+	QProcess *_capture = (QProcess*) sender();
+
+	if (!_mergedChannels)
+		return;
+
+	_consolOutput.appendPlainText(_capture->readAllStandardOutput());
+	QCoreApplication::processEvents();
+}
+
+/**
+ * Connect the Capture monitor widget to the signals of the recording process.
+ */
+void KsCaptureMonitor::connectMe(QProcess *proc, KsCaptureControl *ctrl)
+{
+	connect(proc,	&QProcess::started,
+		this,	&KsCaptureMonitor::_captureStarted);
+
+	/* Using the old Signal-Slot syntax because QProcess::finished has overloads. */
+	connect(proc,	SIGNAL(finished(int, QProcess::ExitStatus)),
+		this,	SLOT(_captureFinished(int, QProcess::ExitStatus)));
+
+	connect(proc,	&QProcess::readyReadStandardError,
+		this,	&KsCaptureMonitor::_printAllStandardError);
+
+	connect(proc,	&QProcess::readyReadStandardOutput,
+		this,	&KsCaptureMonitor::_printAllStandardOutput);
+
+	connect(ctrl,	&KsCaptureControl::argsReady,
+		this,	&KsCaptureMonitor::_argsReady);
+}
+
+void KsCaptureMonitor::_captureStarted()
+{
+	_name.setText("Terminal output:");
+	_readOnlyCB.setCheckState(Qt::Checked);
+
+	QCoreApplication::processEvents();
+}
+
+void KsCaptureMonitor::_captureFinished(int exit, QProcess::ExitStatus status)
+{
+	QProcess *_capture = (QProcess *)sender();
+
+	if (exit != 0 || status != QProcess::NormalExit) {
+		QString errMessage("Capture process failed: ");
+
+		errMessage += _capture->errorString();
+		_consolOutput.appendPlainText(errMessage);
+
+		QCoreApplication::processEvents();
+	}
+}
+
+/** Print a message. */
+void KsCaptureMonitor::print(const QString &message)
+{
+	_consolOutput.appendPlainText(message);
+}
+
+/** @brief Create KsCaptureDialog widget. */
+KsCaptureDialog::KsCaptureDialog(QWidget *parent)
+: QWidget(parent),
+  _captureCtrl(this),
+  _captureMon(this),
+  _captureProc(this)
+{
+	QString captureExe(TRACECMD_BIN_DIR);
+
+	this->setWindowTitle("Capture");
+	_layout.addWidget(&_captureCtrl);
+	_layout.addWidget(&_captureMon);
+	this->setLayout(&_layout);
+
+	connect(&_captureCtrl._commandCheckBox,	&QCheckBox::stateChanged,
+		this,				&KsCaptureDialog::_setChannelMode);
+
+	connect(&_captureCtrl._captureButton,	&QPushButton::pressed,
+		this,				&KsCaptureDialog::_capture);
+
+	connect(&_captureCtrl._closeButton,	&QPushButton::pressed,
+		this,				&KsCaptureDialog::close);
+
+	captureExe += "/trace-cmd";
+	_captureProc.setProgram(captureExe);
+
+	_captureMon.connectMe(&_captureProc, &_captureCtrl);
+}
+
+void KsCaptureDialog::_capture()
+{
+	QStringList argv;
+	int argc;
+
+	if(_captureMon._argsModified) {
+		argv = _captureMon.text().split(" ");
+	} else {
+		argv = _captureCtrl.getArgs();
+	}
+
+	_captureMon.print("\n");
+	_captureMon.print(QString("trace-cmd " + argv.join(" ") + "\n"));
+	_captureProc.setArguments(argv);
+	_captureProc.start();
+	_captureProc.waitForFinished();
+
+	argc = argv.count();
+	for (int i = 0; i < argc; ++i) {
+		if (argv[i] == "-o") {
+			_sendOpenReq(argv[i + 1]);
+			break;
+		}
+	}
+
+	/* Reset the _argsModified flag. */
+	_captureMon._argsModified = false;
+}
+
+void KsCaptureDialog::_setChannelMode(int state)
+{
+	if (state > 0) {
+		_captureMon._mergedChannels = true;
+	} else {
+		_captureMon._mergedChannels = false;
+	}
+}
+
+void KsCaptureDialog::_sendOpenReq(const QString &fileName)
+{
+	QLocalSocket *socket = new QLocalSocket(this);
+
+	socket->connectToServer("KSCapture", QIODevice::WriteOnly);
+	if (socket->waitForConnected()) {
+		QByteArray block;
+		QDataStream out(&block, QIODevice::WriteOnly);
+		const QString message = fileName;
+
+		out << quint32(message.size());
+		out << message;
+
+		socket->write(block);
+		socket->flush();
+		socket->disconnectFromServer();
+	} else {
+		_captureMon.print(socket->errorString());
+	}
+}
diff --git a/kernel-shark-qt/src/KsCaptureDialog.hpp b/kernel-shark-qt/src/KsCaptureDialog.hpp
new file mode 100644
index 0000000..d65f475
--- /dev/null
+++ b/kernel-shark-qt/src/KsCaptureDialog.hpp
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: LGPL-2.1 */
+
+/*
+ * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsCaptureDialog.hpp
+ *  @brief   Dialog for trace data recording.
+ */
+
+#ifndef _KS_CAPTURE_H
+#define _KS_CAPTURE_H
+
+// Qt
+#include <QtWidgets>
+
+// KernelShark
+#include "KsWidgetsLib.hpp"
+
+/**
+ * The KsCaptureControl class provides a control panel for the KernelShark
+ * Capture dialog.
+ */
+class KsCaptureControl : public QWidget
+{
+	Q_OBJECT
+public:
+	explicit KsCaptureControl(QWidget *parent = 0);
+
+	QStringList getArgs();
+
+	/** Get the name of the tracing data output file. */
+	QString outputFileName() const {return _outputLineEdit.text();}
+
+	/** Set the name of the tracing data output file. */
+	void setOutputFileName(const QString &f) {_outputLineEdit.setText(f);}
+
+signals:
+	/** This signal is emitted when the "Apply" button is pressed. */
+	void argsReady(const QString &args);
+
+private:
+	tep_handle		*_localTEP;
+
+	KsEventsCheckBoxWidget	_eventsWidget;
+
+	QVBoxLayout	_topLayout;
+
+	QGridLayout	_execLayout;
+
+	QLabel		_pluginsLabel, _outputLabel, _commandLabel;
+
+	QLineEdit	_outputLineEdit, _commandLineEdit;
+
+	QToolBar	_settingsToolBar, _controlToolBar;
+
+	QComboBox	_pluginsComboBox;
+
+	QPushButton	_importSettingsButton, _exportSettingsButton;
+
+	QPushButton	_outputBrowseButton;
+
+	QStringList _getPlugins();
+
+	void _importSettings();
+
+	void _exportSettings();
+
+	void _browse();
+
+	void _apply();
+
+public:
+	/**
+	 * A Check box used to indicate if the output of the command needs to
+	 * be shown by the KsCaptureMonitor widget.
+	 */
+	QCheckBox	_commandCheckBox;
+
+	/** Capture button for the control panel. */
+	QPushButton	_captureButton;
+
+	/** Apply button for the control panel. */
+	QPushButton	_applyButton;
+
+	/** Close button for the control panel. */
+	QPushButton	_closeButton;
+};
+
+/**
+ * The KsCaptureMonitor class provides a terminal-like widget for monitoring
+ * the tracing data recording process.
+ */
+class KsCaptureMonitor : public QWidget
+{
+	Q_OBJECT
+public:
+	explicit KsCaptureMonitor(QWidget *parent = 0);
+
+	/** Get the text shown by the widget. */
+	QString text() const {return _consolOutput.toPlainText();}
+
+	/** Clear the text shown by the widget. */
+	void clear() {_consolOutput.clear();}
+
+	void print(const QString &message);
+
+	void connectMe(QProcess *proc, KsCaptureControl *ctrl);
+
+	/** A flag indicating if the stdout and stderr channels are _merged. */
+	bool		_mergedChannels;
+
+	/**
+	 * A flag indicating, if the list of the command line arguments for trace-cmd
+	 * has been edited by the user.
+	 */
+	bool		_argsModified;
+
+private:
+	QVBoxLayout	_layout;
+
+	QToolBar	_panel;
+
+	QLabel		_name, _space;
+
+	QCheckBox	_readOnlyCB;
+
+	QLineEdit	_maxLinNumEdit;
+
+	QPlainTextEdit	_consolOutput;
+
+	void _argsReady(const QString &test);
+
+	void _maxLineNumber(const QString &test);
+
+	void _readOnly(int);
+
+	void _argVModified();
+
+	void _captureStarted();
+
+	void _printAllStandardError();
+
+	void _printAllStandardOutput();
+
+private slots:
+	void _captureFinished(int, QProcess::ExitStatus);
+};
+
+/** Default number of lines shown by the KsCaptureMonitor widget. */
+#define KS_CAP_MON_MAX_LINE_NUM 200
+
+/**
+ * The KsCaptureDialog class provides a dialog for recording of tracing data.
+ */
+class KsCaptureDialog : public QWidget
+{
+	Q_OBJECT
+public:
+	explicit KsCaptureDialog(QWidget *parent = 0);
+
+	/** Set the name of the tracing data output file. */
+	void setOutputFileName(const QString &f)
+	{
+		_captureCtrl.setOutputFileName(f);
+	}
+
+private:
+	QHBoxLayout		_layout;
+
+	KsCaptureControl	_captureCtrl;
+
+	KsCaptureMonitor	_captureMon;
+
+	QProcess		_captureProc;
+
+	void _capture();
+
+	void _setChannelMode(int state);
+
+	void _sendOpenReq(const QString &fileName);
+};
+
+#endif // _KS_CAPTURE_H
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 15/23] kernel-shark-qt: Add kshark-record executable
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (13 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 14/23] kernel-shark-qt: Add dialog for of trace data recording Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 16/23] kernel-shark-qt: Instruct CMake to search for "pkexec" Yordan Karadzhov
                   ` (6 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

kshark-record provides a simple GUI (dialog) used to generate command line
arguments for trace-cmd and to record trace data.

Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
---
 kernel-shark-qt/src/CMakeLists.txt    |  4 ++++
 kernel-shark-qt/src/kshark-record.cpp | 29 +++++++++++++++++++++++++++
 2 files changed, 33 insertions(+)
 create mode 100644 kernel-shark-qt/src/kshark-record.cpp

diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
index 3c9e1bf..3b47ce1 100644
--- a/kernel-shark-qt/src/CMakeLists.txt
+++ b/kernel-shark-qt/src/CMakeLists.txt
@@ -69,6 +69,10 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
     add_executable(kernelshark          kernelshark.cpp)
     target_link_libraries(kernelshark   kshark-gui)
 
+    message(STATUS "kshark-record")
+    add_executable(kshark-record        kshark-record.cpp)
+    target_link_libraries(kshark-record kshark-gui)
+
 endif (Qt5Widgets_FOUND AND Qt5Network_FOUND)
 
 add_subdirectory(plugins)
diff --git a/kernel-shark-qt/src/kshark-record.cpp b/kernel-shark-qt/src/kshark-record.cpp
new file mode 100644
index 0000000..7d10469
--- /dev/null
+++ b/kernel-shark-qt/src/kshark-record.cpp
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2018 VMware Inc, Yordan Karadzhov <y.karadz@gmail.com>
+ */
+
+// C
+#include <unistd.h>
+
+// KernelShark
+#include "KsCaptureDialog.hpp"
+
+int main(int argc, char **argv)
+{
+	QApplication a(argc, argv);
+	KsCaptureDialog cd;
+
+	int c;
+	while ((c = getopt(argc, argv, "o:")) != -1) {
+		switch(c) {
+		case 'o':
+			cd.setOutputFileName(QString(optarg));
+			break;
+		}
+	}
+
+	cd.show();
+	return a.exec();
+}
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 16/23] kernel-shark-qt: Instruct CMake to search for "pkexec"
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (14 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 15/23] kernel-shark-qt: Add kshark-record executable Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 17/23] kernel-shark-qt: Add PolicyKit Configuration for kshark-record Yordan Karadzhov
                   ` (5 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

CMake will search for "pkexec" and if it is found will add a line

to the CMake-generated header file.

Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
---
 kernel-shark-qt/build/deff.h.cmake | 3 +++
 kernel-shark-qt/src/CMakeLists.txt | 2 ++
 2 files changed, 5 insertions(+)

diff --git a/kernel-shark-qt/build/deff.h.cmake b/kernel-shark-qt/build/deff.h.cmake
index d1a1bb7..80d624c 100644
--- a/kernel-shark-qt/build/deff.h.cmake
+++ b/kernel-shark-qt/build/deff.h.cmake
@@ -20,6 +20,9 @@
 /** Location of the trace-cmd executable. */
 #cmakedefine TRACECMD_BIN_DIR "@TRACECMD_BIN_DIR@"
 
+/** "pkexec" executable. */
+#cmakedefine DO_AS_ROOT "@DO_AS_ROOT@"
+
 #ifdef __cplusplus
 
 	#include <QString>
diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
index 3b47ce1..6819f86 100644
--- a/kernel-shark-qt/src/CMakeLists.txt
+++ b/kernel-shark-qt/src/CMakeLists.txt
@@ -77,5 +77,7 @@ endif (Qt5Widgets_FOUND AND Qt5Network_FOUND)
 
 add_subdirectory(plugins)
 
+find_program(DO_AS_ROOT pkexec)
+
 configure_file( ${KS_DIR}/build/deff.h.cmake
                 ${KS_DIR}/src/KsCmakeDef.hpp)
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 17/23] kernel-shark-qt: Add PolicyKit Configuration for kshark-record
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (15 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 16/23] kernel-shark-qt: Instruct CMake to search for "pkexec" Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 19/23] kernel-shark-qt: Add kernelshark.desktop file Yordan Karadzhov
                   ` (4 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel

The PolicyKit Policy Configuration  will allow the kshark-record
executable to be started as Root via pkexec.

Signed-off-by: Yordan Karadzhov <ykaradzhov@vmware.com>
---
 .../org.freedesktop.kshark-record.policy       | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
 create mode 100644 kernel-shark-qt/org.freedesktop.kshark-record.policy

diff --git a/kernel-shark-qt/org.freedesktop.kshark-record.policy b/kernel-shark-qt/org.freedesktop.kshark-record.policy
new file mode 100644
index 0000000..dc73817
--- /dev/null
+++ b/kernel-shark-qt/org.freedesktop.kshark-record.policy
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policyconfig PUBLIC
+  "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+  "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+<policyconfig>
+
+  <action id="org.freedesktop.pkexec.kshark-record">
+    <message gettext-domain="gparted">Authentication is required to run KernelShark Record</message>
+    <defaults>
+      <allow_any>auth_admin</allow_any>
+      <allow_inactive>auth_admin</allow_inactive>
+      <allow_active>auth_admin</allow_active>
+    </defaults>
+    <annotate key="org.freedesktop.policykit.exec.path">/usr/local/bin/kshark-record</annotate>
+    <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
+  </action>
+
+</policyconfig>
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 19/23] kernel-shark-qt: Add kernelshark.desktop file
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (16 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 17/23] kernel-shark-qt: Add PolicyKit Configuration for kshark-record Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 20/23] kernel-shark-qt: Add make install Yordan Karadzhov
                   ` (3 subsequent siblings)
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

Instruct CMake to generate kernelshark.desktop. This file is used to
add the KernelShark application in the desktop menus.

Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
---
 kernel-shark-qt/CMakeLists.txt         | 3 +++
 kernel-shark-qt/build/cmake_clean.sh   | 1 +
 kernel-shark-qt/build/ks.desktop.cmake | 9 +++++++++
 3 files changed, 13 insertions(+)
 create mode 100644 kernel-shark-qt/build/ks.desktop.cmake

diff --git a/kernel-shark-qt/CMakeLists.txt b/kernel-shark-qt/CMakeLists.txt
index 0ccb61e..278241b 100644
--- a/kernel-shark-qt/CMakeLists.txt
+++ b/kernel-shark-qt/CMakeLists.txt
@@ -75,4 +75,7 @@ if (_DOXYGEN_DOC AND DOXYGEN_FOUND)
 
 endif ()
 
+configure_file( ${KS_DIR}/build/ks.desktop.cmake
+                ${KS_DIR}/kernelshark.desktop)
+
 message("")
diff --git a/kernel-shark-qt/build/cmake_clean.sh b/kernel-shark-qt/build/cmake_clean.sh
index 4f984db..ea04dc0 100755
--- a/kernel-shark-qt/build/cmake_clean.sh
+++ b/kernel-shark-qt/build/cmake_clean.sh
@@ -6,6 +6,7 @@ rm -rf CMakeFiles/
 rm -rf src/
 rm -rf examples/
 rm -f ../lib/*
+rm ../kernelshark.desktop
 rm -f ../src/KsCmakeDef.hpp
 rm -f CMakeDoxyfile.in
 rm -f CMakeDoxygenDefaults.cmake
diff --git a/kernel-shark-qt/build/ks.desktop.cmake b/kernel-shark-qt/build/ks.desktop.cmake
new file mode 100644
index 0000000..0b947f1
--- /dev/null
+++ b/kernel-shark-qt/build/ks.desktop.cmake
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Version=@KS_VERSION_STRING@
+Type=Application
+Name=Kernel Shark
+GenericName=Kernel Shark
+Comment=
+Exec=/usr/local/bin/kernelshark
+Icon=@KS_DIR@/icons/ksharkicon.png
+Terminal=false
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 20/23] kernel-shark-qt: Add make install
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (17 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 19/23] kernel-shark-qt: Add kernelshark.desktop file Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-19 15:52   ` Steven Rostedt
  2018-10-16 15:53 ` [PATCH v2 21/23] kernel-shark-qt: Add Record dialog to KS GUI Yordan Karadzhov
                   ` (2 subsequent siblings)
  21 siblings, 1 reply; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

Define CMake installation rules for kernel-shark-qt project.
Add a script for uninstalling.

Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
---
 kernel-shark-qt/CMakeLists.txt             |  3 +++
 kernel-shark-qt/build/cmake_uninstall.sh   | 17 +++++++++++++++++
 kernel-shark-qt/src/CMakeLists.txt         | 10 ++++++++++
 kernel-shark-qt/src/plugins/CMakeLists.txt |  3 +++
 4 files changed, 33 insertions(+)
 create mode 100755 kernel-shark-qt/build/cmake_uninstall.sh

diff --git a/kernel-shark-qt/CMakeLists.txt b/kernel-shark-qt/CMakeLists.txt
index 278241b..767bec9 100644
--- a/kernel-shark-qt/CMakeLists.txt
+++ b/kernel-shark-qt/CMakeLists.txt
@@ -46,6 +46,9 @@ if (NOT _DEBUG)
 
 endif (NOT _DEBUG)
 
+SET(CMAKE_INSTALL_RPATH "/usr/local/lib/kshark/")
+SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
+
 include_directories(${KS_DIR}/src/
                     ${KS_DIR}/build/src/
                     ${JSONC_INCLUDE_DIR}
diff --git a/kernel-shark-qt/build/cmake_uninstall.sh b/kernel-shark-qt/build/cmake_uninstall.sh
new file mode 100755
index 0000000..50c163b
--- /dev/null
+++ b/kernel-shark-qt/build/cmake_uninstall.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+CYAN='\033[0;36m'
+PURPLE='\033[0;35m'
+NC='\033[0m' # No Color
+
+if [[ $EUID -ne 0 ]]; then
+   echo -e "${PURPLE}Permission denied${NC}" 1>&2
+   exit 100
+fi
+
+if [ -e install_manifest.txt ]
+then
+    echo -e "${CYAN}Uninstall the project...${NC}"
+    xargs rm -v < install_manifest.txt
+    rm -f install_manifest.txt
+fi
diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
index 6819f86..2592094 100644
--- a/kernel-shark-qt/src/CMakeLists.txt
+++ b/kernel-shark-qt/src/CMakeLists.txt
@@ -73,6 +73,16 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
     add_executable(kshark-record        kshark-record.cpp)
     target_link_libraries(kshark-record kshark-gui)
 
+    install(TARGETS kernelshark kshark-record kshark kshark-plot kshark-gui
+            RUNTIME DESTINATION /usr/local/bin/
+            LIBRARY DESTINATION /usr/local/lib/kshark/)
+
+    install(FILES "${KS_DIR}/kernelshark.desktop"
+            DESTINATION /usr/share/applications/)
+
+    install(FILES "${KS_DIR}/org.freedesktop.kshark-record.policy"
+            DESTINATION /usr/share/polkit-1/actions/)
+
 endif (Qt5Widgets_FOUND AND Qt5Network_FOUND)
 
 add_subdirectory(plugins)
diff --git a/kernel-shark-qt/src/plugins/CMakeLists.txt b/kernel-shark-qt/src/plugins/CMakeLists.txt
index 88fd93c..2d7251d 100644
--- a/kernel-shark-qt/src/plugins/CMakeLists.txt
+++ b/kernel-shark-qt/src/plugins/CMakeLists.txt
@@ -24,4 +24,7 @@ BUILD_PLUGIN(NAME sched_events
 list(APPEND PLUGIN_LIST "sched_events default") # This plugin will be loaded by default
 # list(APPEND PLUGIN_LIST "sched_events") # This plugin isn't loaded by default
 
+install(TARGETS sched_events
+        LIBRARY DESTINATION /usr/local/lib/kshark/)
+
 set(PLUGINS ${PLUGIN_LIST} PARENT_SCOPE)
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 21/23] kernel-shark-qt: Add Record dialog to KS GUI.
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (18 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 20/23] kernel-shark-qt: Add make install Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 22/23] kernel-shark-qt: Workaround for running as Root on Wayland Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 23/23] kernel-shark-qt: Version 0.9.0 Yordan Karadzhov
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel

A Record dialog is added to the Tools menu of the KernelShark GUI.
The Record dialog can be used only after installing the project
(sudo make install).

Signed-off-by: Yordan Karadzhov <ykaradzhov@vmware.com>
---
 kernel-shark-qt/src/KsMainWindow.cpp | 128 +++++++++++++++++++++++++++
 kernel-shark-qt/src/KsMainWindow.hpp |  22 +++++
 2 files changed, 150 insertions(+)

diff --git a/kernel-shark-qt/src/KsMainWindow.cpp b/kernel-shark-qt/src/KsMainWindow.cpp
index b9fb587..b3cbc3b 100644
--- a/kernel-shark-qt/src/KsMainWindow.cpp
+++ b/kernel-shark-qt/src/KsMainWindow.cpp
@@ -28,6 +28,7 @@
 #include "libkshark.h"
 #include "KsCmakeDef.hpp"
 #include "KsMainWindow.hpp"
+#include "KsCaptureDialog.hpp"
 #include "KsAdvFilteringDialog.hpp"
 
 /** Create KernelShark Main window. */
@@ -39,6 +40,8 @@ KsMainWindow::KsMainWindow(QWidget *parent)
   _graph(this),
   _mState(this),
   _plugins(this),
+  _capture(this),
+  _captureLocalServer(this),
   _openAction("Open", this),
   _restorSessionAction("Restor Last Session", this),
   _importSessionAction("Import Session", this),
@@ -56,6 +59,7 @@ KsMainWindow::KsMainWindow(QWidget *parent)
   _cpuSelectAction("CPUs", this),
   _taskSelectAction("Tasks", this),
   _pluginsAction("Plugins", this),
+  _captureAction("Record", this),
   _colorAction(this),
   _colSlider(this),
   _colorPhaseSlider(Qt::Horizontal, this),
@@ -67,6 +71,7 @@ KsMainWindow::KsMainWindow(QWidget *parent)
 	setWindowTitle("Kernel Shark");
 	_createActions();
 	_createMenus();
+	_initCapture();
 
 	_splitter.addWidget(&_graph);
 	_splitter.addWidget(&_view);
@@ -215,6 +220,13 @@ void KsMainWindow::_createActions()
 	connect(&_pluginsAction,	&QAction::triggered,
 		this,			&KsMainWindow::_pluginSelect);
 
+	_captureAction.setIcon(QIcon::fromTheme("media-record"));
+	_captureAction.setShortcut(tr("Ctrl+R"));
+	_captureAction.setStatusTip("Capture trace data");
+
+	connect(&_captureAction,	&QAction::triggered,
+		this,			&KsMainWindow::_record);
+
 	_colorPhaseSlider.setMinimum(20);
 	_colorPhaseSlider.setMaximum(180);
 	_colorPhaseSlider.setValue(KsPlot::Color::getRainbowFrequency() * 100);
@@ -321,6 +333,7 @@ void KsMainWindow::_createMenus()
 	/* Tools menu */
 	tools = menuBar()->addMenu("Tools");
 	tools->addAction(&_pluginsAction);
+	tools->addAction(&_captureAction);
 	tools->addSeparator();
 	tools->addAction(&_colorAction);
 	tools->addAction(&_fullScreenModeAction);
@@ -690,6 +703,29 @@ void KsMainWindow::_pluginSelect()
 	dialog->show();
 }
 
+void KsMainWindow::_record()
+{
+#ifndef DO_AS_ROOT
+
+	QErrorMessage *em = new QErrorMessage(this);
+	QString message;
+
+	message = "Record is currently not supported.";
+	message += " Install \"pkexec\" and then do:<br>";
+	message += " cd build <br> sudo ./cmake_uninstall.sh <br>";
+	message += " ./cmake_clean.sh <br> cmake .. <br> make <br>";
+	message += " sudo make install";
+
+	em->showMessage(message);
+	qCritical() << "ERROR: " << message;
+
+	return;
+
+#endif
+
+	_capture.start();
+}
+
 void KsMainWindow::_setColorPhase(int f)
 {
 	KsPlot::Color::setRainbowFrequency(f / 100.);
@@ -895,6 +931,98 @@ void KsMainWindow::loadSession(const QString &fileName)
 	_colorPhaseSlider.setValue(_session.getColorScheme() * 100);
 }
 
+void KsMainWindow::_initCapture()
+{
+#ifdef DO_AS_ROOT
+
+	_capture.setProgram("kshark-su-record");
+
+	connect(&_capture,	&QProcess::started,
+		this,		&KsMainWindow::_captureStarted);
+
+	/*
+	 * Using the old Signal-Slot syntax because QProcess::finished has
+	 * overloads.
+	 */
+	connect(&_capture,	SIGNAL(finished(int, QProcess::ExitStatus)),
+		this,		SLOT(_captureFinished(int, QProcess::ExitStatus)));
+
+	connect(&_capture,	&QProcess::errorOccurred,
+		this,		&KsMainWindow::_captureError);
+
+	connect(&_captureLocalServer,	&QLocalServer::newConnection,
+		this,			&KsMainWindow::_readSocket);
+
+#endif
+}
+
+void KsMainWindow::_captureStarted()
+{
+	_captureLocalServer.listen("KSCapture");
+}
+
+void KsMainWindow::_captureFinished(int exit, QProcess::ExitStatus st)
+{
+	QProcess *capture = (QProcess *)sender();
+
+	_captureLocalServer.close();
+
+	if (exit != 0 || st != QProcess::NormalExit) {
+		QString message = "Capture process failed:<br>";
+
+		message += capture->errorString();
+		message += "<br>Try doing:<br> sudo make install";
+
+		_error(message, "captureFinishedErr", false, false);
+	}
+}
+
+void KsMainWindow::_captureError(QProcess::ProcessError error)
+{
+	QProcess *capture = (QProcess *)sender();
+	QString message = "Capture process failed:<br>";
+
+	message += capture->errorString();
+	message += "<br>Try doing:<br> sudo make install";
+
+	_error(message, "captureFinishedErr", false, false);
+}
+
+void KsMainWindow::_readSocket()
+{
+	QLocalSocket *socket;
+	quint32 blockSize;
+	QString fileName;
+
+	auto lamSocketError = [&](QString message)
+	{
+		message = "ERROR from Local Server: " + message;
+		_error(message, "readSocketErr", false, false);
+	};
+
+	socket = _captureLocalServer.nextPendingConnection();
+	if (!socket) {
+		lamSocketError("Pending connectio not found!");
+		return;
+	}
+
+	QDataStream in(socket);
+	socket->waitForReadyRead();
+	if (socket->bytesAvailable() < (int)sizeof(quint32)) {
+		lamSocketError("Message size is corrupted!");
+		return;
+	};
+
+	in >> blockSize;
+	if (socket->bytesAvailable() < blockSize || in.atEnd()) {
+		lamSocketError("Message is corrupted!");
+		return;
+	}
+
+	in >> fileName;
+	loadDataFile(fileName);
+}
+
 void KsMainWindow::_splitterMoved(int pos, int index)
 {
 	_session.saveSplitterSize(_splitter);
diff --git a/kernel-shark-qt/src/KsMainWindow.hpp b/kernel-shark-qt/src/KsMainWindow.hpp
index 6e3f864..0e14c80 100644
--- a/kernel-shark-qt/src/KsMainWindow.hpp
+++ b/kernel-shark-qt/src/KsMainWindow.hpp
@@ -14,6 +14,7 @@
 
 // Qt
 #include <QMainWindow>
+#include <QLocalServer>
 
 // KernelShark
 #include "KsTraceViewer.hpp"
@@ -81,6 +82,12 @@ private:
 	/** Plugin manager. */
 	KsPluginManager	_plugins;
 
+	/** The process used to record trace data. */
+	QProcess	_capture;
+
+	/** Local Server used for comunucation with the Capture process. */
+	QLocalServer	_captureLocalServer;
+
 	// File menu.
 	QAction		_openAction;
 
@@ -119,6 +126,8 @@ private:
 	// Tools menu.
 	QAction		_pluginsAction;
 
+	QAction		_captureAction;
+
 	QWidgetAction	_colorAction;
 
 	QWidget		_colSlider;
@@ -166,6 +175,8 @@ private:
 
 	void _pluginSelect();
 
+	void _record();
+
 	void _setColorPhase(int);
 
 	void _fullScreenMode();
@@ -174,18 +185,29 @@ private:
 
 	void _contents();
 
+	void _captureStarted();
+
+	void _captureError(QProcess::ProcessError error);
+
+	void _readSocket();
+
 	void _splitterMoved(int pos, int index);
 
 	void _createActions();
 
 	void _createMenus();
 
+	void _initCapture();
+
 	void _updateSession();
 
 	inline void _resizeEmpty() {resize(SCREEN_WIDTH * .5, FONT_HEIGHT * 3);}
 
 	void _error(const QString &text, const QString &errCode,
 		    bool resize, bool unloadPlugins);
+
+private slots:
+	void _captureFinished(int, QProcess::ExitStatus);
 };
 
 #endif // _KS_MAINWINDOW_H
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 22/23] kernel-shark-qt: Workaround for running as Root on Wayland
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (19 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 21/23] kernel-shark-qt: Add Record dialog to KS GUI Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  2018-10-16 15:53 ` [PATCH v2 23/23] kernel-shark-qt: Version 0.9.0 Yordan Karadzhov
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

The Record dialog requires Root privileges in order to be able to collect
tracing data. However graphical applications cannot be run as root on
Wayland. The problem is worked around by wrapping the kshark-record
executable in a shell script which checks the XDG_SESSION_TYPE and in
the case of Wayland, allows the root user to access the running
X server.

Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
---
 kernel-shark-qt/bin/kshark-su-record | 8 ++++++++
 kernel-shark-qt/src/CMakeLists.txt   | 3 +++
 2 files changed, 11 insertions(+)
 create mode 100755 kernel-shark-qt/bin/kshark-su-record

diff --git a/kernel-shark-qt/bin/kshark-su-record b/kernel-shark-qt/bin/kshark-su-record
new file mode 100755
index 0000000..ee839a2
--- /dev/null
+++ b/kernel-shark-qt/bin/kshark-su-record
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [ $XDG_SESSION_TYPE = "wayland" ]
+then
+    xhost +si:localuser:root &>/dev/null
+fi
+
+pkexec kshark-record -o ${PWD}/trace.dat
diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
index 2592094..ef0aa71 100644
--- a/kernel-shark-qt/src/CMakeLists.txt
+++ b/kernel-shark-qt/src/CMakeLists.txt
@@ -83,6 +83,9 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
     install(FILES "${KS_DIR}/org.freedesktop.kshark-record.policy"
             DESTINATION /usr/share/polkit-1/actions/)
 
+    install(PROGRAMS "${KS_DIR}/bin/kshark-su-record"
+            DESTINATION /usr/local/bin/)
+
 endif (Qt5Widgets_FOUND AND Qt5Network_FOUND)
 
 add_subdirectory(plugins)
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH v2 23/23] kernel-shark-qt: Version 0.9.0
  2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
                   ` (20 preceding siblings ...)
  2018-10-16 15:53 ` [PATCH v2 22/23] kernel-shark-qt: Workaround for running as Root on Wayland Yordan Karadzhov
@ 2018-10-16 15:53 ` Yordan Karadzhov
  21 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-16 15:53 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
---
 kernel-shark-qt/CMakeLists.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/kernel-shark-qt/CMakeLists.txt b/kernel-shark-qt/CMakeLists.txt
index 767bec9..d92bc3d 100644
--- a/kernel-shark-qt/CMakeLists.txt
+++ b/kernel-shark-qt/CMakeLists.txt
@@ -5,8 +5,8 @@ cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR)
 project(kernel-shark-qt)
 
 set(KS_VERSION_MAJOR 0)
-set(KS_VERSION_MINOR 7)
-set(KS_VERSION_PATCH 1)
+set(KS_VERSION_MINOR 9)
+set(KS_VERSION_PATCH 0)
 set(KS_VERSION_STRING ${KS_VERSION_MAJOR}.${KS_VERSION_MINOR}.${KS_VERSION_PATCH})
 message("\n project: Kernel Shark: (version: ${KS_VERSION_STRING})\n")
 
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

* Re: [PATCH v2 02/23] kernel-shark-qt: Add Dual Marker for KernelShark GUI.
  2018-10-16 15:52 ` [PATCH v2 02/23] kernel-shark-qt: Add Dual Marker for KernelShark GUI Yordan Karadzhov
@ 2018-10-19  2:03   ` Steven Rostedt
  2018-10-19  7:41     ` Yordan Karadzhov (VMware)
  2018-10-19  2:05   ` Steven Rostedt
  1 sibling, 1 reply; 32+ messages in thread
From: Steven Rostedt @ 2018-10-19  2:03 UTC (permalink / raw)
  To: Yordan Karadzhov; +Cc: linux-trace-devel, Yordan Karadzhov

On Tue, 16 Oct 2018 15:52:58 +0000
Yordan Karadzhov <ykaradzhov@vmware.com> wrote:

> +
> +/** @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);

As a general rule, no "magic numbers". Why 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)");

Same for the 70s.

> +
> +	styleSheetB = "background : " +
> +		      _markB._color.name() +
> +		      "; color : white";
> +
> +	_stateB = new QState;
> +	_stateB->setObjectName("B");
> +	_stateB->assignProperty(&_buttonA,
> +				"styleSheet",
> +				"color : rgb(70, 70, 70)");

Here too.

> +
> +	_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();
> +}
> +



> +/**
> + * @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);

Same for these 7s.

> +		_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("");
> +	}
> +}

Don't resend this patch (I'm going to just take it). Send a patch on
top that just replaces the hard coded numbers with meaningful macros.

Thanks!

-- Steve

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH v2 02/23] kernel-shark-qt: Add Dual Marker for KernelShark GUI.
  2018-10-16 15:52 ` [PATCH v2 02/23] kernel-shark-qt: Add Dual Marker for KernelShark GUI Yordan Karadzhov
  2018-10-19  2:03   ` Steven Rostedt
@ 2018-10-19  2:05   ` Steven Rostedt
  1 sibling, 0 replies; 32+ messages in thread
From: Steven Rostedt @ 2018-10-19  2:05 UTC (permalink / raw)
  To: Yordan Karadzhov; +Cc: linux-trace-devel, Yordan Karadzhov

On Tue, 16 Oct 2018 15:52:58 +0000
Yordan Karadzhov <ykaradzhov@vmware.com> wrote:

> +
> +/** Get the Graph marker associated with a given state. */ 

Also be careful about extre whitespace. There's a extra space at the
end of the above line.

(I removed it in the commit).

-- Steve

> +KsGraphMark &KsDualMarkerSM::getMarker(DualMarkerState s)
> +{
> +	if (s == DualMarkerState::A)
> +		return _markA;
> +
> +	return _markB;
> +}

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH v2 04/23] kernel-shark-qt: Add Trace Viewer widget.
  2018-10-16 15:53 ` [PATCH v2 04/23] kernel-shark-qt: Add Trace Viewer widget Yordan Karadzhov
@ 2018-10-19  2:20   ` Steven Rostedt
  2018-10-19  2:24   ` Steven Rostedt
  1 sibling, 0 replies; 32+ messages in thread
From: Steven Rostedt @ 2018-10-19  2:20 UTC (permalink / raw)
  To: Yordan Karadzhov; +Cc: linux-trace-devel, Yordan Karadzhov

On Tue, 16 Oct 2018 15:53:01 +0000
Yordan Karadzhov <ykaradzhov@vmware.com> wrote:

> From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
> 
> 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.
> 

BTW, for the subject, leave off the period at the end.

Thanks!

-- Steve

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH v2 04/23] kernel-shark-qt: Add Trace Viewer widget.
  2018-10-16 15:53 ` [PATCH v2 04/23] kernel-shark-qt: Add Trace Viewer widget Yordan Karadzhov
  2018-10-19  2:20   ` Steven Rostedt
@ 2018-10-19  2:24   ` Steven Rostedt
  1 sibling, 0 replies; 32+ messages in thread
From: Steven Rostedt @ 2018-10-19  2:24 UTC (permalink / raw)
  To: Yordan Karadzhov; +Cc: linux-trace-devel, Yordan Karadzhov

On Tue, 16 Oct 2018 15:53:01 +0000
Yordan Karadzhov <ykaradzhov@vmware.com> wrote:

> +	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. */

The above is rather ugly. Again, just have another patch on top that
converts this to:

	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. */
	}

-- Steve

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH v2 06/23] kernel-shark-qt: Add widget for OpenGL rendering
  2018-10-16 15:53 ` [PATCH v2 06/23] kernel-shark-qt: Add widget for OpenGL rendering Yordan Karadzhov
@ 2018-10-19  2:33   ` Steven Rostedt
  0 siblings, 0 replies; 32+ messages in thread
From: Steven Rostedt @ 2018-10-19  2:33 UTC (permalink / raw)
  To: Yordan Karadzhov; +Cc: linux-trace-devel, Yordan Karadzhov

On Tue, 16 Oct 2018 15:53:03 +0000
Yordan Karadzhov <ykaradzhov@vmware.com> wrote:


> 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 <ykaradzhov@vmware.com>
> + */
> +
> + /**
> + *  @file    KsGLWidget.cpp
> + *  @brief   OpenGL widget for plotting trace graphs.
> + */
> +
> +// OpenGL
> +#include <GL/glut.h>
> +#include <GL/gl.h>
> +
> +// 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);

Again, let's get rid of the 1.5 and replace it with a meaningful macro
or variable (that perhaps can be changed later?

> +
> +	/* 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);

Why 5?

> +	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);

Same for the .05s

> +	} 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<int> cpuList, QVector<int> 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<int> cpuList, QVector<int> 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);

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) {

Perhaps make the 1e6 and 100 in macros or variables.

-- Steve

> +		/*
> +		 * 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;
> +}
> +
> +

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH v2 07/23] kernel-shark-qt: Add Trace Graph widget.
  2018-10-16 15:53 ` [PATCH v2 07/23] kernel-shark-qt: Add Trace Graph widget Yordan Karadzhov
@ 2018-10-19  2:38   ` Steven Rostedt
  0 siblings, 0 replies; 32+ messages in thread
From: Steven Rostedt @ 2018-10-19  2:38 UTC (permalink / raw)
  To: Yordan Karadzhov; +Cc: linux-trace-devel, Yordan Karadzhov

On Tue, 16 Oct 2018 15:53:05 +0000
Yordan Karadzhov <ykaradzhov@vmware.com> wrote:

> F
> diff --git a/kernel-shark-qt/src/KsTraceGraph.cpp b/kernel-shark-qt/src/KsTraceGraph.cpp
> new file mode 100644
> index 0000000..21a09d0
> --- /dev/null
> +++ b/kernel-shark-qt/src/KsTraceGraph.cpp
> @@ -0,0 +1,690 @@
> +// SPDX-License-Identifier: LGPL-2.1
> +
> +/*
> + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
> + */
> +
> +/**
> + *  @file    KsTraceGraph.cpp
> + *  @brief   KernelShark Trace Graph widget.
> + */
> +
> +// KernelShark
> +#include "KsUtils.hpp"
> +#include "KsDualMarker.hpp"
> +#include "KsTraceGraph.hpp"
> +
> +/** Create a default (empty) Trace graph widget. */
> +KsTraceGraph::KsTraceGraph(QWidget *parent)
> +: QWidget(parent),
> +  _pointerBar(this),
> +  _navigationBar(this),
> +  _zoomInButton("+", this),
> +  _quickZoomInButton("++", this),
> +  _zoomOutButton("-", this),
> +  _quickZoomOutButton("- -", this),
> +  _scrollLeftButton("<", this),
> +  _scrollRightButton(">", this),
> +  _labelP1("Pointer: ", this),
> +  _labelP2("", this),
> +  _labelI1("", this),
> +  _labelI2("", this),
> +  _labelI3("", this),
> +  _labelI4("", this),
> +  _labelI5("", this),
> +  _scrollArea(this),
> +  _drawWindow(&_scrollArea),
> +  _legendWindow(&_drawWindow),
> +  _legendAxisX(&_drawWindow),
> +  _labelXMin("", &_legendAxisX),
> +  _labelXMid("", &_legendAxisX),
> +  _labelXMax("", &_legendAxisX),
> +  _glWindow(&_drawWindow),
> +  _mState(nullptr),
> +  _data(nullptr),
> +  _keyPressed(false)
> +{
> +	auto lamMakeNavButton = [&](QPushButton *b) {
> +		b->setMaximumWidth(FONT_WIDTH * 5);
> +
> +		connect(b,	&QPushButton::released,
> +			this,	&KsTraceGraph::_stopUpdating);
> +		_navigationBar.addWidget(b);
> +	};
> +
> +	_pointerBar.setMaximumHeight(FONT_HEIGHT * 1.75);

Why the 1.75?

> +	_pointerBar.setOrientation(Qt::Horizontal);
> +
> +	_navigationBar.setMaximumHeight(FONT_HEIGHT * 1.75);
> +	_navigationBar.setMinimumWidth(FONT_WIDTH * 90);

Why the 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);

Why 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);

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);

The 100s should probably be MACROS

> +	_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)

Why 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;

Why 3?

> +	elidedText = metrix.elidedText(info, Qt::ElideRight, width);
> +
> +	while(labelWidth < STRING_WIDTH(elidedText) + FONT_WIDTH * 5) {
> +		width -= FONT_WIDTH * 3;

Why 5 and 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,

Why 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<int> v)
> +{
> +	_glWindow._cpuList = v;
> +	_selfUpdate();
> +}
> +
> +/**
> + * @brief Redreaw all Task graphs.
> + *
> + * @param v: Process ids of the tasks to be plotted.
> + */
> +void KsTraceGraph::taskReDraw(QVector<int> 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;

Why 8?

(You are probably noticing a theme here ;-)

> +
> +	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.
> +			       */

Ug, finish the 2); on the previous line, and keep the comment on its
own line.

-- Steve


> +}
> +
> +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);
> +}

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH v2 02/23] kernel-shark-qt: Add Dual Marker for KernelShark GUI.
  2018-10-19  2:03   ` Steven Rostedt
@ 2018-10-19  7:41     ` Yordan Karadzhov (VMware)
  0 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov (VMware) @ 2018-10-19  7:41 UTC (permalink / raw)
  To: Steven Rostedt, Yordan Karadzhov; +Cc: linux-trace-devel



On 19.10.2018 05:03, Steven Rostedt wrote:
> On Tue, 16 Oct 2018 15:52:58 +0000
> Yordan Karadzhov <ykaradzhov@vmware.com> wrote:
> 
>> +
>> +/** @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);
> 
> As a general rule, no "magic numbers". Why 16?
> 

> 
> Don't resend this patch (I'm going to just take it). Send a patch on
> top that just replaces the hard coded numbers with meaningful macros.
> 

Hi Steven,

This is one of these cases when I disagree with you.
I don't see anything wrong with the line

l->setFixedWidth(FONT_WIDTH * 16);

or the many other lines in the code which are similar to this one.
It sets the width of the label to be 16 times the width of a typical 
character shown by Qt at the particular resolution of your screen.
The line is perfectly self-explanatory. And there is nothing magic in 
the number 16. It is just a normal number that everyone can understand 
end modify if this is necessary. Why 16? Because I tried different sizes 
and I found that this particular one looks nicer to me. If you find this 
size ugly, it is absolutist trivial to change it.

Now let's look in the alternative that you suggest (if I understand 
correctly):

l->setFixedWidth(DUAL_MARKER_TOOLBAR_LABEL_WIDTH);

This line is a magic word, because it tells you absolutist nothing about 
the actual size of this label. And if we apply this rule everywhere, we 
will need a ton of macros, with very long and in the same time very 
similar names. This will also make the geometry of the GUI completely 
hard-coded and will eliminate any possibility of making these sizes 
configurable via the Json I/O.

I do not believe in "general rules". Each rule has a field of 
applicability. When you program GUIs you need to apply fine tuning. I 
don't think we can avoid this.
Thanks!

Yordan

> Thanks!
> 
> -- Steve
> 

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH v2 20/23] kernel-shark-qt: Add make install
  2018-10-16 15:53 ` [PATCH v2 20/23] kernel-shark-qt: Add make install Yordan Karadzhov
@ 2018-10-19 15:52   ` Steven Rostedt
  2018-10-19 17:13     ` [PATCH v3] " Yordan Karadzhov
  0 siblings, 1 reply; 32+ messages in thread
From: Steven Rostedt @ 2018-10-19 15:52 UTC (permalink / raw)
  To: Yordan Karadzhov; +Cc: linux-trace-devel, Yordan Karadzhov

On Tue, 16 Oct 2018 15:53:21 +0000
Yordan Karadzhov <ykaradzhov@vmware.com> wrote:

> diff --git a/kernel-shark-qt/build/cmake_uninstall.sh b/kernel-shark-qt/build/cmake_uninstall.sh
> new file mode 100755
> index 0000000..50c163b
> --- /dev/null
> +++ b/kernel-shark-qt/build/cmake_uninstall.sh
> @@ -0,0 +1,17 @@
> +#!/bin/bash
> +
> +CYAN='\033[0;36m'
> +PURPLE='\033[0;35m'
> +NC='\033[0m' # No Color

BTW, this also works:

 CYAN='\e[36m'
 PURPLE='\e[35m'
 NC='\e[0m' # No Color

Which is a little cleaner.

If you agree, can you resend just this patch with the update (as v2)?

Thanks!

-- Steve

> +
> +if [[ $EUID -ne 0 ]]; then
> +   echo -e "${PURPLE}Permission denied${NC}" 1>&2
> +   exit 100
> +fi
> +
> +if [ -e install_manifest.txt ]
> +then
> +    echo -e "${CYAN}Uninstall the project...${NC}"
> +    xargs rm -v < install_manifest.txt
> +    rm -f install_manifest.txt
> +fi
> diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
> index 6819f86..2592094 100644
> --- a/kernel-shark-qt/src/CMakeLists.txt
> +++ b/kernel-shark-qt/src/CMakeLists.txt
> @@ -73,6 +73,16 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
>      add_executable(kshark-record        kshark-record.cpp)
>      target_link_libraries(kshark-record kshark-gui)
>  
> +    install(TARGETS kernelshark kshark-record kshark kshark-plot kshark-gui
> +            RUNTIME DESTINATION /usr/local/bin/
> +            LIBRARY DESTINATION /usr/local/lib/kshark/)
> +
> +    install(FILES "${KS_DIR}/kernelshark.desktop"
> +            DESTINATION /usr/share/applications/)
> +
> +    install(FILES "${KS_DIR}/org.freedesktop.kshark-record.policy"
> +            DESTINATION /usr/share/polkit-1/actions/)
> +
>  endif (Qt5Widgets_FOUND AND Qt5Network_FOUND)
>  
>  add_subdirectory(plugins)
> diff --git a/kernel-shark-qt/src/plugins/CMakeLists.txt b/kernel-shark-qt/src/plugins/CMakeLists.txt
> index 88fd93c..2d7251d 100644
> --- a/kernel-shark-qt/src/plugins/CMakeLists.txt
> +++ b/kernel-shark-qt/src/plugins/CMakeLists.txt
> @@ -24,4 +24,7 @@ BUILD_PLUGIN(NAME sched_events
>  list(APPEND PLUGIN_LIST "sched_events default") # This plugin will be loaded by default
>  # list(APPEND PLUGIN_LIST "sched_events") # This plugin isn't loaded by default
>  
> +install(TARGETS sched_events
> +        LIBRARY DESTINATION /usr/local/lib/kshark/)
> +
>  set(PLUGINS ${PLUGIN_LIST} PARENT_SCOPE)

^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH v3] kernel-shark-qt: Add make install
  2018-10-19 15:52   ` Steven Rostedt
@ 2018-10-19 17:13     ` Yordan Karadzhov
  0 siblings, 0 replies; 32+ messages in thread
From: Yordan Karadzhov @ 2018-10-19 17:13 UTC (permalink / raw)
  To: rostedt; +Cc: linux-trace-devel, Yordan Karadzhov

From: Yordan Karadzhov (VMware) <y.karadz@gmail.com>

Define CMake installation rules for kernel-shark-qt project.
Add a script for uninstalling.

Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@gmail.com>
---
 kernel-shark-qt/CMakeLists.txt             |  3 +++
 kernel-shark-qt/build/cmake_uninstall.sh   | 17 +++++++++++++++++
 kernel-shark-qt/src/CMakeLists.txt         | 10 ++++++++++
 kernel-shark-qt/src/plugins/CMakeLists.txt |  3 +++
 4 files changed, 33 insertions(+)
 create mode 100755 kernel-shark-qt/build/cmake_uninstall.sh

diff --git a/kernel-shark-qt/CMakeLists.txt b/kernel-shark-qt/CMakeLists.txt
index 278241b..767bec9 100644
--- a/kernel-shark-qt/CMakeLists.txt
+++ b/kernel-shark-qt/CMakeLists.txt
@@ -46,6 +46,9 @@ if (NOT _DEBUG)
 
 endif (NOT _DEBUG)
 
+SET(CMAKE_INSTALL_RPATH "/usr/local/lib/kshark/")
+SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
+
 include_directories(${KS_DIR}/src/
                     ${KS_DIR}/build/src/
                     ${JSONC_INCLUDE_DIR}
diff --git a/kernel-shark-qt/build/cmake_uninstall.sh b/kernel-shark-qt/build/cmake_uninstall.sh
new file mode 100755
index 0000000..ae9eab5
--- /dev/null
+++ b/kernel-shark-qt/build/cmake_uninstall.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+CYAN='\e[36m'
+PURPLE='\e[35m'
+NC='\e[0m' # No Color
+
+if [[ $EUID -ne 0 ]]; then
+   echo -e "${PURPLE}Permission denied${NC}" 1>&2
+   exit 100
+fi
+
+if [ -e install_manifest.txt ]
+then
+    echo -e "${CYAN}Uninstall the project...${NC}"
+    xargs rm -v < install_manifest.txt
+    rm -f install_manifest.txt
+fi
diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
index 6819f86..2592094 100644
--- a/kernel-shark-qt/src/CMakeLists.txt
+++ b/kernel-shark-qt/src/CMakeLists.txt
@@ -73,6 +73,16 @@ if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
     add_executable(kshark-record        kshark-record.cpp)
     target_link_libraries(kshark-record kshark-gui)
 
+    install(TARGETS kernelshark kshark-record kshark kshark-plot kshark-gui
+            RUNTIME DESTINATION /usr/local/bin/
+            LIBRARY DESTINATION /usr/local/lib/kshark/)
+
+    install(FILES "${KS_DIR}/kernelshark.desktop"
+            DESTINATION /usr/share/applications/)
+
+    install(FILES "${KS_DIR}/org.freedesktop.kshark-record.policy"
+            DESTINATION /usr/share/polkit-1/actions/)
+
 endif (Qt5Widgets_FOUND AND Qt5Network_FOUND)
 
 add_subdirectory(plugins)
diff --git a/kernel-shark-qt/src/plugins/CMakeLists.txt b/kernel-shark-qt/src/plugins/CMakeLists.txt
index 88fd93c..2d7251d 100644
--- a/kernel-shark-qt/src/plugins/CMakeLists.txt
+++ b/kernel-shark-qt/src/plugins/CMakeLists.txt
@@ -24,4 +24,7 @@ BUILD_PLUGIN(NAME sched_events
 list(APPEND PLUGIN_LIST "sched_events default") # This plugin will be loaded by default
 # list(APPEND PLUGIN_LIST "sched_events") # This plugin isn't loaded by default
 
+install(TARGETS sched_events
+        LIBRARY DESTINATION /usr/local/lib/kshark/)
+
 set(PLUGINS ${PLUGIN_LIST} PARENT_SCOPE)
-- 
2.17.1

^ permalink raw reply related	[flat|nested] 32+ messages in thread

end of thread, other threads:[~2018-10-20  1:21 UTC | newest]

Thread overview: 32+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-10-16 15:52 [PATCH v2 00/23] Add Qt-based GUI for KernelShark Yordan Karadzhov
2018-10-16 15:52 ` [PATCH v2 01/23] kernel-shark-qt: Fix a simple bug in KsDataStore::_freeData() Yordan Karadzhov
2018-10-16 15:52 ` [PATCH v2 02/23] kernel-shark-qt: Add Dual Marker for KernelShark GUI Yordan Karadzhov
2018-10-19  2:03   ` Steven Rostedt
2018-10-19  7:41     ` Yordan Karadzhov (VMware)
2018-10-19  2:05   ` Steven Rostedt
2018-10-16 15:52 ` [PATCH v2 03/23] kernel-shark-qt: Add model for showing trace data in a text format Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 04/23] kernel-shark-qt: Add Trace Viewer widget Yordan Karadzhov
2018-10-19  2:20   ` Steven Rostedt
2018-10-19  2:24   ` Steven Rostedt
2018-10-16 15:53 ` [PATCH v2 05/23] kernel-shark-qt: Add visualization (graph) model Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 06/23] kernel-shark-qt: Add widget for OpenGL rendering Yordan Karadzhov
2018-10-19  2:33   ` Steven Rostedt
2018-10-16 15:53 ` [PATCH v2 07/23] kernel-shark-qt: Add Trace Graph widget Yordan Karadzhov
2018-10-19  2:38   ` Steven Rostedt
2018-10-16 15:53 ` [PATCH v2 08/23] kernel-shark-qt: Add dialog for Advanced filtering Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 09/23] kernel-shark-qt: Add a manager class for GUI sessions Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 10/23] kernel-shark-qt: Add Main Window widget for the KernelShark GUI Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 11/23] kernel-shark-qt: Add KernelShark GUI executable Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 12/23] kernel-shark-qt: Add "File exists" dialog Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 13/23] kernel-shark-qt: Fix the glitches in the preemption time visualization Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 14/23] kernel-shark-qt: Add dialog for of trace data recording Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 15/23] kernel-shark-qt: Add kshark-record executable Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 16/23] kernel-shark-qt: Instruct CMake to search for "pkexec" Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 17/23] kernel-shark-qt: Add PolicyKit Configuration for kshark-record Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 19/23] kernel-shark-qt: Add kernelshark.desktop file Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 20/23] kernel-shark-qt: Add make install Yordan Karadzhov
2018-10-19 15:52   ` Steven Rostedt
2018-10-19 17:13     ` [PATCH v3] " Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 21/23] kernel-shark-qt: Add Record dialog to KS GUI Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 22/23] kernel-shark-qt: Workaround for running as Root on Wayland Yordan Karadzhov
2018-10-16 15:53 ` [PATCH v2 23/23] kernel-shark-qt: Version 0.9.0 Yordan Karadzhov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).