blob: 3b2e0d4d7a10fa5a1934a8093ebb82b3a9f12ef7 [file] [log] [blame]
// 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 "libkshark-plugin.h"
#include "KsGLWidget.hpp"
#include "KsUtils.hpp"
#include "KsPlugins.hpp"
/** A stream operator for converting vector of integers into KsPlotEntry. */
KsPlotEntry &operator <<(KsPlotEntry &plot, QVector<int> &v)
{
plot._streamId = v.takeFirst();
plot._type = v.takeFirst();
plot._id = v.takeFirst();
return plot;
}
/** A stream operator for converting KsPlotEntry into vector of integers. */
void operator >>(const KsPlotEntry &plot, QVector<int> &v)
{
v.append(plot._streamId);
v.append(plot._type);
v.append(plot._id);
}
/** Create a default (empty) OpenGL widget. */
KsGLWidget::KsGLWidget(QWidget *parent)
: QOpenGLWidget(parent),
_labelSize(100),
_hMargin(15),
_vMargin(25),
_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()));
}
void KsGLWidget::_freeGraphs()
{
for (auto &stream: _graphs) {
for (auto &g: stream)
delete g;
stream.resize(0);
}
}
void KsGLWidget::freePluginShapes()
{
while (!_shapes.empty()) {
auto s = _shapes.front();
_shapes.pop_front();
delete s;
}
}
KsGLWidget::~KsGLWidget()
{
_freeGraphs();
freePluginShapes();
}
/** Reimplemented function used to set up all required OpenGL resources. */
void KsGLWidget::initializeGL()
{
_dpr = QApplication::desktop()->devicePixelRatio();
ksplot_init_opengl(_dpr);
ksplot_init_font(&_font, 15, TT_FONT_FILE);
_labelSize = _getMaxLabelSize() + FONT_WIDTH * 2;
update();
}
/**
* 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() - _bin0Offset() - _hMargin;
if (nBins <= 0)
return;
/*
* 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);
}
/** Reimplemented function used to plot trace graphs. */
void KsGLWidget::paintGL()
{
float size = 1.5 * _dpr;
glClear(GL_COLOR_BUFFER_BIT);
if (isEmpty())
return;
render();
/* Draw the time axis. */
_drawAxisX(size);
for (auto const &stream: _graphs)
for (auto const &g: stream)
g->draw(size);
for (auto const &s: _shapes) {
if (!s)
continue;
if (s->_size < 0)
s->_size = size + abs(s->_size + 1);
s->draw();
}
/*
* 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();
}
/** Process and draw all graphs. */
void KsGLWidget::render()
{
/* Process and draw all graphs by using the built-in logic. */
_makeGraphs();
/* Process and draw all plugin-specific shapes. */
_makePluginShapes();
};
/** Reset (empty) the widget. */
void KsGLWidget::reset()
{
_streamPlots.clear();
_comboPlots.clear();
_data = nullptr;
_model.reset();
}
/** 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 sd, 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,
sd, &cpu, 1);
for (int b = bin; b >= 0; --b) {
pid = ksmodel_get_pid_back(histo, b, sd, cpu,
false, col, nullptr);
if (pid >= 0)
return pid;
}
return ksmodel_get_pid_back(histo, LOWER_OVERFLOW_BIN,
sd,
cpu,
false,
col,
nullptr);
}
int KsGLWidget::_getLastCPU(struct kshark_trace_histo *histo,
int bin, int sd, 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,
sd, &pid, 1);
for (int b = bin; b >= 0; --b) {
cpu = ksmodel_get_cpu_back(histo, b, sd, pid,
false, col, nullptr);
if (cpu >= 0)
return cpu;
}
return ksmodel_get_cpu_back(histo, LOWER_OVERFLOW_BIN,
sd,
pid,
false,
col,
nullptr);
}
/** Reimplemented event handler used to receive mouse move events. */
void KsGLWidget::mouseMoveEvent(QMouseEvent *event)
{
int bin, sd, cpu, pid;
size_t row;
bool ret;
if (isEmpty())
return;
if (_rubberBand.isVisible())
_rangeBoundStretched(_posInRange(event->pos().x()));
bin = event->pos().x() - _bin0Offset();
getPlotInfo(event->pos(), &sd, &cpu, &pid);
ret = _find(bin, sd, cpu, pid, 5, false, &row);
if (ret) {
emit found(row);
} else {
if (cpu >= 0) {
pid = _getLastTask(_model.histo(), bin, sd, cpu);
}
if (pid > 0) {
cpu = _getLastCPU(_model.histo(), bin, sd, pid);
}
emit notFound(ksmodel_bin_ts(_model.histo(), bin), sd, cpu, pid);
}
}
/** Reimplemented event handler used to receive mouse release events. */
void KsGLWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (isEmpty())
return;
if (event->button() == Qt::LeftButton) {
size_t posMouseRel = _posInRange(event->pos().x());
int min, max;
if (_posMousePress < posMouseRel) {
min = _posMousePress - _bin0Offset();
max = posMouseRel - _bin0Offset();
} else {
max = _posMousePress - _bin0Offset();
min = posMouseRel - _bin0Offset();
}
_rangeChanged(min, max);
}
}
/** Reimplemented event handler used to receive mouse double click events. */
void KsGLWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
KsPlot::PlotObject *pluginClicked(nullptr);
double distance, distanceMin = FONT_HEIGHT;
for (auto const &s: _shapes) {
distance = s->distance(event->pos().x(), event->pos().y());
if (distance < distanceMin) {
distanceMin = distance;
pluginClicked = s;
}
}
if (pluginClicked)
pluginClicked->doubleClick();
else if (event->button() == Qt::LeftButton)
_findAndSelect(event);
}
/** Reimplemented event handler used to receive mouse wheel events. */
void KsGLWidget::wheelEvent(QWheelEvent * event)
{
int zoomFocus;
if (QApplication::keyboardModifiers() != Qt::ControlModifier ||
isEmpty())
return;
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.
*/
#ifdef QT_VERSION_LESS_5_15
zoomFocus = event->pos().x() - _bin0Offset();
#else
zoomFocus = event->position().x() - _bin0Offset();
#endif // QT_VERSION_LESS_5_15
}
if (event->angleDelta().y() > 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;
}
/**
* The maximum number of CPU plots to be shown by default when the GUI starts.
*/
#define KS_MAX_START_PLOTS 16
void KsGLWidget::_defaultPlots(kshark_context *kshark_ctx)
{
QVector<int> streamIds, plotVec;
uint64_t tMin, tMax;
int nCPUs, nBins;
_model.reset();
_streamPlots.clear();
/*
* Make default CPU and Task lists. All CPUs from all Data streams will
* be plotted. No tasks will be plotted.
*/
streamIds = KsUtils::getStreamIdList(kshark_ctx);
for (auto const &sd: streamIds) {
nCPUs = kshark_ctx->stream[sd]->n_cpus;
plotVec.clear();
/* If the number of CPUs is too big show only the first 16. */
if (nCPUs > KS_MAX_START_PLOTS / kshark_ctx->n_streams)
nCPUs = KS_MAX_START_PLOTS / kshark_ctx->n_streams;
for (int i = 0; i < nCPUs; ++i)
plotVec.append(i);
_streamPlots[sd]._cpuList = plotVec;
_streamPlots[sd]._taskList = {};
}
/*
* From the size of the widget, calculate the number of bins.
* One bin will correspond to one pixel.
*/
nBins = width() - _bin0Offset() - _hMargin;
if (nBins < 0)
nBins = 0;
/* 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);
}
/**
* @brief Load and show trace data.
*
* @param data: Input location for the KsDataStore object.
* KsDataStore::loadDataFile() must be called first.
* @param resetPlots: If true, all existing graphs are closed
* and a default configuration of graphs is displayed
* (all CPU plots). If false, the current set of graphs
* is preserved.
*/
void KsGLWidget::loadData(KsDataStore *data, bool resetPlots)
{
kshark_context *kshark_ctx(nullptr);
if (!kshark_instance(&kshark_ctx) || !kshark_ctx->n_streams)
return;
loadColors();
_data = data;
if (resetPlots)
_defaultPlots(kshark_ctx);
_model.fill(_data);
}
/**
* 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::taskColorTable();
_cpuColors.clear();
_cpuColors = KsPlot::CPUColorTable();
_streamColors.clear();
_streamColors = KsPlot::streamColorTable();
}
/**
* Position the graphical elements of the marker according to the current
* position of the graphs inside the GL widget.
*/
void KsGLWidget::setMarkPoints(const KsDataStore &data, KsGraphMark *mark)
{
const kshark_entry *e = data.rows()[mark->_pos];
int sd = e->stream_id;
mark->_mark.setDPR(_dpr);
mark->_mark.setX(mark->_bin + _bin0Offset());
mark->_mark.setY(_vMargin * 3 / 2 + 2, height() - _vMargin / 4);
mark->_mark.setCPUVisible(false);
mark->_mark.setTaskVisible(false);
mark->_mark.setComboVisible(false);
for (int i = 0; i < _streamPlots[sd]._cpuList.count(); ++i) {
if (_streamPlots[sd]._cpuList[i] == e->cpu) {
mark->_mark.setCPUY(_streamPlots[sd]._cpuGraphs[i]->base());
mark->_mark.setCPUVisible(true);
}
}
for (int i = 0; i < _streamPlots[sd]._taskList.count(); ++i) {
if (_streamPlots[sd]._taskList[i] == e->pid) {
mark->_mark.setTaskY(_streamPlots[sd]._taskGraphs[i]->base());
mark->_mark.setTaskVisible(true);
}
}
for (auto const &c: _comboPlots)
for (auto const &p: c) {
if (p._streamId != e->stream_id)
continue;
if (p._type & KSHARK_CPU_DRAW &&
p._id == e->cpu) {
mark->_mark.setComboY(p.base());
mark->_mark.setComboVisible(true);
} else if (p._type & KSHARK_TASK_DRAW &&
p._id == e->pid) {
mark->_mark.setComboY(p.base());
mark->_mark.setComboVisible(true);
}
}
}
void KsGLWidget::_drawAxisX(float size)
{
int64_t model_min = model()->histo()->min;
int64_t model_max = model()->histo()->max;
uint64_t sec, usec, tsMid;
char *tMin, *tMid, *tMax;
int mid = (width() - _bin0Offset() - _hMargin) / 2;
int y_1 = _vMargin * 5 / 4;
int y_2 = _vMargin * 6 / 4;
int count;
KsPlot::Point a0(_bin0Offset(), y_1);
KsPlot::Point a1(_bin0Offset(), y_2);
KsPlot::Point b0(_bin0Offset() + mid, y_1);
KsPlot::Point b1(_bin0Offset() + mid, y_2);
KsPlot::Point c0(width() - _hMargin, y_1);
KsPlot::Point c1(width() - _hMargin, y_2);
a0._size = c0._size = _dpr;
a0.draw();
c0.draw();
KsPlot::drawLine(a0, a1, {}, size);
KsPlot::drawLine(b0, b1, {}, size);
KsPlot::drawLine(c0, c1, {}, size);
KsPlot::drawLine(a0, c0, {}, size);
if (model_min < 0)
model_min = 0;
kshark_convert_nano(model_min, &sec, &usec);
count = asprintf(&tMin,"%" PRIu64 ".%06" PRIu64 "", sec, usec);
if (count <= 0)
return;
tsMid = (model_min + model_max) / 2;
kshark_convert_nano(tsMid, &sec, &usec);
count = asprintf(&tMid, "%" PRIu64 ".%06" PRIu64 "", sec, usec);
if (count <= 0)
return;
kshark_convert_nano(model_max, &sec, &usec);
count = asprintf(&tMax, "%" PRIu64 ".%06" PRIu64 "", sec, usec);
if (count <= 0)
return;
ksplot_print_text(&_font, nullptr,
a0.x(),
a0.y() - _hMargin / 2,
tMin);
ksplot_print_text(&_font, nullptr,
b0.x() - _font.char_width * count / 2,
b0.y() - _hMargin / 2,
tMid);
ksplot_print_text(&_font, nullptr,
c0.x() - _font.char_width * count,
c0.y() - _hMargin / 2,
tMax);
free(tMin);
free(tMid);
free(tMax);
}
int KsGLWidget::_getMaxLabelSize()
{
int size, max(0);
for (auto it = _streamPlots.begin(); it != _streamPlots.end(); ++it) {
int sd = it.key();
for (auto const &pid: it.value()._taskList) {
size = _font.char_width *
KsUtils::taskPlotName(sd, pid).count();
max = (size > max) ? size : max;
}
for (auto const &cpu: it.value()._cpuList) {
size = _font.char_width * KsUtils::cpuPlotName(cpu).count();
max = (size > max) ? size : max;
}
}
for (auto &c: _comboPlots)
for (auto const &p: c) {
if (p._type & KSHARK_TASK_DRAW) {
size = _font.char_width *
KsUtils::taskPlotName(p._streamId, p._id).count();
max = (size > max) ? size : max;
} else if (p._type & KSHARK_CPU_DRAW) {
size = _font.char_width *
KsUtils::cpuPlotName(p._id).count();
max = (size > max) ? size : max;
}
}
return max;
}
void KsGLWidget::_makeGraphs()
{
int base(_vMargin * 2 + KS_GRAPH_HEIGHT), sd;
KsPlot::Graph *g;
/* The very first thing to do is to clean up. */
_freeGraphs();
if (!_data || !_data->size())
return;
_labelSize = _getMaxLabelSize() + FONT_WIDTH * 2;
auto lamAddGraph = [&](int sd, KsPlot::Graph *graph, int vSpace=0) {
if (!graph)
return graph;
KsPlot::Color color = {255, 255, 255}; // White
/*
* Calculate the base level of the CPU graph inside the widget.
* Remember that the "Y" coordinate is inverted.
*/
graph->setBase(base);
/*
* If we have multiple Data streams use the color of the stream
* for the label of the graph.
*/
if (KsUtils::getNStreams() > 1)
color = KsPlot::getColor(&_streamColors, sd);
graph->setLabelAppearance(&_font,
color,
_labelSize,
_hMargin);
_graphs[sd].append(graph);
base += graph->height() + vSpace;
return graph;
};
for (auto it = _streamPlots.begin(); it != _streamPlots.end(); ++it) {
sd = it.key();
/* Create CPU graphs according to the cpuList. */
it.value()._cpuGraphs = {};
for (auto const &cpu: it.value()._cpuList) {
g = lamAddGraph(sd, _newCPUGraph(sd, cpu), _vSpacing);
it.value()._cpuGraphs.append(g);
}
/* Create Task graphs according to the taskList. */
it.value()._taskGraphs = {};
for (auto const &pid: it.value()._taskList) {
g = lamAddGraph(sd, _newTaskGraph(sd, pid), _vSpacing);
it.value()._taskGraphs.append(g);
}
}
for (auto &c: _comboPlots) {
int n = c.count();
for (int i = 0; i < n; ++i) {
sd = c[i]._streamId;
if (c[i]._type & KSHARK_TASK_DRAW) {
c[i]._graph = lamAddGraph(sd, _newTaskGraph(sd, c[i]._id));
} else if (c[i]._type & KSHARK_CPU_DRAW) {
c[i]._graph = lamAddGraph(sd, _newCPUGraph(sd, c[i]._id));
} else {
c[i]._graph = nullptr;
}
if (c[i]._graph && i < n - 1)
c[i]._graph->setDrawBase(false);
}
base += _vSpacing;
}
}
void KsGLWidget::_makePluginShapes()
{
kshark_context *kshark_ctx(nullptr);
kshark_draw_handler *draw_handlers;
struct kshark_data_stream *stream;
KsCppArgV cppArgv;
int sd;
if (!kshark_instance(&kshark_ctx))
return;
/* The very first thing to do is to clean up. */
freePluginShapes();
cppArgv._histo = _model.histo();
cppArgv._shapes = &_shapes;
for (auto it = _streamPlots.constBegin(); it != _streamPlots.constEnd(); ++it) {
sd = it.key();
stream = kshark_get_data_stream(kshark_ctx, sd);
if (!stream)
continue;
for (int g = 0; g < it.value()._cpuList.count(); ++g) {
cppArgv._graph = it.value()._cpuGraphs[g];
draw_handlers = stream->draw_handlers;
while (draw_handlers) {
draw_handlers->draw_func(cppArgv.toC(),
sd,
it.value()._cpuList[g],
KSHARK_CPU_DRAW);
draw_handlers = draw_handlers->next;
}
}
for (int g = 0; g < it.value()._taskList.count(); ++g) {
cppArgv._graph = it.value()._taskGraphs[g];
draw_handlers = stream->draw_handlers;
while (draw_handlers) {
draw_handlers->draw_func(cppArgv.toC(),
sd,
it.value()._taskList[g],
KSHARK_TASK_DRAW);
draw_handlers = draw_handlers->next;
}
}
}
for (auto const &c: _comboPlots) {
for (auto const &p: c) {
stream = kshark_get_data_stream(kshark_ctx, p._streamId);
draw_handlers = stream->draw_handlers;
cppArgv._graph = p._graph;
while (draw_handlers) {
draw_handlers->draw_func(cppArgv.toC(),
p._streamId,
p._id,
p._type);
draw_handlers = draw_handlers->next;
}
}
}
}
KsPlot::Graph *KsGLWidget::_newCPUGraph(int sd, int cpu)
{
QString name;
/* The CPU graph needs to know only the colors of the tasks. */
KsPlot::Graph *graph = new KsPlot::Graph(_model.histo(),
&_pidColors,
&_pidColors);
kshark_context *kshark_ctx(nullptr);
kshark_data_stream *stream;
kshark_entry_collection *col;
if (!kshark_instance(&kshark_ctx))
return nullptr;
stream = kshark_get_data_stream(kshark_ctx, sd);
if (!stream)
return nullptr;
graph->setIdleSuppressed(true, stream->idle_pid);
graph->setHeight(KS_GRAPH_HEIGHT);
graph->setLabelText(KsUtils::cpuPlotName(cpu).toStdString());
col = kshark_find_data_collection(kshark_ctx->collections,
KsUtils::matchCPUVisible,
sd, &cpu, 1);
graph->setDataCollectionPtr(col);
graph->fillCPUGraph(sd, cpu);
return graph;
}
KsPlot::Graph *KsGLWidget::_newTaskGraph(int sd, int pid)
{
QString name;
/*
* 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;
kshark_data_stream *stream;
if (!kshark_instance(&kshark_ctx))
return nullptr;
stream = kshark_get_data_stream(kshark_ctx, sd);
if (!stream)
return nullptr;
graph->setHeight(KS_GRAPH_HEIGHT);
graph->setLabelText(KsUtils::taskPlotName(sd, pid).toStdString());
col = kshark_find_data_collection(kshark_ctx->collections,
kshark_match_pid, sd, &pid, 1);
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,
sd, &pid, 1,
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(sd, 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)
{
int bin, sd, cpu, pid;
/*
* Get the bin, pid and cpu numbers.
* Remember that one bin corresponds to one pixel.
*/
bin = point.x() - _bin0Offset();
getPlotInfo(point, &sd, &cpu, &pid);
return _find(bin, sd, cpu, pid, variance, joined, index);
}
int KsGLWidget::_getNextCPU(int bin, int sd, 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,
sd, &pid, 1);
if (!col)
return KS_EMPTY_BIN;
for (int i = bin; i < _model.histo()->n_bins; ++i) {
cpu = ksmodel_get_cpu_front(_model.histo(), i, sd, pid,
false, col, nullptr);
if (cpu >= 0)
return cpu;
}
return KS_EMPTY_BIN;
}
bool KsGLWidget::_find(int bin, int sd, 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, sd, 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, sd, 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(sd, bin, pid);
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);
_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 < _bin0Offset())
posX = _bin0Offset();
else if (x > (width() - _hMargin))
posX = width() - _hMargin;
else
posX = x;
return posX;
}
/**
* @brief Get information about the graph plotted at given position (under the mouse).
*
* @param point: The position to be inspected.
* @param sd: Output location for the Data stream Identifier of the graph.
* @param cpu: Output location for the CPU Id of the graph, or -1 if this is
* a Task graph.
* @param pid: Output location for the Process Id of the graph, or -1 if this is
* a CPU graph.
*/
bool KsGLWidget::getPlotInfo(const QPoint &point, int *sd, int *cpu, int *pid)
{
int base, n;
*sd = *cpu = *pid = -1;
for (auto it = _streamPlots.constBegin(); it != _streamPlots.constEnd(); ++it) {
n = it.value()._cpuList.count();
for (int i = 0; i < n; ++i) {
base = it.value()._cpuGraphs[i]->base();
if (base - KS_GRAPH_HEIGHT < point.y() &&
point.y() < base) {
*sd = it.key();
*cpu = it.value()._cpuList[i];
return true;
}
}
n = it.value()._taskList.count();
for (int i = 0; i < n; ++i) {
base = it.value()._taskGraphs[i]->base();
if (base - KS_GRAPH_HEIGHT < point.y() &&
point.y() < base) {
*sd = it.key();
*pid = it.value()._taskList[i];
return true;
}
}
}
for (auto const &c: _comboPlots) {
for (auto const &p: c) {
base = p.base();
if (base - KS_GRAPH_HEIGHT < point.y() && point.y() < base) {
*sd = p._streamId;
if (p._type & KSHARK_CPU_DRAW)
*cpu = p._id;
else if (p._type & KSHARK_TASK_DRAW)
*pid = p._id;
return true;
}
}
}
return false;
}