| // 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); |
| } |
| } |
| |
| 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 = getPlotCPU(event->pos()); |
| pid = getPlotPid(event->pos()); |
| |
| 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::getTaskColorTable(); |
| _cpuColors.clear(); |
| _cpuColors = KsPlot::getCPUColorTable(); |
| } |
| |
| /** |
| * 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) |
| { |
| /* The CPU graph needs to know only the colors of the tasks. */ |
| KsPlot::Graph *graph = new KsPlot::Graph(_model.histo(), |
| &_pidColors, |
| &_pidColors); |
| graph->setZeroSuppressed(true); |
| |
| 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) |
| { |
| /* |
| * The Task graph needs to know the colors of the tasks and the colors |
| * of the CPUs. |
| */ |
| KsPlot::Graph *graph = new KsPlot::Graph(_model.histo(), |
| &_pidColors, |
| &_cpuColors); |
| 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; |
| } |
| |
| /** |
| * @brief Find the KernelShark entry under the the cursor. |
| * |
| * @param point: The position of the cursor. |
| * @param variance: The variance of the position (range) in which an entry will |
| * be searched. |
| * @param joined: It True, search also in the associated CPU/Task graph. |
| * @param index: Output location for the index of the entry under the cursor. |
| * If no entry has been found, the outputted value is zero. |
| * |
| * @returns True, if an entry has been found, otherwise False. |
| */ |
| bool KsGLWidget::find(const QPoint &point, int variance, bool joined, |
| size_t *index) |
| { |
| /* |
| * Get the bin, pid and cpu numbers. |
| * Remember that one bin corresponds to one pixel. |
| */ |
| int bin = point.x() - _hMargin; |
| int cpu = getPlotCPU(point); |
| int pid = getPlotPid(point); |
| |
| return _find(bin, cpu, pid, variance, joined, index); |
| } |
| |
| 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->pos(), 10, true, &row); |
| |
| 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; |
| } |
| |
| /** Get the CPU Id of the Graph plotted at given position. */ |
| int KsGLWidget::getPlotCPU(const QPoint &point) |
| { |
| int cpuId, y = point.y(); |
| |
| 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]; |
| } |
| |
| /** Get the CPU Id of the Graph plotted at given position. */ |
| int KsGLWidget::getPlotPid(const QPoint &point) |
| { |
| int pidId, y = point.y(); |
| |
| 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]; |
| } |