diff mbox series

[05/10] kernel-shark-qt: Add widget for OpenGL rendering

Message ID 20181012161318.5302-6-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 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 mbox series

Patch

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