diff mbox series

[06/10] kernel-shark-qt: Add Trace Graph widget.

Message ID 20181012161318.5302-7-ykaradzhov@vmware.com (mailing list archive)
State Superseded
Headers show
Series Add Qt-based GUI for KernelShark | expand

Commit Message

Yordan Karadzhov Oct. 12, 2018, 4:13 p.m. UTC
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 mbox series

Patch

diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
index 2ca5187..d406866 100644
--- a/kernel-shark-qt/src/CMakeLists.txt
+++ b/kernel-shark-qt/src/CMakeLists.txt
@@ -36,6 +36,7 @@  if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
                         KsGLWidget.hpp
                         KsDualMarker.hpp
                         KsWidgetsLib.hpp
+                        KsTraceGraph.hpp
                         KsTraceViewer.hpp)
 
     QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr})
@@ -45,6 +46,7 @@  if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
                                                             KsGLWidget.cpp
                                                             KsDualMarker.cpp
                                                             KsWidgetsLib.cpp
+                                                            KsTraceGraph.cpp
                                                             KsTraceViewer.cpp)
 
     target_link_libraries(kshark-gui kshark-plot
diff --git a/kernel-shark-qt/src/KsTraceGraph.cpp b/kernel-shark-qt/src/KsTraceGraph.cpp
new file mode 100644
index 0000000..6994644
--- /dev/null
+++ b/kernel-shark-qt/src/KsTraceGraph.cpp
@@ -0,0 +1,690 @@ 
+// SPDX-License-Identifier: LGPL-2.1
+
+/*
+ * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsTraceGraph.cpp
+ *  @brief   KernelShark Trace Graph.
+ */
+
+// KernelShark
+#include "KsUtils.hpp"
+#include "KsDualMarker.hpp"
+#include "KsTraceGraph.hpp"
+
+/** Create a default (empty) Trace graph widget. */
+KsTraceGraph::KsTraceGraph(QWidget *parent)
+: QWidget(parent),
+  _pointerBar(this),
+  _navigationBar(this),
+  _zoomInButton("+", this),
+  _quickZoomInButton("++", this),
+  _zoomOutButton("-", this),
+  _quickZoomOutButton("- -", this),
+  _scrollLeftButton("<", this),
+  _scrollRightButton(">", this),
+  _labelP1("Pointer: ", this),
+  _labelP2("", this),
+  _labelI1("", this),
+  _labelI2("", this),
+  _labelI3("", this),
+  _labelI4("", this),
+  _labelI5("", this),
+  _scrollArea(this),
+  _drawWindow(&_scrollArea),
+  _legendWindow(&_drawWindow),
+  _legendAxisX(&_drawWindow),
+  _labelXMin("", &_legendAxisX),
+  _labelXMid("", &_legendAxisX),
+  _labelXMax("", &_legendAxisX),
+  _glWindow(&_drawWindow),
+  _mState(nullptr),
+  _data(nullptr),
+  _keyPressed(false)
+{
+	auto lamMakeNavButton = [&](QPushButton *b) {
+		b->setMaximumWidth(FONT_WIDTH * 5);
+
+		connect(b,	&QPushButton::released,
+			this,	&KsTraceGraph::_stopUpdating);
+		_navigationBar.addWidget(b);
+	};
+
+	_pointerBar.setMaximumHeight(FONT_HEIGHT * 1.75);
+	_pointerBar.setOrientation(Qt::Horizontal);
+
+	_navigationBar.setMaximumHeight(FONT_HEIGHT * 1.75);
+	_navigationBar.setMinimumWidth(FONT_WIDTH * 90);
+	_navigationBar.setOrientation(Qt::Horizontal);
+
+	_pointerBar.addWidget(&_labelP1);
+	_labelP2.setFrameStyle(QFrame::Panel | QFrame::Sunken);
+	_labelP2.setStyleSheet("QLabel { background-color : white;}");
+	_labelP2.setTextInteractionFlags(Qt::TextSelectableByMouse);
+	_labelP2.setFixedWidth(FONT_WIDTH * 16);
+	_pointerBar.addWidget(&_labelP2);
+	_pointerBar.addSeparator();
+
+	_labelI1.setStyleSheet("QLabel {color : blue;}");
+	_labelI2.setStyleSheet("QLabel {color : green;}");
+	_labelI3.setStyleSheet("QLabel {color : red;}");
+	_labelI4.setStyleSheet("QLabel {color : blue;}");
+	_labelI5.setStyleSheet("QLabel {color : green;}");
+
+	_pointerBar.addWidget(&_labelI1);
+	_pointerBar.addSeparator();
+	_pointerBar.addWidget(&_labelI2);
+	_pointerBar.addSeparator();
+	_pointerBar.addWidget(&_labelI3);
+	_pointerBar.addSeparator();
+	_pointerBar.addWidget(&_labelI4);
+	_pointerBar.addSeparator();
+	_pointerBar.addWidget(&_labelI5);
+
+	_legendAxisX.setFixedHeight(FONT_HEIGHT * 1.5);
+	_legendAxisX.setLayout(new QHBoxLayout);
+	_legendAxisX.layout()->setSpacing(0);
+	_legendAxisX.layout()->setContentsMargins(0, 0, FONT_WIDTH, 0);
+
+	_labelXMin.setAlignment(Qt::AlignLeft);
+	_labelXMid.setAlignment(Qt::AlignHCenter);
+	_labelXMax.setAlignment(Qt::AlignRight);
+
+	_legendAxisX.layout()->addWidget(&_labelXMin);
+	_legendAxisX.layout()->addWidget(&_labelXMid);
+	_legendAxisX.layout()->addWidget(&_labelXMax);
+	_drawWindow.setMinimumSize(100, 100);
+	_drawWindow.setStyleSheet("QWidget {background-color : white;}");
+
+	_drawLayout.setContentsMargins(0, 0, 0, 0);
+	_drawLayout.setSpacing(0);
+	_drawLayout.addWidget(&_legendAxisX, 0, 1);
+	_drawLayout.addWidget(&_legendWindow, 1, 0);
+	_drawLayout.addWidget(&_glWindow, 1, 1);
+	_drawWindow.setLayout(&_drawLayout);
+
+	_drawWindow.installEventFilter(this);
+
+	connect(&_glWindow,	&KsGLWidget::select,
+		this,		&KsTraceGraph::markEntry);
+
+	connect(&_glWindow,	&KsGLWidget::found,
+		this,		&KsTraceGraph::_setPointerInfo);
+
+	connect(&_glWindow,	&KsGLWidget::notFound,
+		this,		&KsTraceGraph::_resetPointer);
+
+	connect(&_glWindow,	&KsGLWidget::zoomIn,
+		this,		&KsTraceGraph::_zoomIn);
+
+	connect(&_glWindow,	&KsGLWidget::zoomOut,
+		this,		&KsTraceGraph::_zoomOut);
+
+	connect(&_glWindow,	&KsGLWidget::scrollLeft,
+		this,		&KsTraceGraph::_scrollLeft);
+
+	connect(&_glWindow,	&KsGLWidget::scrollRight,
+		this,		&KsTraceGraph::_scrollRight);
+
+	connect(&_glWindow,	&KsGLWidget::stopUpdating,
+		this,		&KsTraceGraph::_stopUpdating);
+
+	connect(_glWindow.model(),	&KsGraphModel::modelReset,
+		this,			&KsTraceGraph::_updateTimeLegends);
+
+	_scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+	_scrollArea.setWidget(&_drawWindow);
+
+	lamMakeNavButton(&_scrollLeftButton);
+	connect(&_scrollLeftButton,	&QPushButton::pressed,
+		this,			&KsTraceGraph::_scrollLeft);
+
+	lamMakeNavButton(&_zoomInButton);
+	connect(&_zoomInButton,		&QPushButton::pressed,
+		this,			&KsTraceGraph::_zoomIn);
+
+	lamMakeNavButton(&_zoomOutButton);
+	connect(&_zoomOutButton,	&QPushButton::pressed,
+		this,			&KsTraceGraph::_zoomOut);
+
+	lamMakeNavButton(&_scrollRightButton);
+	connect(&_scrollRightButton,	&QPushButton::pressed,
+		this,			&KsTraceGraph::_scrollRight);
+
+	_navigationBar.addSeparator();
+
+	lamMakeNavButton(&_quickZoomInButton);
+	connect(&_quickZoomInButton,	&QPushButton::pressed,
+		this,			&KsTraceGraph::_quickZoomIn);
+
+	lamMakeNavButton(&_quickZoomOutButton);
+	connect(&_quickZoomOutButton,	&QPushButton::pressed,
+		this,			&KsTraceGraph::_quickZoomOut);
+
+	_layout.addWidget(&_pointerBar);
+	_layout.addWidget(&_navigationBar);
+	_layout.addWidget(&_scrollArea);
+	this->setLayout(&_layout);
+	updateGeom();
+}
+
+/**
+ * @brief Load and show trace data.
+ *
+ * @param data: Input location for the KsDataStore object.
+ *	  KsDataStore::loadDataFile() must be called first.
+ */
+void KsTraceGraph::loadData(KsDataStore *data)
+{
+	_data = data;
+	_glWindow.loadData(data);
+	_updateGraphLegends();
+	updateGeom();
+}
+
+/** Connect the KsGLWidget widget and the State machine of the Dual marker. */
+void KsTraceGraph::setMarkerSM(KsDualMarkerSM *m)
+{
+	_mState = m;
+	_navigationBar.addSeparator();
+	_mState->placeInToolBar(&_navigationBar);
+	_glWindow.setMarkerSM(m);
+}
+
+/** Reset (empty) the widget. */
+void KsTraceGraph::reset()
+{
+	/* Clear the all graph lists and update. */
+	_glWindow._cpuList = {};
+	_glWindow._taskList = {};
+
+	_labelP2.setText("");
+	for (auto l1: {&_labelI1, &_labelI2, &_labelI3, &_labelI4, &_labelI5})
+		l1->setText("");
+
+	_glWindow.model()->reset();
+	_selfUpdate();
+	for (auto l2: {&_labelXMin, &_labelXMid, &_labelXMax})
+		l2->setText("");
+}
+
+void KsTraceGraph::_selfUpdate()
+{
+	_updateGraphLegends();
+	_updateTimeLegends();
+	_markerReDraw();
+	updateGeom();
+}
+
+void KsTraceGraph::_zoomIn()
+{
+	_updateGraphs(GraphActions::ZoomIn);
+}
+
+void KsTraceGraph::_zoomOut()
+{
+	_updateGraphs(GraphActions::ZoomOut);
+}
+
+void KsTraceGraph::_quickZoomIn()
+{
+	/* Bin size will be 100 ns. */
+	_glWindow.model()->quickZoomIn(100);
+	if (_mState->activeMarker()._isSet &&
+	    _mState->activeMarker().isVisible()) {
+		/*
+		 * Use the position of the active marker as
+		 * a focus point of the zoom.
+		 */
+		uint64_t ts = _mState->activeMarker()._ts;
+		_glWindow.model()->jumpTo(ts);
+	}
+}
+
+void KsTraceGraph::_quickZoomOut()
+{
+	_glWindow.model()->quickZoomOut();
+}
+
+void KsTraceGraph::_scrollLeft()
+{
+	_updateGraphs(GraphActions::ScrollLeft);
+}
+
+void KsTraceGraph::_scrollRight()
+{
+	_updateGraphs(GraphActions::ScrollRight);
+}
+
+void KsTraceGraph::_stopUpdating()
+{
+	/*
+	 * The user is no longer pressing the action button. Reset the
+	 * "Key Pressed" flag. This will stop the ongoing user action.
+	 */
+	_keyPressed = false;
+}
+
+void KsTraceGraph::_resetPointer(uint64_t ts, int cpu, int pid)
+{
+	uint64_t sec, usec;
+	QString pointer;
+
+	kshark_convert_nano(ts, &sec, &usec);
+	pointer.sprintf("%lu.%lu", sec, usec);
+	_labelP2.setText(pointer);
+
+	if (pid > 0 && cpu >= 0) {
+		struct kshark_context *kshark_ctx(NULL);
+
+		if (!kshark_instance(&kshark_ctx))
+			return;
+
+		QString comm(tep_data_comm_from_pid(kshark_ctx->pevent, pid));
+		comm.append("-");
+		comm.append(QString("%1").arg(pid));
+		_labelI1.setText(comm);
+		_labelI2.setText(QString("CPU %1").arg(cpu));
+	} else {
+		_labelI1.setText("");
+		_labelI2.setText("");
+	}
+
+	for (auto const &l: {&_labelI3, &_labelI4, &_labelI5}) {
+		l->setText("");
+	}
+}
+
+void KsTraceGraph::_setPointerInfo(size_t i)
+{
+	kshark_entry *e = _data->rows()[i];
+	QString event(kshark_get_event_name_easy(e));
+	QString lat(kshark_get_latency_easy(e));
+	QString info(kshark_get_info_easy(e));
+	QString comm(kshark_get_task_easy(e));
+	QString pointer, elidedText;
+	int labelWidth, width;
+	uint64_t sec, usec;
+
+	kshark_convert_nano(e->ts, &sec, &usec);
+	pointer.sprintf("%lu.%lu", sec, usec);
+	_labelP2.setText(pointer);
+
+	comm.append("-");
+	comm.append(QString("%1").arg(kshark_get_pid_easy(e)));
+
+	_labelI1.setText(comm);
+	_labelI2.setText(QString("CPU %1").arg(e->cpu));
+	_labelI3.setText(lat);
+	_labelI4.setText(event);
+	_labelI5.setText(info);
+	QCoreApplication::processEvents();
+
+	labelWidth =
+		_pointerBar.geometry().right() - _labelI4.geometry().right();
+	if (labelWidth > STRING_WIDTH(info) + FONT_WIDTH * 5)
+		return;
+
+	/*
+	 * The Info string is too long and cannot be displayed on the toolbar.
+	 * Try to fit the text in the available space.
+	 */
+	QFontMetrics metrix(_labelI5.font());
+	width = labelWidth - FONT_WIDTH * 3;
+	elidedText = metrix.elidedText(info, Qt::ElideRight, width);
+
+	while(labelWidth < STRING_WIDTH(elidedText) + FONT_WIDTH * 5) {
+		width -= FONT_WIDTH * 3;
+		elidedText = metrix.elidedText(info, Qt::ElideRight, width);
+	}
+
+	_labelI5.setText(elidedText);
+	_labelI5.setVisible(true);
+	QCoreApplication::processEvents();
+}
+
+/**
+ * @brief Use the active marker to select particular entry.
+ *
+ * @param row: The index of the entry to be selected by the marker.
+ */
+void KsTraceGraph::markEntry(size_t row)
+{
+	int graph, cpuGrId, taskGrId;
+
+	_glWindow.findGraphIds(*_data->rows()[row], &cpuGrId, &taskGrId);
+
+	/*
+	 * If a Task graph has been found, this Task graph will be
+	 * visible. If no Task graph has been found, make visible
+	 * the corresponding CPU graph.
+	 */
+	if (taskGrId >= 0)
+		graph = taskGrId;
+	else
+		graph = cpuGrId;
+
+	_scrollArea.ensureVisible(0,
+				  _legendAxisX.height() +
+				  _glWindow.vMargin() +
+				  KS_GRAPH_HEIGHT / 2 +
+				  graph*(KS_GRAPH_HEIGHT + _glWindow.vSpacing()),
+				  50,
+				  KS_GRAPH_HEIGHT / 2 + _glWindow.vSpacing() / 2);
+
+	_glWindow.model()->jumpTo(_data->rows()[row]->ts);
+	_mState->activeMarker().set(*_data,
+				    _glWindow.model()->histo(),
+				    row, cpuGrId, taskGrId);
+
+	_mState->updateMarkers(*_data, &_glWindow);
+}
+
+void KsTraceGraph::_markerReDraw()
+{
+	int cpuGrId, taskGrId;
+	size_t row;
+
+	if (_mState->markerA()._isSet) {
+		row = _mState->markerA()._pos;
+		_glWindow.findGraphIds(*_data->rows()[row], &cpuGrId, &taskGrId);
+		_mState->markerA().set(*_data,
+				       _glWindow.model()->histo(),
+				       row, cpuGrId, taskGrId);
+	}
+
+	if (_mState->markerB()._isSet) {
+		row = _mState->markerB()._pos;
+		_glWindow.findGraphIds(*_data->rows()[row], &cpuGrId, &taskGrId);
+		_mState->markerB().set(*_data,
+				       _glWindow.model()->histo(),
+				       row, cpuGrId, taskGrId);
+	}
+}
+
+/**
+ * @brief Redreaw all CPU graphs.
+ *
+ * @param v: CPU ids to be plotted.
+ */
+void KsTraceGraph::cpuReDraw(QVector<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..b57b256
--- /dev/null
+++ b/kernel-shark-qt/src/KsTraceGraph.hpp
@@ -0,0 +1,137 @@ 
+/* SPDX-License-Identifier: LGPL-2.1 */
+
+/*
+ * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
+ */
+
+/**
+ *  @file    KsTraceGraph.hpp
+ *  @brief   KernelShark Trace Graph.
+ */
+#ifndef _KS_TRACEGRAPH_H
+#define _KS_TRACEGRAPH_H
+
+// KernelShark
+#include "KsGLWidget.hpp"
+
+/**
+ * Scroll Area class, needed in order to reimplemented the handler for mouse
+ * wheel events.
+ */
+class KsGraphScrollArea : public QScrollArea {
+public:
+	/** Create a default Scroll Area. */
+	explicit KsGraphScrollArea(QWidget *parent = nullptr)
+	: QScrollArea(parent) {}
+
+	/**
+	 * Reimplemented handler for mouse wheel events. All mouse wheel
+	 * events will be ignored.
+	 */
+	void wheelEvent(QWheelEvent *evt) {evt->ignore();}
+};
+
+/**
+ * The KsTraceViewer class provides a widget for interactive visualization of
+ * trace data shown as time-series.
+ */
+class KsTraceGraph : public QWidget
+{
+	Q_OBJECT
+public:
+	explicit KsTraceGraph(QWidget *parent = nullptr);
+
+	void loadData(KsDataStore *data);
+
+	void setMarkerSM(KsDualMarkerSM *m);
+
+	void reset();
+
+	/** Get the KsGLWidget object. */
+	KsGLWidget *glPtr() {return &_glWindow;}
+
+	void markEntry(size_t);
+
+	void cpuReDraw(QVector<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