| // SPDX-License-Identifier: LGPL-2.1 |
| |
| /* |
| * Copyright (C) 2019 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@gmail.com> |
| */ |
| |
| /** |
| * @file KsPlugins.cpp |
| * @brief KernelShark C++ plugin declarations. |
| */ |
| |
| // C++ |
| #include<iostream> |
| #include <limits> |
| |
| // KernelShark |
| #include "KsPlugins.hpp" |
| |
| using namespace KsPlot; |
| |
| /** |
| * A pair of Bin Id and a trace event data field in this bin, that needs to be |
| * plotted. |
| */ |
| typedef std::forward_list<std::pair<int, kshark_data_field_int64 *>> PlotPointList; |
| |
| //! @cond Doxygen_Suppress |
| |
| typedef std::function<void(int, kshark_data_container *, ssize_t, |
| PlotPointList *)> pushFunc; |
| |
| typedef std::function<void(kshark_data_container *, ssize_t, |
| PlotPointList *)> resolveFunc; |
| |
| //! @endcond |
| |
| static void pointPlot(KsCppArgV *argvCpp, IsApplicableFunc isApplicable, |
| pluginShapeFunc makeShape, Color col, float size) |
| { |
| int nBins = argvCpp->_graph->size(); |
| |
| for (int bin = 0; bin < nBins; ++bin) |
| if (isApplicable(nullptr, bin)) |
| argvCpp->_shapes->push_front(makeShape({argvCpp->_graph}, |
| {bin}, {}, |
| col, size)); |
| } |
| |
| static std::pair<ssize_t, ssize_t> |
| getRange(kshark_trace_histo *histo, kshark_data_container *data) |
| { |
| ssize_t firstEntry, lastEntry; |
| std::pair<ssize_t, ssize_t> err(-1, -2); |
| |
| firstEntry = kshark_find_entry_field_by_time(histo->min, |
| data->data, |
| 0, |
| data->size - 1); |
| |
| if (firstEntry == BSEARCH_ALL_SMALLER) |
| return err; |
| |
| if (firstEntry == BSEARCH_ALL_GREATER) |
| firstEntry = 0; |
| |
| lastEntry = kshark_find_entry_field_by_time(histo->max, |
| data->data, |
| firstEntry, |
| data->size - 1); |
| |
| if (lastEntry == BSEARCH_ALL_GREATER) |
| return err; |
| |
| if (lastEntry == BSEARCH_ALL_SMALLER) |
| lastEntry = data->size - 1; |
| |
| return {firstEntry, lastEntry}; |
| } |
| |
| static PlotPointList |
| getInBinEvents(kshark_trace_histo *histo, |
| kshark_data_container *data, |
| IsApplicableFunc isApplicable, |
| pushFunc push, |
| resolveFunc resolve) |
| { |
| int bin, lastBin(-1); |
| PlotPointList buffer; |
| |
| auto lamIsOverflow = [] (int bin) { |
| return (bin == UPPER_OVERFLOW_BIN || |
| bin == LOWER_OVERFLOW_BIN) ? true : false; |
| }; |
| |
| auto range = getRange(histo, data); |
| |
| for (ssize_t i = range.second; i >= range.first; --i) { |
| if (isApplicable(data, i)) { |
| bin = ksmodel_get_bin(histo, data->data[i]->entry); |
| if (lamIsOverflow(bin)) |
| continue; |
| |
| if (bin != lastBin) { |
| push(bin, data, i, &buffer); |
| lastBin = bin; |
| } else { |
| resolve(data, i, &buffer); |
| } |
| } |
| } |
| |
| return buffer; |
| } |
| |
| static PlotPointList |
| getLastInBinEvents(kshark_trace_histo *histo, kshark_data_container *data, |
| IsApplicableFunc isApplicable) |
| { |
| pushFunc push = [] (int bin, kshark_data_container *data, ssize_t i, |
| PlotPointList *list) { |
| list->push_front({bin, data->data[i]}); |
| }; |
| |
| /* |
| * Do not resolve. This means that only the very last (in time) |
| * appearance of the event in the bin will be visualized. |
| */ |
| resolveFunc resolve = [] (kshark_data_container *data, ssize_t i, |
| PlotPointList *list) {}; |
| |
| return getInBinEvents(histo, data, isApplicable, push, resolve); |
| } |
| |
| static PlotPointList |
| getMaxInBinEvents(kshark_trace_histo *histo, kshark_data_container *data, |
| IsApplicableFunc isApplicable) |
| { |
| pushFunc push = [] (int bin, kshark_data_container *data, ssize_t i, |
| PlotPointList *list) { |
| list->push_front({bin, data->data[i]}); |
| }; |
| |
| /* Overwrite if bigger. */ |
| resolveFunc resolve = [] (kshark_data_container *data, ssize_t i, |
| PlotPointList *list) { |
| if (list->front().second < data->data[i]) |
| list->front().second = data->data[i]; |
| }; |
| |
| return getInBinEvents(histo, data, isApplicable, push, resolve); |
| } |
| |
| |
| static PlotPointList |
| getMinInBinEvents(kshark_trace_histo *histo, kshark_data_container *data, |
| IsApplicableFunc isApplicable) |
| { |
| pushFunc push = [] (int bin, kshark_data_container *data, ssize_t i, |
| PlotPointList *list) { |
| list->push_front({bin, data->data[i]}); |
| }; |
| |
| /* Overwrite if smaller. */ |
| resolveFunc resolve = [] (kshark_data_container *data, ssize_t i, |
| PlotPointList *list) { |
| if (list->front().second > data->data[i]) |
| list->front().second = data->data[i]; |
| }; |
| |
| return getInBinEvents(histo, data, isApplicable, push, resolve); |
| } |
| |
| //! @cond Doxygen_Suppress |
| |
| #define PLUGIN_MIN_BOX_SIZE 4 |
| |
| //! @endcond |
| |
| static void intervalPlot(kshark_trace_histo *histo, |
| kshark_data_container *dataEvtA, |
| IsApplicableFunc checkFieldA, |
| kshark_data_container *dataEvtB, |
| IsApplicableFunc checkFieldB, |
| Graph *graph, |
| PlotObjList *shapes, |
| pluginShapeFunc makeShape, |
| Color col, |
| float size) |
| { |
| kshark_data_field_int64 *dataA, *dataB; |
| PlotPointList bufferA, bufferB; |
| int binA, binB; |
| int64_t tsB; |
| |
| auto lamGetBin = [] (auto it) {return (*it).first;}; |
| |
| auto lamGetTime = [] (auto it) {return (*it).second->entry->ts;}; |
| |
| auto lamGetData = [] (auto it) {return (*it).second;}; |
| |
| bufferA = getLastInBinEvents(histo, |
| dataEvtA, |
| checkFieldA); |
| |
| bufferB = getLastInBinEvents(histo, |
| dataEvtB, |
| checkFieldB); |
| |
| if (bufferA.empty() || bufferB.empty()) |
| return; |
| |
| auto itA = bufferA.cbegin(); |
| auto itB = bufferB.cbegin(); |
| while (itA != bufferA.cend() && itB != bufferB.cend()) { |
| binA = lamGetBin(itA); |
| dataA = lamGetData(itA); |
| |
| /* |
| * We will draw a shape between "Event A" and "Event B". |
| * Because the shape starts with "Event A", we will skip all |
| * "Event B" entries before the "Event A" entry. |
| */ |
| do { |
| dataB = lamGetData(itB); |
| tsB = lamGetTime(itB); |
| binB = lamGetBin(itB); |
| itB++; |
| } while (itB != bufferB.cend() && tsB < lamGetTime(itA)); |
| |
| /* |
| * The shape ends with "Event B" and we already have this |
| * event. However, we have to make sure that we will start the |
| * shape from the very last "Event A" entry, which is rigth |
| * before the "Event B" entry, which we already selected. |
| */ |
| while (itA != bufferA.cend() && lamGetTime(itA) < tsB) { |
| dataA = lamGetData(itA); |
| binA = lamGetBin(itA); |
| itA++; |
| } |
| |
| if (binB - binA >= PLUGIN_MIN_BOX_SIZE) |
| shapes->push_front(makeShape({graph}, |
| {binA, binB}, |
| {dataA, dataB}, |
| col, size)); |
| } |
| } |
| |
| /** |
| * @brief Generic plotting method for plugins. To be used for visualizing |
| * a trace events. |
| * |
| * @param argvCpp: The C++ arguments of the drawing function of the plugin. |
| * @param isApplicable: Check function used to select events from data |
| * container A. |
| * @param makeShape: Input location for a function pointer used to generate |
| * the shape to be plotted. |
| * @param col: The color of the shape to be plotted. |
| * @param size: The size of the shape to be plotted. |
| */ |
| void eventPlot(KsCppArgV *argvCpp, |
| IsApplicableFunc isApplicable, |
| pluginShapeFunc makeShape, |
| Color col, |
| float size) |
| { |
| try { |
| pointPlot(argvCpp, isApplicable, makeShape, col, size); |
| } catch (const std::exception &exc) { |
| std::cerr << "Exception in eventPlot\n" |
| << exc.what() << std::endl; |
| } |
| } |
| |
| //! @cond Doxygen_Suppress |
| |
| enum class PlotWath { |
| Maximum, |
| Minimum, |
| }; |
| |
| //! @endcond |
| |
| static void eventFieldPlot(KsCppArgV *argvCpp, |
| kshark_data_container *dataEvt, |
| IsApplicableFunc checkField, |
| PlotWath s, |
| pluginShapeFunc makeShape, |
| KsPlot::Color col, |
| float size) |
| { |
| PlotPointList buffer; |
| |
| if (dataEvt->size == 0) |
| return; |
| |
| if (!dataEvt->sorted) |
| kshark_data_container_sort(dataEvt); |
| |
| try { |
| if (s == PlotWath::Maximum) |
| buffer = getMaxInBinEvents(argvCpp->_histo, |
| dataEvt, checkField); |
| |
| if (s == PlotWath::Minimum) |
| buffer = getMinInBinEvents(argvCpp->_histo, |
| dataEvt, checkField); |
| |
| for (auto const &i: buffer) { |
| argvCpp->_shapes->push_front(makeShape({argvCpp->_graph}, |
| {i.first}, |
| {i.second}, |
| col, size)); |
| } |
| } catch (const std::exception &exc) { |
| std::cerr << "Exception in eventFieldPlot\n" |
| << exc.what() << std::endl; |
| } |
| } |
| |
| /** |
| * @brief Generic plotting method for plugins. To be used for visualizing |
| * the value of a data fiels trace events. |
| * |
| * @param argvCpp: The C++ arguments of the drawing function of the plugin. |
| * @param dataEvt: Input location for the container of the Evant's data. |
| * @param checkField: Check function used to select events from data |
| * container. |
| * @param makeShape: Input location for a function pointer used to generate |
| * the shape to be plotted. |
| * @param col: The color of the shape to be plotted. |
| * @param size: The size of the shape to be plotted. |
| */ |
| void eventFieldPlotMax(KsCppArgV *argvCpp, |
| kshark_data_container *dataEvt, |
| IsApplicableFunc checkField, |
| pluginShapeFunc makeShape, |
| KsPlot::Color col, |
| float size) |
| { |
| eventFieldPlot(argvCpp, dataEvt, checkField, |
| PlotWath::Maximum, |
| makeShape, col, size); |
| } |
| |
| /** |
| * @brief Generic plotting method for plugins. To be used for visualizing |
| * the value of a data fiels trace events. |
| * |
| * @param argvCpp: The C++ arguments of the drawing function of the plugin. |
| * @param dataEvt: Input location for the container of the Evant's data. |
| * @param checkField: check function used to select events from data |
| * container. |
| * @param makeShape: Input location for a function pointer used to generate |
| * the shape to be plotted. |
| * @param col: The color of the shape to be plotted. |
| * @param size: The size of the shape to be plotted. |
| */ |
| void eventFieldPlotMin(KsCppArgV *argvCpp, |
| kshark_data_container *dataEvt, |
| IsApplicableFunc checkField, |
| pluginShapeFunc makeShape, |
| KsPlot::Color col, |
| float size) |
| { |
| eventFieldPlot(argvCpp, dataEvt, checkField, |
| PlotWath::Minimum, |
| makeShape, col, size); |
| } |
| |
| /** |
| * @brief Generic plotting method for plugins. To be used for visualizing |
| * the correlation between two trace events. |
| * |
| * @param argvCpp: The C++ arguments of the drawing function of the plugin. |
| * @param dataEvtA: Input location for the container of the Evant A data. |
| * @param checkFieldA: Check function used to select events from data |
| * container A. |
| * @param dataEvtB: Input location for the container of the Evant B data. |
| * @param checkFieldB: Check function used to select events from data |
| * container B. |
| * @param makeShape: Input location for a function pointer used to generate |
| * the shape to be plotted. |
| * @param col: The color of the shape to be plotted. |
| * @param size: The size of the shape to be plotted. |
| */ |
| void eventFieldIntervalPlot(KsCppArgV *argvCpp, |
| kshark_data_container *dataEvtA, |
| IsApplicableFunc checkFieldA, |
| kshark_data_container *dataEvtB, |
| IsApplicableFunc checkFieldB, |
| pluginShapeFunc makeShape, |
| KsPlot::Color col, |
| float size) |
| { |
| if (dataEvtA->size == 0 || dataEvtB->size == 0) |
| return; |
| |
| if (!dataEvtA->sorted) |
| kshark_data_container_sort(dataEvtA); |
| |
| if (!dataEvtB->sorted) |
| kshark_data_container_sort(dataEvtB); |
| |
| try { |
| intervalPlot(argvCpp->_histo, |
| dataEvtA, checkFieldA, |
| dataEvtB, checkFieldB, |
| argvCpp->_graph, |
| argvCpp->_shapes, |
| makeShape, col, size); |
| } catch (const std::exception &exc) { |
| std::cerr << "Exception in eventFieldIntervalPlot\n" |
| << exc.what() << std::endl; |
| } |
| } |
| |
| /** |
| * @brief Distance between the click and the shape. Used to decide if |
| * the double click action must be executed. |
| * |
| * @param x: X coordinate of the click. |
| * @param y: Y coordinate of the click. |
| * |
| * @returns If the click is inside the box, the distance is zero. |
| * Otherwise infinity. |
| */ |
| double LatencyBox::distance(int x, int y) const |
| { |
| if (x < pointX(0) || x > pointX(2)) |
| return std::numeric_limits<double>::max(); |
| |
| if (y < pointY(0) || y > pointY(1)) |
| return std::numeric_limits<double>::max(); |
| |
| return 0; |
| } |