blob: 65e5a796a239274c52c2d7fef6d05d39ea79ddbe [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
*/
/**
* @file KsTraceGraph.cpp
* @brief KernelShark Trace Graph widget.
*/
// KernelShark
#include "KsUtils.hpp"
#include "KsDualMarker.hpp"
#include "KsTraceGraph.hpp"
#include "KsQuickContextMenu.hpp"
/** Create a default (empty) Trace graph widget. */
KsTraceGraph::KsTraceGraph(QWidget *parent)
: KsWidgetsLib::KsDataWidget(parent),
_pointerBar(this),
_navigationBar(this),
_zoomInButton("+", this),
_quickZoomInButton("++", this),
_zoomOutButton("-", this),
_quickZoomOutButton("- -", this),
_scrollLeftButton("<", this),
_scrollRightButton(">", this),
_labelP1("Pointer: ", this),
_labelP2("", this),
_labelI1("", this),
_labelI2("", this),
_labelI3("", this),
_labelI4("", this),
_labelI5("", this),
_scrollArea(this),
_glWindow(&_scrollArea),
_mState(nullptr),
_data(nullptr),
_keyPressed(false)
{
auto lamMakeNavButton = [&](QPushButton *b) {
b->setMaximumWidth(FONT_WIDTH * 5);
connect(b, &QPushButton::released,
this, &KsTraceGraph::_stopUpdating);
_navigationBar.addWidget(b);
};
_pointerBar.setMaximumHeight(FONT_HEIGHT * 1.75);
_pointerBar.setOrientation(Qt::Horizontal);
_navigationBar.setMaximumHeight(FONT_HEIGHT * 1.75);
_navigationBar.setMinimumWidth(FONT_WIDTH * 90);
_navigationBar.setOrientation(Qt::Horizontal);
_pointerBar.addWidget(&_labelP1);
_labelP2.setFrameStyle(QFrame::Panel | QFrame::Sunken);
_labelP2.setStyleSheet("QLabel {background-color : white; color: black}");
_labelP2.setTextInteractionFlags(Qt::TextSelectableByMouse);
_labelP2.setFixedWidth(FONT_WIDTH * 16);
_pointerBar.addWidget(&_labelP2);
_pointerBar.addSeparator();
_labelI1.setStyleSheet("QLabel {color : blue;}");
_labelI2.setStyleSheet("QLabel {color : green;}");
_labelI3.setStyleSheet("QLabel {color : red;}");
_labelI4.setStyleSheet("QLabel {color : blue;}");
_labelI5.setStyleSheet("QLabel {color : green;}");
_pointerBar.addWidget(&_labelI1);
_pointerBar.addSeparator();
_pointerBar.addWidget(&_labelI2);
_pointerBar.addSeparator();
_pointerBar.addWidget(&_labelI3);
_pointerBar.addSeparator();
_pointerBar.addWidget(&_labelI4);
_pointerBar.addSeparator();
_pointerBar.addWidget(&_labelI5);
_glWindow.installEventFilter(this);
connect(&_glWindow, &KsGLWidget::select,
this, &KsTraceGraph::markEntry);
connect(&_glWindow, &KsGLWidget::found,
this, &KsTraceGraph::_setPointerInfo);
connect(&_glWindow, &KsGLWidget::notFound,
this, &KsTraceGraph::_resetPointer);
connect(&_glWindow, &KsGLWidget::zoomIn,
this, &KsTraceGraph::_zoomIn);
connect(&_glWindow, &KsGLWidget::zoomOut,
this, &KsTraceGraph::_zoomOut);
connect(&_glWindow, &KsGLWidget::scrollLeft,
this, &KsTraceGraph::_scrollLeft);
connect(&_glWindow, &KsGLWidget::scrollRight,
this, &KsTraceGraph::_scrollRight);
connect(&_glWindow, &KsGLWidget::stopUpdating,
this, &KsTraceGraph::_stopUpdating);
_glWindow.setContextMenuPolicy(Qt::CustomContextMenu);
connect(&_glWindow, &QWidget::customContextMenuRequested,
this, &KsTraceGraph::_onCustomContextMenu);
_scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_scrollArea.setWidget(&_glWindow);
lamMakeNavButton(&_scrollLeftButton);
connect(&_scrollLeftButton, &QPushButton::pressed,
this, &KsTraceGraph::_scrollLeft);
lamMakeNavButton(&_zoomInButton);
connect(&_zoomInButton, &QPushButton::pressed,
this, &KsTraceGraph::_zoomIn);
lamMakeNavButton(&_zoomOutButton);
connect(&_zoomOutButton, &QPushButton::pressed,
this, &KsTraceGraph::_zoomOut);
lamMakeNavButton(&_scrollRightButton);
connect(&_scrollRightButton, &QPushButton::pressed,
this, &KsTraceGraph::_scrollRight);
_navigationBar.addSeparator();
lamMakeNavButton(&_quickZoomInButton);
connect(&_quickZoomInButton, &QPushButton::pressed,
this, &KsTraceGraph::_quickZoomIn);
lamMakeNavButton(&_quickZoomOutButton);
connect(&_quickZoomOutButton, &QPushButton::pressed,
this, &KsTraceGraph::_quickZoomOut);
_layout.addWidget(&_pointerBar);
_layout.addWidget(&_navigationBar);
_layout.addWidget(&_scrollArea);
this->setLayout(&_layout);
updateGeom();
}
/**
* @brief Load and show trace data.
*
* @param data: Input location for the KsDataStore object.
* KsDataStore::loadDataFile() must be called first.
* @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 KsTraceGraph::loadData(KsDataStore *data, bool resetPlots)
{
_data = data;
_glWindow.loadData(data, resetPlots);
updateGeom();
}
/** Connect the KsGLWidget widget and the State machine of the Dual marker. */
void KsTraceGraph::setMarkerSM(KsDualMarkerSM *m)
{
_mState = m;
_navigationBar.addSeparator();
_mState->placeInToolBar(&_navigationBar);
_glWindow.setMarkerSM(m);
}
/** Reset (empty) the widget. */
void KsTraceGraph::reset()
{
/* Reset (empty) the OpenGL widget. */
_glWindow.reset();
_labelP2.setText("");
for (auto l1: {&_labelI1, &_labelI2, &_labelI3, &_labelI4, &_labelI5})
l1->setText("");
_selfUpdate();
}
void KsTraceGraph::_selfUpdate()
{
_markerReDraw();
_glWindow.model()->update();
updateGeom();
}
void KsTraceGraph::_zoomIn()
{
KsWidgetsLib::KsDataWork action = KsWidgetsLib::KsDataWork::ZoomIn;
startOfWork(action);
_updateGraphs(action);
endOfWork(action);
}
void KsTraceGraph::_zoomOut()
{
KsWidgetsLib::KsDataWork action = KsWidgetsLib::KsDataWork::ZoomOut;
startOfWork(action);
_updateGraphs(action);
endOfWork(action);
}
void KsTraceGraph::_quickZoomIn()
{
if (_glWindow.isEmpty())
return;
startOfWork(KsWidgetsLib::KsDataWork::QuickZoomIn);
/* Bin size will be 100 ns. */
_glWindow.model()->quickZoomIn(100);
if (_mState->activeMarker()._isSet &&
_mState->activeMarker().isVisible()) {
/*
* Use the position of the active marker as
* a focus point of the zoom.
*/
uint64_t ts = _mState->activeMarker()._ts;
_glWindow.model()->jumpTo(ts);
_glWindow.render();
}
endOfWork(KsWidgetsLib::KsDataWork::QuickZoomIn);
}
void KsTraceGraph::_quickZoomOut()
{
if (_glWindow.isEmpty())
return;
startOfWork(KsWidgetsLib::KsDataWork::QuickZoomOut);
_glWindow.model()->quickZoomOut();
_glWindow.render();
endOfWork(KsWidgetsLib::KsDataWork::QuickZoomOut);
}
void KsTraceGraph::_scrollLeft()
{
KsWidgetsLib::KsDataWork action = KsWidgetsLib::KsDataWork::ScrollLeft;
startOfWork(action);
_updateGraphs(action);
endOfWork(action);
}
void KsTraceGraph::_scrollRight()
{
KsWidgetsLib::KsDataWork action = KsWidgetsLib::KsDataWork::ScrollRight;
startOfWork(action);
_updateGraphs(action);
endOfWork(action);
}
void KsTraceGraph::_stopUpdating()
{
/*
* The user is no longer pressing the action button. Reset the
* "Key Pressed" flag. This will stop the ongoing user action.
*/
_keyPressed = false;
}
QString KsTraceGraph::_t2str(uint64_t sec, uint64_t usec) {
QString usecStr;
QTextStream ts(&usecStr);
ts.setFieldAlignment(QTextStream::AlignRight);
ts.setFieldWidth(6);
ts.setPadChar('0');
ts << usec;
return QString::number(sec) + "." + usecStr;
}
void KsTraceGraph::_resetPointer(int64_t ts, int sd, int cpu, int pid)
{
kshark_entry entry;
uint64_t sec, usec;
entry.cpu = cpu;
entry.pid = pid;
entry.stream_id = sd;
if (ts < 0)
ts = 0;
kshark_convert_nano(ts, &sec, &usec);
_labelP2.setText(_t2str(sec, usec));
if (pid > 0 && cpu >= 0) {
struct kshark_context *kshark_ctx(NULL);
if (!kshark_instance(&kshark_ctx))
return;
QString comm(kshark_get_task(&entry));
comm.append("-");
comm.append(QString("%1").arg(pid));
_labelI1.setText(comm);
_labelI2.setText(QString("CPU %1").arg(cpu));
} else {
_labelI1.setText("");
_labelI2.setText("");
}
for (auto const &l: {&_labelI3, &_labelI4, &_labelI5}) {
l->setText("");
}
}
void KsTraceGraph::_setPointerInfo(size_t i)
{
kshark_entry *e = _data->rows()[i];
auto lanMakeString = [] (char *buffer) {
QString str(buffer);
free(buffer);
return str;
};
QString event(lanMakeString(kshark_get_event_name(e)));
QString aux(lanMakeString(kshark_get_aux_info(e)));
QString info(lanMakeString(kshark_get_info(e)));
QString comm(lanMakeString(kshark_get_task(e)));
QString elidedText;
int labelWidth;
uint64_t sec, usec;
char *pointer;
kshark_convert_nano(e->ts, &sec, &usec);
labelWidth = asprintf(&pointer, "%" PRIu64 ".%06" PRIu64 "", sec, usec);
if (labelWidth <= 0)
return;
_labelP2.setText(pointer);
free(pointer);
comm.append("-");
comm.append(QString("%1").arg(kshark_get_pid(e)));
_labelI1.setText(comm);
_labelI2.setText(QString("CPU %1").arg(e->cpu));
_labelI3.setText(aux);
_labelI4.setText(event);
_labelI5.setText(info);
QCoreApplication::processEvents();
labelWidth =
_pointerBar.geometry().right() - _labelI4.geometry().right();
if (labelWidth > STRING_WIDTH(info) + FONT_WIDTH * 5)
return;
/*
* The Info string is too long and cannot be displayed on the toolbar.
* Try to fit the text in the available space.
*/
KsUtils::setElidedText(&_labelI5, info, Qt::ElideRight, labelWidth);
_labelI5.setVisible(true);
QCoreApplication::processEvents();
}
/**
* @brief Use the active marker to select particular entry.
*
* @param row: The index of the entry to be selected by the marker.
*/
void KsTraceGraph::markEntry(size_t row)
{
int yPosVis(-1);
_glWindow.model()->jumpTo(_data->rows()[row]->ts);
_mState->activeMarker().set(*_data, _glWindow.model()->histo(),
row, _data->rows()[row]->stream_id);
_mState->updateMarkers(*_data, &_glWindow);
/*
* If a Combo graph has been found, this Combo graph will be visible.
* Else the Task graph will be shown. If no Combo and no Task graph
* has been found, make visible the corresponding CPU graph.
*/
if (_mState->activeMarker()._mark.comboIsVisible())
yPosVis = _mState->activeMarker()._mark.comboY();
else if (_mState->activeMarker()._mark.taskIsVisible())
yPosVis = _mState->activeMarker()._mark.taskY();
else if (_mState->activeMarker()._mark.cpuIsVisible())
yPosVis = _mState->activeMarker()._mark.cpuY();
if (yPosVis > 0)
_scrollArea.ensureVisible(0, yPosVis);
}
void KsTraceGraph::_markerReDraw()
{
size_t row;
if (_mState->markerA()._isSet) {
row = _mState->markerA()._pos;
_mState->markerA().set(*_data, _glWindow.model()->histo(),
row, _data->rows()[row]->stream_id);
}
if (_mState->markerB()._isSet) {
row = _mState->markerB()._pos;
_mState->markerB().set(*_data, _glWindow.model()->histo(),
row, _data->rows()[row]->stream_id);
}
}
/**
* @brief Redreaw all CPU graphs.
*
* @param sd: Data stream identifier.
* @param v: CPU ids to be plotted.
*/
void KsTraceGraph::cpuReDraw(int sd, QVector<int> v)
{
startOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
if (_glWindow._streamPlots.contains(sd))
_glWindow._streamPlots[sd]._cpuList = v;
_selfUpdate();
endOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
}
/**
* @brief Redreaw all Task graphs.
*
* @param sd: Data stream identifier.
* @param v: Process ids of the tasks to be plotted.
*/
void KsTraceGraph::taskReDraw(int sd, QVector<int> v)
{
startOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
if (_glWindow._streamPlots.contains(sd))
_glWindow._streamPlots[sd]._taskList = v;
_selfUpdate();
endOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
}
/**
* @brief Redreaw all virtCombo graphs.
*
* @param nCombos: Numver of Combo plots.
* @param v: Descriptor of the Combo to be plotted.
*/
void KsTraceGraph::comboReDraw(int nCombos, QVector<int> v)
{
KsComboPlot combo;
startOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
_glWindow._comboPlots.clear();
for (int i = 0; i < nCombos; ++i) {
combo.resize(v.takeFirst());
for (auto &p: combo)
p << v;
_glWindow._comboPlots.append(combo);
}
_selfUpdate();
endOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
}
/** Add (and plot) a CPU graph to the existing list of CPU graphs. */
void KsTraceGraph::addCPUPlot(int sd, int cpu)
{
startOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
QVector<int> &list = _glWindow._streamPlots[sd]._cpuList;
if (list.contains(cpu))
return;
list.append(cpu);
std::sort(list.begin(), list.end());
_selfUpdate();
endOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
}
/** Add (and plot) a Task graph to the existing list of Task graphs. */
void KsTraceGraph::addTaskPlot(int sd, int pid)
{
startOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
QVector<int> &list = _glWindow._streamPlots[sd]._taskList;
if (list.contains(pid))
return;
list.append(pid);
std::sort(list.begin(), list.end());
_selfUpdate();
endOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
}
/** Remove a CPU graph from the existing list of CPU graphs. */
void KsTraceGraph::removeCPUPlot(int sd, int cpu)
{
startOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
if (!_glWindow._streamPlots[sd]._cpuList.contains(cpu))
return;
_glWindow._streamPlots[sd]._cpuList.removeAll(cpu);
_selfUpdate();
endOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
}
/** Remove a Task graph from the existing list of Task graphs. */
void KsTraceGraph::removeTaskPlot(int sd, int pid)
{
startOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
if (!_glWindow._streamPlots[sd]._taskList.contains(pid))
return;
_glWindow._streamPlots[sd]._taskList.removeAll(pid);
_selfUpdate();
endOfWork(KsWidgetsLib::KsDataWork::EditPlotList);
}
/** Update the content of all graphs. */
void KsTraceGraph::update(KsDataStore *data)
{
kshark_context *kshark_ctx(nullptr);
QVector<int> streamIds;
if (!kshark_instance(&kshark_ctx))
return;
streamIds = KsUtils::getStreamIdList(kshark_ctx);
for (auto const &sd: streamIds)
for (auto &pid: _glWindow._streamPlots[sd]._taskList) {
kshark_unregister_data_collection(&kshark_ctx->collections,
kshark_match_pid,
sd, &pid, 1);
}
_selfUpdate();
streamIds = KsUtils::getStreamIdList(kshark_ctx);
for (auto const &sd: streamIds)
for (auto &pid: _glWindow._streamPlots[sd]._taskList) {
kshark_register_data_collection(kshark_ctx,
data->rows(),
data->size(),
kshark_match_pid,
sd, &pid, 1,
25);
}
}
/** Update the geometry of the widget. */
void KsTraceGraph::updateGeom()
{
int saWidth, saHeight, dwWidth, hMin;
/* Set the size of the Scroll Area. */
saWidth = width() - _layout.contentsMargins().left() -
_layout.contentsMargins().right();
saHeight = height() - _pointerBar.height() -
_navigationBar.height() -
_layout.spacing() * 2 -
_layout.contentsMargins().top() -
_layout.contentsMargins().bottom();
_scrollArea.resize(saWidth, saHeight);
/*
* Calculate the width of the Draw Window, taking into account the size
* of the scroll bar.
*/
dwWidth = _scrollArea.width();
if (_glWindow.height() > _scrollArea.height())
dwWidth -=
qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent);
_glWindow.resize(dwWidth, _glWindow.height());
/* Set the minimum height of the Graph widget. */
hMin = _glWindow.height() +
_pointerBar.height() +
_navigationBar.height() +
_layout.contentsMargins().top() +
_layout.contentsMargins().bottom();
if (hMin > KS_GRAPH_HEIGHT * 8)
hMin = KS_GRAPH_HEIGHT * 8;
setMinimumHeight(hMin);
/*
* Now use the height of the Draw Window to fix the maximum height
* of the Graph widget.
*/
setMaximumHeight(_glWindow.height() +
_pointerBar.height() +
_navigationBar.height() +
_layout.spacing() * 2 +
_layout.contentsMargins().top() +
_layout.contentsMargins().bottom() +
2); /* Just a little bit of extra space. This will
* allow the scroll bar to disappear when the
* widget is extended to maximum.
*/
_glWindow.update();
}
/**
* Reimplemented event handler used to update the geometry of the widget on
* resize events.
*/
void KsTraceGraph::resizeEvent(QResizeEvent* event)
{
updateGeom();
}
/**
* Reimplemented event handler (overriding a virtual function from QObject)
* used to detect the position of the mouse with respect to the Draw window and
* according to this position to grab / release the focus of the keyboard. The
* function has nothing to do with the filtering of the trace events.
*/
bool KsTraceGraph::eventFilter(QObject* obj, QEvent* evt)
{
/* Desable all mouse events for the OpenGL wiget when busy. */
if (obj == &_glWindow && this->isBusy() &&
(evt->type() == QEvent::MouseButtonDblClick ||
evt->type() == QEvent::MouseButtonPress ||
evt->type() == QEvent::MouseButtonRelease ||
evt->type() == QEvent::MouseMove)
)
return true;
if (obj == &_glWindow && evt->type() == QEvent::Enter)
_glWindow.setFocus();
if (obj == &_glWindow && evt->type() == QEvent::Leave)
_glWindow.clearFocus();
return QWidget::eventFilter(obj, evt);
}
void KsTraceGraph::_updateGraphs(KsWidgetsLib::KsDataWork action)
{
double k;
int bin;
if (_glWindow.isEmpty())
return;
/*
* Set the "Key Pressed" flag. The flag will stay set as long as the user
* keeps the corresponding action button pressed.
*/
_keyPressed = true;
/* Initialize the zooming factor with a small value. */
k = .01;
while (_keyPressed) {
switch (action) {
case KsWidgetsLib::KsDataWork::ZoomIn:
if (_mState->activeMarker()._isSet &&
_mState->activeMarker().isVisible()) {
/*
* Use the position of the active marker as
* a focus point of the zoom.
*/
bin = _mState->activeMarker()._bin;
_glWindow.model()->zoomIn(k, bin);
} else {
/*
* The default focus point is the center of the
* range interval of the model.
*/
_glWindow.model()->zoomIn(k);
}
break;
case KsWidgetsLib::KsDataWork::ZoomOut:
if (_mState->activeMarker()._isSet &&
_mState->activeMarker().isVisible()) {
/*
* Use the position of the active marker as
* a focus point of the zoom.
*/
bin = _mState->activeMarker()._bin;
_glWindow.model()->zoomOut(k, bin);
} else {
/*
* The default focus point is the center of the
* range interval of the model.
*/
_glWindow.model()->zoomOut(k);
}
break;
case KsWidgetsLib::KsDataWork::ScrollLeft:
_glWindow.model()->shiftBackward(10);
break;
case KsWidgetsLib::KsDataWork::ScrollRight:
_glWindow.model()->shiftForward(10);
break;
default:
return;
}
/*
* As long as the action button is pressed, the zooming factor
* will grow smoothly until it reaches a maximum value. This
* will have a visible effect of an accelerating zoom.
*/
if (k < .25)
k *= 1.02;
_mState->updateMarkers(*_data, &_glWindow);
_glWindow.render();
QCoreApplication::processEvents();
}
}
void KsTraceGraph::_onCustomContextMenu(const QPoint &point)
{
KsQuickMarkerMenu *menu(nullptr);
int sd, cpu, pid;
size_t row;
bool found;
found = _glWindow.find(point, 20, true, &row);
if (found) {
/* KernelShark entry has been found under the cursor. */
KsQuickContextMenu *entryMenu;
menu = entryMenu = new KsQuickContextMenu(_mState, _data, row,
this);
connect(entryMenu, &KsQuickContextMenu::addTaskPlot,
this, &KsTraceGraph::addTaskPlot);
connect(entryMenu, &KsQuickContextMenu::addCPUPlot,
this, &KsTraceGraph::addCPUPlot);
connect(entryMenu, &KsQuickContextMenu::removeTaskPlot,
this, &KsTraceGraph::removeTaskPlot);
connect(entryMenu, &KsQuickContextMenu::removeCPUPlot,
this, &KsTraceGraph::removeCPUPlot);
} else {
if (!_glWindow.getPlotInfo(point, &sd, &cpu, &pid))
return;
if (cpu >= 0) {
/*
* This is a CPU plot, but we do not have an entry
* under the cursor.
*/
KsRmCPUPlotMenu *rmMenu;
menu = rmMenu = new KsRmCPUPlotMenu(_mState, sd, cpu, this);
auto lamRmPlot = [&sd, &cpu, this] () {
removeCPUPlot(sd, cpu);
};
connect(rmMenu, &KsRmPlotContextMenu::removePlot,
lamRmPlot);
}
if (pid >= 0) {
/*
* This is a Task plot, but we do not have an entry
* under the cursor.
*/
KsRmTaskPlotMenu *rmMenu;
menu = rmMenu = new KsRmTaskPlotMenu(_mState, sd, pid, this);
auto lamRmPlot = [&sd, &pid, this] () {
removeTaskPlot(sd, pid);
};
connect(rmMenu, &KsRmPlotContextMenu::removePlot,
lamRmPlot);
}
}
if (menu) {
connect(menu, &KsQuickMarkerMenu::deselect,
this, &KsTraceGraph::deselect);
/*
* Note that this slot was connected to the
* customContextMenuRequested signal of the OpenGL widget.
* Because of this the coordinates of the point are given with
* respect to the frame of this widget.
*/
QPoint global = _glWindow.mapToGlobal(point);
global.ry() -= menu->sizeHint().height() / 2;
/*
* Shift the menu so that it is not positioned under the mouse.
* This will prevent from an accidental selection of the menu
* item under the mouse.
*/
global.rx() += FONT_WIDTH;
menu->exec(global);
}
}