From patchwork Tue Aug 28 13:08:37 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759217 Return-Path: Received: from mail-wm0-f66.google.com ([74.125.82.66]:40842 "EHLO mail-wm0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727218AbeH1RAn (ORCPT ); Tue, 28 Aug 2018 13:00:43 -0400 Received: by mail-wm0-f66.google.com with SMTP id 207-v6so1934840wme.5 for ; Tue, 28 Aug 2018 06:09:04 -0700 (PDT) From: "Yordan Karadzhov (VMware)" To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, "Yordan Karadzhov (VMware)" Subject: [PATCH v3 3/4] kernel-shark-qt: Add C++ API for drawing of Graphs Date: Tue, 28 Aug 2018 16:08:37 +0300 Message-Id: <20180828130838.30790-4-y.karadz@gmail.com> In-Reply-To: <20180828130838.30790-1-y.karadz@gmail.com> References: <20180828130838.30790-1-y.karadz@gmail.com> Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 37163 This patch extends the KernelShark Plotting library, by adding a C++ API for drawing CPU and Task Graphs. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 3 +- kernel-shark-qt/src/KsPlotTools.cpp | 1123 +++++++++++++++++++++++++++ kernel-shark-qt/src/KsPlotTools.hpp | 485 ++++++++++++ 3 files changed, 1610 insertions(+), 1 deletion(-) create mode 100644 kernel-shark-qt/src/KsPlotTools.cpp create mode 100644 kernel-shark-qt/src/KsPlotTools.hpp diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 9c74bc0..ac2847a 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -16,7 +16,8 @@ set_target_properties(kshark PROPERTIES SUFFIX ".so.${KS_VERSION_STRING}") if (OPENGL_FOUND AND GLUT_FOUND) message(STATUS "libkshark-plot") - add_library(kshark-plot SHARED libkshark-plot.c) + add_library(kshark-plot SHARED libkshark-plot.c + KsPlotTools.cpp) target_link_libraries(kshark-plot kshark ${OPENGL_LIBRARIES} diff --git a/kernel-shark-qt/src/KsPlotTools.cpp b/kernel-shark-qt/src/KsPlotTools.cpp new file mode 100644 index 0000000..0cd7a7d --- /dev/null +++ b/kernel-shark-qt/src/KsPlotTools.cpp @@ -0,0 +1,1123 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsPlotTools.cpp + * @brief KernelShark Plot tools. + */ + +// C +#include + +// C++ +#include +#include + +// OpenGL +#include +#include + +// KernelShark +#include "KsPlotTools.hpp" + +namespace KsPlot +{ + +float Color::_frequency = .75; + +/** + * @brief Create a default color (black). + */ +Color::Color() +{ + _col_c.red = _col_c.green = _col_c.blue = 0; +} + +/** + * @brief Constructs a RGB color object. + * + * @param r: The red component of the color. + * @param g: The green component of the color. + * @param b: The blue component of the color + */ +Color::Color(uint8_t r, uint8_t g, uint8_t b) +{ + set(r, g, b); +} + +/** + * @brief Constructs a RGB color object. + * + * @param rgb: RGB value. + */ +Color::Color(int rgb) +{ + set(rgb); +} + +/** + * @brief Sets the color. + * + * @param r: The red component of the color. + * @param g: The green component of the color. + * @param b: The blue component of the color + */ +void Color::set(uint8_t r, uint8_t g, uint8_t b) +{ + _col_c.red = r; + _col_c.green = g; + _col_c.blue = b; +} + +/** + * @brief Sets the color. + * + * @param rgb: RGB value. + */ +void Color::set(int rgb) +{ + int r = rgb & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = (rgb >> 16) & 0xFF; + + set(r, g, b); +} + +/** + * @brief The color is selected from the Rainbow palette. + * + * @param n: index of the color inside the Rainbow palette. + */ +void Color::setRainbowColor(int n) +{ + int r = sin(_frequency * n + 0) * 127 + 128; + int g = sin(_frequency * n + 2) * 127 + 128; + int b = sin(_frequency * n + 4) * 127 + 128; + + set(r, g, b); +} + +/** + * @brief Create a Hash table of Rainbow colors. The sorted Pid values are + * mapped to the palette of Rainbow colors. + * + * @returns ColorTable instance. + */ +ColorTable getColorTable() +{ + struct kshark_context *kshark_ctx(nullptr); + ColorTable colors; + int nTasks, pid, *pids; + + if (!kshark_instance(&kshark_ctx)) + return colors; + + nTasks = kshark_get_task_pids(kshark_ctx, &pids); + if (!nTasks) + return colors; + + std::vector temp_pids(pids, pids + nTasks); + std::sort(temp_pids.begin(), temp_pids.end()); + + /* The "Idle" process (pid = 0) will be plotted in black. */ + colors[0] = {}; + + for (int i = 1; i < nTasks; ++i) { + pid = temp_pids[i]; + colors[pid].setRainbowColor(i - 1); + } + + return colors; +} + +/** + * @brief Search the Hash table of Rainbow colors for a particular key (pid). + * + * @param colors: Input location for the ColorTable instance. + * @param pid: the Process Id to search for. + * + * @returns The Rainbow color of the key "pid". If "pid" does not exist, the + * returned color is Black. + */ +Color getPidColor(ColorTable *colors, int pid) +{ + auto item = colors->find(pid); + + if (item != colors->end()) + return item->second; + + return {}; +} + +/** + * @brief Create a default Shape. + */ +Shape::Shape() +: _nPoints(0), + _points(nullptr) +{} + +/** + * @brief Create a Shape defined by "n" points. All points are initialized + * at (0, 0). + */ +Shape::Shape(int n) +: _nPoints(n), + _points(new(std::nothrow) ksplot_point[n]()) +{ + if (!_points) { + _size = 0; + fprintf(stderr, + "Failed to allocate memory for ksplot_points.\n"); + } +} + +/** Copy constructor. */ +Shape::Shape(const Shape &s) +: _nPoints(0), + _points(nullptr) +{ + *this = s; +} + +/** Move constructor. */ +Shape::Shape(Shape &&s) +: _nPoints(s._nPoints), + _points(s._points) +{ + s._nPoints = 0; + s._points = nullptr; +} + +/** +* @brief Destroy the Shape object. +*/ +Shape::~Shape() { + delete[] _points; +} + +/** Assignment operator. */ +void Shape::operator=(const Shape &s) +{ + PlotObject::operator=(s); + + if (s._nPoints != _nPoints) { + delete[] _points; + _points = new(std::nothrow) ksplot_point[s._nPoints]; + } + + if (_points) { + _nPoints = s._nPoints; + memcpy(_points, s._points, + sizeof(_points) * _nPoints); + } else { + _nPoints = 0; + fprintf(stderr, + "Failed to allocate memory for ksplot_points.\n"); + } +} + +/** + * @brief Set the point of the polygon indexed by "i". + * + * @param i: the index of the point to be set. + * @param x: X coordinate of the point in pixels. + * @param y: Y coordinate of the point in pixels. + */ +void Shape::setPoint(size_t i, int x, int y) +{ + if (i < _nPoints) { + _points[i].x = x; + _points[i].y = y; + } +} + +/** + * @brief Set the point of the polygon indexed by "i". + * + * @param i: the index of the point to be set. + * @param p: A ksplot_point object used to provide coordinate values. + */ +void Shape::setPoint(size_t i, const ksplot_point &p) +{ + setPoint(i, p.x, p.y); +} + +/** + * @brief Set the point of the polygon indexed by "i". + * + * @param i: the index of the point to be set. + * @param p: A Point object used to provide coordinate values. + */ +void Shape::setPoint(size_t i, const Point &p) +{ + setPoint(i, p.x(), p.y()); +} + +/** + * @brief Get the point "i". If the point does not exist, the function returns + * nullptr. + */ +const ksplot_point *Shape::getPoint(size_t i) const +{ + if (i < _nPoints) + return &_points[i]; + + return nullptr; +} + +/** + * @brief Set the horizontal coordinate of the point "i". + * + * @param i: the index of the point to be set. + * @param x: X coordinate of the point in pixels. + */ +void Shape::setPointX(size_t i, int x) { + if (i < _nPoints) + _points[i].x = x; +} + +/** + * @brief Set the vertical coordinate of the point "i". + * + * @param i: the index of the point to be set. + * @param y: Y coordinate of the point in pixels. + */ +void Shape::setPointY(size_t i, int y) { + if (i < _nPoints) + _points[i].y = y; +} + +/** + * @brief Get the horizontal coordinate of the point "i". If the point does + * not exist, the function returns 0. + */ +int Shape::getPointX(size_t i) const { + if (i < _nPoints) + return _points[i].x; + + return 0; +} + +/** + * @brief Get the vertical coordinate of the point "i". If the point does + * not exist, the function returns 0. + */ +int Shape::getPointY(size_t i) const { + if (i < _nPoints) + return _points[i].y; + + return 0; +} + +/** @brief Create a default Point. The point is initialized at (0, 0). */ +Point::Point() +: Shape(1) +{} + +/** + * @brief Create a point. + * + * @param x: X coordinate of the point in pixels. + * @param y: Y coordinate of the point in pixels. + */ +Point::Point(int x, int y) +: Shape(1) +{ + setPoint(0, x, y); +} + +void Point::_draw(const Color &col, float size) const +{ + if (_nPoints == 1) + ksplot_draw_point(_points, col.color_c_ptr(), size); +} + +/** + * @brief Draw a line between point "a" and point "b". + * + * @param a: The first finishing point of the line. + * @param b: The second finishing point of the line. + * @param col: The color of the line. + * @param size: The size of the line. + */ +void drawLine(const Point &a, const Point &b, + const Color &col, float size) +{ + ksplot_draw_line(a.point_c_ptr(), + b.point_c_ptr(), + col.color_c_ptr(), + size); +} + +/** @brief Create a default line. The two points are initialized at (0, 0). */ +Line::Line() +: Shape(2) +{} + +/** + * @brief Create a line between the point "a" and point "b". + * + * @param a: first finishing point of the line. + * @param b: second finishing point of the line. + */ +Line::Line(const Point &a, const Point &b) +: Shape(2) +{ + setPoint(0, a.x(), a.y()); + setPoint(1, b.x(), b.y()); +} + +void Line::_draw(const Color &col, float size) const +{ + if (_nPoints == 2) + ksplot_draw_line(&_points[0], &_points[1], + col.color_c_ptr(), size); +} + +/** + * @brief Create a default polygon. All points are initialized at (0, 0). + * + * @param n: The number of edges of the polygon. + */ +Polygon::Polygon(size_t n) +: Shape(n), + _fill(true) +{} + +void Polygon::_draw(const Color &col, float size) const +{ + if (_fill) + ksplot_draw_polygon(_points, _nPoints, + col.color_c_ptr(), + size); + else + ksplot_draw_polygon_contour(_points, _nPoints, + col.color_c_ptr(), + size); +} + +/** + * @brief Create a default Mark. + */ +Mark::Mark() +{ + _visible = false; + _cpu._color = Color(225, 255, 100); + _cpu._size = 5.5f; + _task._color = Color(0, 255, 0); + _task._size = 5.5f; +} + +void Mark::_draw(const Color &col, float size) const +{ + drawLine(_a, _b, col, size); + _cpu.draw(); + _task.draw(); +} + +/** + * @brief Set the device pixel ratio. + * + * @param dpr: device pixel ratio value. + */ +void Mark::setDPR(int dpr) +{ + _size = 1.5 * dpr; + _task._size = _cpu._size = 1.5 + 4.0 * dpr; +} + +/** + * @brief Set the X coordinate (horizontal) of the Mark. + * + * @param x: X coordinate of the Makr in pixels. + */ +void Mark::setX(int x) +{ + _a.setX(x); + _b.setX(x); + _cpu.setX(x); + _task.setX(x); +} + +/** + * @brief Set the Y coordinates (vertical) of the Mark's finishing points. + * + * @param yA: Y coordinate of the first finishing point of the Mark's line. + * @param yB: Y coordinate of the second finishing point of the Mark's line. + */ +void Mark::setY(int yA, int yB) +{ + _a.setY(yA); + _b.setY(yB); +} + +/** + * @brief Set the Y coordinates (vertical) of the Mark's CPU points. + * + * @param yCPU: Y coordinate of the Mark's CPU point. + */ +void Mark::setCPUY(int yCPU) +{ + _cpu.setY(yCPU); +} + +/** + * @brief Set the visiblity of the Mark's CPU points. + * + * @param v: If True, the CPU point will be visible. + */ +void Mark::setCPUVisible(bool v) +{ + _cpu._visible = v; +} + +/** + * @brief Set the Y coordinates (vertical) of the Mark's Task points. + * + * @param yTask: Y coordinate of the Mark's Task point. + */ +void Mark::setTaskY(int yTask) +{ + _task.setY(yTask); +} + +/** + * @brief Set the visiblity of the Mark's Task points. + * + * @param v: If True, the Task point will be visible. + */ +void Mark::setTaskVisible(bool v) +{ + _task._visible = v; +} + +/** + * @brief Create a default Bin. + */ +Bin::Bin() +: _pidFront(KS_EMPTY_BIN), + _pidBack(KS_EMPTY_BIN) +{} + +void Bin::_draw(const Color &col, float size) const +{ + drawLine(_base, _val, col, size); +} + +/** + * @brief Draw only the "val" Point og the Bin. + * + * @param size: The size of the point. + */ +void Bin::drawVal(float size) +{ + _val._size = size; + _val.draw(); +} + +/** + * @brief Create a default (empty) Graph. + */ +Graph::Graph() +: _histoPtr(nullptr), + _bins(nullptr), + _size(0), + _hMargin(30), + _collectionPtr(nullptr), + _pidColors(nullptr) +{} + +/** + * @brief Create a Graph to represent the state of the Vis. model. + * + * @param histo: Input location for the model descriptor. + * @param ct: Input location for the Hash table of Task's colors. + */ +Graph::Graph(kshark_trace_histo *histo, KsPlot::ColorTable *ct) +: _histoPtr(histo), + _bins(new(std::nothrow) Bin[histo->n_bins]), + _size(histo->n_bins), + _hMargin(30), + _collectionPtr(nullptr), + _pidColors(ct) +{ + if (!_bins) { + _size = 0; + fprintf(stderr, "Failed to allocate memory graph's bins.\n"); + } + + _initBins(); +} + +/** + * @brief Destroy the Graph object. + */ +Graph::~Graph() +{ + delete[] _bins; +} + +void Graph::_initBins() +{ + for (int i = 0; i < _size; ++i) { + _bins[i]._base.setX(i + _hMargin); + _bins[i]._base.setY(0); + _bins[i]._val.setX(_bins[i]._base.x()); + _bins[i]._val.setY(_bins[i]._base.y()); + } +} + +/** + * Get the number of bins. + */ +int Graph::size() +{ + return _size; +} + +/** + * @brief Reinitialize the Graph according to the Vis. model. + * + * @param histo: Input location for the model descriptor. + */ +void Graph::setModelPtr(kshark_trace_histo *histo) +{ + if (_size != histo->n_bins) { + delete[] _bins; + _size = histo->n_bins; + _bins = new(std::nothrow) Bin[_size]; + if (!_bins) { + _size = 0; + fprintf(stderr, + "Failed to allocate memory graph's bins.\n"); + } + } + + _histoPtr = histo; + _initBins(); +} + +/** + * @brief This function will set the Y (vertical) coordinate of the Graph's + * base. It is safe to use this function even if the Graph contains + * data. + * + * @param b: Y coordinate of the Graph's base in pixels. + */ +void Graph::setBase(int b) +{ + int mod; + + if (!_size) + return; + + if (b == _bins[0]._base.y()) // Nothing to do. + return; + + for (int i = 0; i < _size; ++i) { + mod = _bins[i].mod(); + _bins[i]._base.setY(b); + _bins[i]._val.setY(b + mod); + } +} + +/** + * @brief Set the vertical size (height) of the Graph. + * + * @param h: the height of the Graph in pixels. + */ +void Graph::setHeight(int h) +{ + _height = h; +} + +/** + * @brief Set the size of the white space added on both sides of the Graph. + * + * @param hMargin: the size of the white space in pixels. + */ +void Graph::setHMargin(int hMargin) +{ + if (!_size) + return; + + if (hMargin == _bins[0]._base.x()) // Nothing to do. + return; + + for (int i = 0; i < _size; ++i) { + _bins[i]._base.setX(i + hMargin); + _bins[i]._val.setX(_bins[i]._base.x()); + } + + _hMargin = hMargin; +} + +/** + * @brief Set the value of a given bin. + * + * @param bin: Bin Id. + * @param val: Bin height in pixels. + */ +void Graph::setBinValue(int bin, int val) +{ + _bins[bin].setVal(val); +} + +/** + * @brief Set the Process Id (Front and Back) a given bin. + * + * @param bin: Bin Id. + * @param pidF: The Process Id detected at the from (first in time) edge of + * the bin. + * @param pidB: The Process Id detected at the back (last in time) edge of + * the bin. + */ +void Graph::setBinPid(int bin, int pidF, int pidB) +{ + _bins[bin]._pidFront = pidF; + _bins[bin]._pidBack = pidB; +} + +/** + * @brief Set the color of a given bin. + * + * @param bin: Bin Id. + * @param col: the color of the bin. + */ +void Graph::setBinColor(int bin, const Color &col) +{ + _bins[bin]._color = col; +} + +/** + * @brief Set the visiblity mask of a given bin. + * + * @param bin: Bin Id. + * @param m: the visiblity mask. + */ +void Graph::setBinVisMask(int bin, uint8_t m) +{ + _bins[bin]._visMask = m; +} + +/** + * @brief Set all fields of a given bin. + * + * @param bin: Bin Id. + * @param pidF: The Process Id detected at the from (first in time) edge of + * the bin. + * @param pidB: The Process Id detected at the back (last in time) edge of + * the bin. + * @param col: the color of the bin. + * @param m: the visiblity mask. + */ +void Graph::setBin(int bin, int pidF, int pidB, const Color &col, uint8_t m) +{ + setBinPid(bin, pidF, pidB); + setBinValue(bin, _height * .7); + setBinColor(bin, col); + setBinVisMask(bin, m); +} + +/** + * @brief Process a CPU Graph. + * + * @param cpu: The CPU core. + */ +void Graph::fillCPUGraph(int cpu) +{ + struct kshark_entry *eFront; + int pidFront(0), pidBack(0); + int pidBackNoFilter; + uint8_t visMask; + ssize_t index; + int bin; + + auto lamGetPid = [&] (int bin) + { + eFront = nullptr; + + pidFront = ksmodel_get_pid_front(_histoPtr, bin, + cpu, + true, + _collectionPtr, + &index); + + if (index >= 0) + eFront = _histoPtr->data[index]; + + pidBack = ksmodel_get_pid_back(_histoPtr, bin, + cpu, + true, + _collectionPtr, + nullptr); + + pidBackNoFilter = + ksmodel_get_pid_back(_histoPtr, bin, + cpu, + false, + _collectionPtr, + nullptr); + + if (pidBack != pidBackNoFilter) + pidBack = KS_FILTERED_BIN; + + visMask = 0x0; + if (ksmodel_cpu_visible_event_exist(_histoPtr, bin, + cpu, + _collectionPtr, + &index)) + + visMask = _histoPtr->data[index]->visible; + else if (eFront) + visMask = eFront->visible; + }; + + auto lamSetBin = [&] (int bin) + { + if (pidFront != KS_EMPTY_BIN || pidBack != KS_EMPTY_BIN) { + /* This is a regular process. */ + setBin(bin, pidFront, pidBack, + getPidColor(_pidColors, pidFront), visMask); + } else { + /* The bin contens no data from this CPU. */ + setBinPid(bin, KS_EMPTY_BIN, KS_EMPTY_BIN); + } + }; + + /* + * Check the content of the very first bin and see if the CPU is + * active. + */ + bin = 0; + lamGetPid(bin); + if (pidFront >= 0) { + /* + * The CPU is active and this is a regular process. + * Set this bin. + */ + lamSetBin(bin); + } else { + /* + * No data from this CPU in the very first bin. Use the Lower + * Overflow Bin to retrieve the Process Id (if any). First + * get the Pid back, ignoring the filters. + */ + pidBackNoFilter = ksmodel_get_pid_back(_histoPtr, + LOWER_OVERFLOW_BIN, + cpu, + false, + _collectionPtr, + nullptr); + + /* Now get the Pid back, applying filters. */ + pidBack = ksmodel_get_pid_back(_histoPtr, + LOWER_OVERFLOW_BIN, + cpu, + true, + _collectionPtr, + nullptr); + + if (pidBack != pidBackNoFilter) { + /* The Lower Overflow Bin ends with filtered data. */ + setBinPid(bin, KS_FILTERED_BIN, KS_FILTERED_BIN); + } else { + /* + * The Lower Overflow Bin ends with data which has + * to be plotted. + */ + setBinPid(bin, pidBack, pidBack); + } + } + + /* + * The first bin is already processed. The loop starts from the second + * bin. + */ + for (bin = 1; bin < _histoPtr->n_bins; ++bin) { + /* + * Check the content of this bin and see if the CPU is active. + * If yes, retrieve the Process Id. + */ + lamGetPid(bin); + lamSetBin(bin); + } +} + +/** + * @brief Process a Task Graph. + * + * @param pid: The Process Id of the Task. + */ +void Graph::fillTaskGraph(int pid) +{ + int cpu, pidFront(0), pidBack(0), lastCpu(-1), bin(0); + uint8_t visMask; + ssize_t index; + + auto lamSetBin = [&] (int bin) + { + if (cpu >= 0) { + KsPlot::Color col; + col.setRainbowColor(cpu); + + /* Data from the Task has been found in this bin. */ + if (pid == pidFront && pid == pidBack) { + /* No data from other tasks in this bin. */ + setBin(bin, pid, pid, col, visMask); + } else if (pid != pidFront && pid != pidBack) { + /* + * There is some data from other tasks at both + * front and back sides of this bin. But we + * still want to see this bin drawn. + */ + setBin(bin, pid, KS_FILTERED_BIN, col, + visMask); + } else { + if (pidFront != pid) { + /* + * There is some data from another + * task at the front side of this bin. + */ + pidFront = KS_FILTERED_BIN; + } + + if (pidBack != pid) { + /* + * There is some data from another + * task at the back side of this bin. + */ + pidBack = KS_FILTERED_BIN; + } + + setBin(bin, pidFront, pidBack, col, visMask); + } + + lastCpu = cpu; + } else { + /* + * No data from the Task in this bin. Check the CPU, + * previously used by the task. + */ + int cpuPid = ksmodel_get_pid_back(_histoPtr, + bin, + lastCpu, + false, + _collectionPtr, + nullptr); + + if (cpuPid != KS_EMPTY_BIN) { + /* + * If the CPU is active and works on another + * task break the graph here. + */ + setBinPid(bin, KS_FILTERED_BIN, KS_EMPTY_BIN); + } else { + /* + * No data from this CPU in the bin. + * Continue the graph. + */ + setBinPid(bin, KS_EMPTY_BIN, KS_EMPTY_BIN); + } + } + }; + + auto lamGetPidCPU = [&] (int bin) + { + /* Get the CPU used by this task. */ + cpu = ksmodel_get_cpu_front(_histoPtr, bin, + pid, + false, + _collectionPtr, + nullptr); + + if (cpu < 0) { + pidFront = pidBack = cpu; + } else { + /* + * Get the process Id at the begining and at the end + * of the bin. + */ + pidFront = ksmodel_get_pid_front(_histoPtr, + bin, + cpu, + false, + _collectionPtr, + nullptr); + + pidBack = ksmodel_get_pid_back(_histoPtr, + bin, + cpu, + false, + _collectionPtr, + nullptr); + + visMask = 0x0; + if (ksmodel_task_visible_event_exist(_histoPtr, + bin, + pid, + _collectionPtr, + &index)) { + visMask = _histoPtr->data[index]->visible; + } + } + }; + + /* + * Check the content of the very first bin and see if the Task is + * active. + */ + lamGetPidCPU(bin); + + if (cpu >= 0) { + /* The Task is active. Set this bin. */ + lamSetBin(bin); + } else { + /* + * No data from this Task in the very first bin. Use the Lower + * Overflow Bin to retrieve the CPU used by the task (if any). + */ + cpu = ksmodel_get_cpu_back(_histoPtr, LOWER_OVERFLOW_BIN, pid, + false, _collectionPtr, nullptr); + if (cpu >= 0) { + /* + * The Lower Overflow Bin contains data from this Task. + * Now look again in the Lower Overflow Bin and find + * the Pid of the last active task on the same CPU. + */ + int pidCpu = ksmodel_get_pid_back(_histoPtr, + LOWER_OVERFLOW_BIN, + cpu, + false, + _collectionPtr, + nullptr); + if (pidCpu == pid) { + /* + * The Task is the last one running on this + * CPU. Set the Pid of the bin. In this case + * the very first bin is empty but we derive + * the Process Id from the Lower Overflow Bin. + */ + setBinPid(bin, pid, pid); + lastCpu = cpu; + } + } + } + + /* + * The first bin is already processed. The loop starts from the second + * bin. + */ + for (bin = 1; bin < _histoPtr->n_bins; ++bin) { + lamGetPidCPU(bin); + + /* Set the bin accordingly. */ + lamSetBin(bin); + } +} + +/** + * @brief Draw the Graph + * + * @param size: The size of the lines of the individual Bins. + */ +void Graph::draw(float size) +{ + int lastPid(0), b(0), boxH(_height * .3); + Rectangle taskBox; + + /* + * Start by drawing a line between the base points of the first and + * the last bin. + */ + drawLine(_bins[0]._base, _bins[_size - 1]._base, {}, size); + + /* Draw as vartical lines all bins containing data. */ + for (int i = 0; i < _size; ++i) + if (_bins[i]._pidFront >= 0 || _bins[i]._pidBack >= 0) + if (_bins[i]._visMask & KS_EVENT_VIEW_FILTER_MASK) + _bins[i].draw(); + + /* + * Draw colored boxes for processes. First find the first bin, which + * contains data and determine its PID. + */ + for (; b < _size; ++b) { + if (_bins[b]._pidBack > 0) { + lastPid = _bins[b]._pidFront; + /* + * Initialize a box starting from this bin. + * The color of the taskBox corresponds to the Pid + * of the process. + */ + taskBox._color = getPidColor(_pidColors, lastPid); + taskBox.setPoint(0, _bins[b]._base.x(), + _bins[b]._base.y() - boxH); + taskBox.setPoint(1, _bins[b]._base.x(), + _bins[b]._base.y()); + break; + } + } + + for (; b < _size; ++b) { + if (_bins[b]._pidFront == KS_EMPTY_BIN && + _bins[b]._pidBack == KS_EMPTY_BIN) { + /* + * This bin is empty. If a colored taskBox is already + * initialized, it will be extended. + */ + continue; + } + + if (_bins[b]._pidFront != _bins[b]._pidBack || + _bins[b]._pidFront != lastPid || + _bins[b]._pidBack != lastPid) { + /* A new process starts here. */ + if (lastPid > 0 && b > 0) { + /* + * There is another process running up to this + * point. Close its colored box here and draw. + */ + taskBox.setPoint(3, _bins[b]._base.x() - 1, + _bins[b]._base.y() - boxH); + taskBox.setPoint(2, _bins[b]._base.x() - 1, + _bins[b]._base.y()); + taskBox.draw(); + } + + if (_bins[b]._pidBack > 0) { + /* + * This is a regular process. Initialize + * colored box starting from this bin. + */ + taskBox._color = getPidColor(_pidColors, + _bins[b]._pidBack); + + taskBox.setPoint(0, _bins[b]._base.x() - 1, + _bins[b]._base.y() - boxH); + taskBox.setPoint(1, _bins[b]._base.x() - 1, + _bins[b]._base.y()); + } + + lastPid = _bins[b]._pidBack; + } + } + + if (lastPid > 0) { + /* + * This is the end of the Graph and we have a process running. + * Close its colored box and draw. + */ + taskBox.setPoint(3, _bins[_size - 1]._base.x(), + _bins[_size - 1]._base.y() - boxH); + taskBox.setPoint(2, _bins[_size - 1]._base.x(), + _bins[_size - 1]._base.y()); + taskBox.draw(); + } +} + +}; // KsPlot diff --git a/kernel-shark-qt/src/KsPlotTools.hpp b/kernel-shark-qt/src/KsPlotTools.hpp new file mode 100644 index 0000000..d5779d6 --- /dev/null +++ b/kernel-shark-qt/src/KsPlotTools.hpp @@ -0,0 +1,485 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + + /** + * @file KsPlotTools.hpp + * @brief KernelShark Plot tools. + */ + +#ifndef _KS_PLOT_TOOLS_H +#define _KS_PLOT_TOOLS_H + +// C++ +#include +#include + +// KernelShark +#include "libkshark.h" +#include "libkshark-plot.h" +#include "libkshark-model.h" + +namespace KsPlot { + +/** This class represents a RGB color. */ +class Color { +public: + Color(); + + Color(uint8_t r, uint8_t g, uint8_t b); + + Color(int rgb); + + /** @brief Get the Red coordinate of the color. */ + uint8_t r() const {return _col_c.red;} + + /** @brief Get the Green coordinate of the color. */ + uint8_t g() const {return _col_c.green;} + + /** @brief Get the Blue coordinate of the color. */ + uint8_t b() const {return _col_c.blue;} + + void set(uint8_t r, uint8_t g, uint8_t b); + + void set(int rgb); + + void setRainbowColor(int n); + + /** + * @brief Get the C struct defining the RGB color. + */ + const ksplot_color *color_c_ptr() const {return &_col_c;} + + /** + * @brief Set the frequency value used to generate the Rainbow + * palette. + */ + static void setRainbowFrequency(float f) {_frequency = f;} + + /** + * @brief Get the frequency value used to generate the Rainbow + * palette. + */ + static float getRainbowFrequency() {return _frequency;} + +private: + ksplot_color _col_c; + + /** The frequency value used to generate the Rainbow palette. */ + static float _frequency; +}; + +/** Hash table of colors. */ +typedef std::unordered_map ColorTable; + +ColorTable getColorTable(); + +Color getPidColor(ColorTable *colors, int pid); + +/** Represents an abstract graphical element. */ +class PlotObject { +public: + /** + * @brief Create a default object. + */ + PlotObject() : _visible(true), _size(2.) {} + + /** + * @brief Destroy the object. Keep this destructor virtual. + */ + virtual ~PlotObject() {} + + /** Generic function used to draw different objects. */ + void draw() const { + if (_visible) + _draw(_color, _size); + } + + /** Is this object visible. */ + bool _visible; + + /** The color of the object. */ + Color _color; + + /** The size of the object. */ + float _size; + +private: + virtual void _draw(const Color &col, float s) const = 0; +}; + +/** List of graphical element. */ +typedef std::forward_list PlotObjList; + +class Point; + +/** Represents an abstract shape. */ +class Shape : public PlotObject { +public: + Shape(); + + Shape(int n); + + Shape(const Shape &); + + Shape(Shape &&); + + /* Keep this destructor virtual. */ + virtual ~Shape(); + + void operator=(const Shape &s); + + void setPoint(size_t i, int x, int y); + + void setPoint(size_t i, const ksplot_point &p); + + void setPoint(size_t i, const Point &p); + + const ksplot_point *getPoint(size_t i) const; + + void setPointX(size_t i, int x); + + void setPointY(size_t i, int y); + + int getPointX(size_t i) const; + + int getPointY(size_t i) const; + + /** + * @brief Get the number of point used to define the polygon. + */ + size_t pointCount() const {return _nPoints;} + +protected: + /** The number of point used to define the polygon. */ + size_t _nPoints; + + /** The array of point used to define the polygon. */ + ksplot_point *_points; +}; + +/** This class represents a 2D poin. */ +class Point : public Shape { +public: + Point(); + + Point(int x, int y); + + /** + * @brief Destroy the Point object. Keep this destructor virtual. + */ + virtual ~Point() {} + + /** @brief Get the horizontal coordinate of the point. */ + int x() const {return getPointX(0);} + + /** @brief Get the vertical coordinate of the point. */ + int y() const {return getPointY(0);} + + /** @brief Set the horizontal coordinate of the point. */ + void setX(int x) {setPointX(0, x);} + + /** @brief Set the vertical coordinate of the point. */ + void setY(int y) {setPointY(0, y);} + + /** + * @brief Set the coordinats of the point. + * + * @param x: horizontal coordinate of the point in pixels. + * @param y: vertical coordinate of the point in pixels. + */ + void set(int x, int y) {setPoint(0, x, y);} + + /** + * @brief Get the C struct defining the point. + */ + const ksplot_point *point_c_ptr() const {return getPoint(0);} + +private: + void _draw(const Color &col, float size = 1.) const override; +}; + +void drawLine(const Point &a, const Point &b, + const Color &col, float s); + +/** This class represents a straight line. */ +class Line : public Shape { +public: + Line(); + + Line(const Point &a, const Point &b); + + /** + * @brief Destroy the Line object. Keep this destructor virtual. + */ + virtual ~Line() {} + + /** + * @brief Set the coordinats of the first finishing point of the + * line. + * + * @param x: horizontal coordinate of the point in pixels. + * @param y: vertical coordinate of the point in pixels. + */ + void setA(int x, int y) { setPoint(0, x, y);} + + /** @brief Get the first finishing point of the line. */ + const ksplot_point *getA() const {return getPoint(0);} + + /** + * @brief Set the coordinats of the second finishing point of the + * line. + * + * @param x: horizontal coordinate of the point in pixels. + * @param y: vertical coordinate of the point in pixels. + */ + void setB(int x, int y) {setPoint(1, x, y);} + + /** @brief Get the second finishing point of the line. */ + const ksplot_point *getB() const {return getPoint(1);} + +private: + void _draw(const Color &col, float size = 1.) const override; +}; + +/** This class represents a polygon. */ +class Polygon : public Shape { +public: + Polygon(size_t n); + + /** + * @brief Destroy the polygon object. Keep this destructor virtual. + */ + virtual ~Polygon() {} + + /** + * @brief Specify the way the polygon will be drawn. + * + * @param f: If True, the area of the polygon will be colored. + * Otherwise only the contour of the polygon will be plotted. + */ + void setFill(bool f) {_fill = f;} + +private: + Polygon() = delete; + + void _draw(const Color &, float size = 1.) const override; + + /** + * If True, the area of the polygon will be colored. Otherwise only + * the contour of the polygon will be plotted. + */ + bool _fill; +}; + +/** This class represents a triangle. */ +class Triangle : public Polygon { +public: + /** + * Create a default triangle. All points are initialized at (0, 0). + */ + Triangle() : Polygon(3) {} + + /** Destroy the Triangle object. Keep this destructor virtual. */ + virtual ~Triangle() {} +}; + +/** This class represents a rectangle. */ +class Rectangle : public Polygon { +public: + /** + * Create a default Rectangle. All points are initialized at (0, 0). + */ + Rectangle() : Polygon(4) {} + + /** Destroy the Rectangle object. Keep this destructor virtual. */ + virtual ~Rectangle() {} +}; + +/** + * This class represents the graphical element of the KernelShark GUI marker. + */ +class Mark : public PlotObject { +public: + Mark(); + + /** + * @brief Destroy the Mark object. Keep this destructor virtual. + */ + virtual ~Mark() {} + + void setDPR(int dpr); + + void setX(int x); + + void setY(int yA, int yB); + + void setCPUY(int yCPU); + + void setCPUVisible(bool v); + + void setTaskY(int yTask); + + void setTaskVisible(bool v); + +private: + void _draw(const Color &col, float size = 1.) const override; + + /** First finishing point of the Mark's line. */ + Point _a; + + /** Second finishing point of the Mark's line. */ + Point _b; + + /** A point indicating the position of the Mark in a CPU graph. */ + Point _cpu; + + /** A point indicating the position of the Mark in a Task graph. */ + Point _task; +}; + +/** This class represents a KernelShark graph's bin. */ +class Bin : public PlotObject { +public: + Bin(); + + /** + * @brief Destroy the Bin object. Keep this destructor virtual. + */ + virtual ~Bin() {} + + void drawVal(float size = 2.); + + /** Get the height (module) of the line, representing the Bin. */ + int mod() {return _val.y() - _base.y();} + + /** @brief Set the vertical coordinate of the "val" Point. */ + void setVal(int v) {_val.setY(_base.y() - v); } + + /** + * The Process Id detected at the front (first in time) edge of + * the bin. + */ + int _pidFront; + + /** + * The Process Id detected at the back (last in time) edge of + * the bin. + */ + int _pidBack; + + /** Lower finishing point of the line, representing the Bin. */ + Point _base; + + /** Upper finishing point of the line, representing the Bin. */ + Point _val; + + /** A bit mask controlling the visibility of the Bin. */ + uint8_t _visMask; + +private: + void _draw(const Color &col, float size = 1.) const override; +}; + +/** This class represents a KernelShark graph. */ +class Graph { +public: + Graph(); + + /* + * Disable copying. We can enable the Copy Constructor in the future, + * but only if we really need it for some reason. + */ + Graph(const Graph &) = delete; + + /* Disable moving. Same as copying.*/ + Graph(Graph &&) = delete; + + Graph(kshark_trace_histo *histo, KsPlot::ColorTable *ct); + + /* Keep this destructor virtual. */ + virtual ~Graph(); + + int size(); + + void setModelPtr(kshark_trace_histo *histo); + + /** + * @brief Provide the Graph with a Data Collection. The collection + * will be used to optimise the processing of the content of + * the bins. + * + * @param col: Input location for the data collection descriptor. + */ + void setDataCollectionPtr(kshark_entry_collection *col) { + _collectionPtr = col; + } + + /** @brief Set the Hash table of Task's colors. */ + void setColorTablePtr(KsPlot::ColorTable *ct) {_pidColors = ct;} + + void fillCPUGraph(int cpu); + + void fillTaskGraph(int pid); + + void draw(float s = 1); + + void setBase(int b); + + /** @brief Get the vertical coordinate of the Graph's base. */ + int getBase() const {return _bins[0]._base.y();} + + void setHeight(int h); + + /** @brief Get the vertical size (height) of the Graph. */ + int getHeight() const {return _height;} + + void setBinValue(int bin, int val); + + void setBinPid(int bin, int pidF, int pidB); + + void setBinColor(int bin, const Color &col); + + void setBinVisMask(int bin, uint8_t m); + + void setBin(int bin, int pidF, int pidB, + const Color &col, uint8_t m); + + /** @brief Get a particular bin. */ + const Bin &getBin(int bin) const {return _bins[bin];} + + void setHMargin(int hMargin); + +private: + /** Pointer to the model descriptor object. */ + kshark_trace_histo *_histoPtr; + + /** An array of Bins. */ + Bin *_bins; + + /** The number of Bins. */ + int _size; + + /** + * The size (in pixels) of the white space added on both sides of + * the Graph. + */ + int _hMargin; + + /** The vertical size (height) of the Graph. */ + int _height; + + /** Pointer to the data collection object. */ + kshark_entry_collection *_collectionPtr; + + /** Hash table of Task's colors. */ + ColorTable *_pidColors; + + void _initBins(); +}; + +}; // KsPlot + +#endif /* _KS_PLOT_TOOLS_H */