@@ -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)
@@ -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();
}
@@ -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();
new file mode 100644
@@ -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];
+}
new file mode 100644
@@ -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