The plugin allows the user to visualize the recorded value of a given data field from a given trace event. The core logic that implements the processing of the data and the visualization itself is rather simple. It is implemented in: event_field_plot.h, event_field_plot.c and EventFieldPlot.cpp The plugin also registers its own dialog, that allows the user to select the event and field to be visualized. The widget of the dialog gets implemented in: EventFieldDialog.hpp and EventFieldDialog.cpp Signed-off-by: Yordan Karadzhov (VMware) <y.karadz@xxxxxxxxx> --- src/plugins/CMakeLists.txt | 5 + src/plugins/EventFieldDialog.cpp | 183 +++++++++++++++++++++++++++++++ src/plugins/EventFieldDialog.hpp | 60 ++++++++++ src/plugins/EventFieldPlot.cpp | 109 ++++++++++++++++++ src/plugins/event_field_plot.c | 125 +++++++++++++++++++++ src/plugins/event_field_plot.h | 64 +++++++++++ tests/libkshark-gui-tests.cpp | 1 + 7 files changed, 547 insertions(+) create mode 100644 src/plugins/EventFieldDialog.cpp create mode 100644 src/plugins/EventFieldDialog.hpp create mode 100644 src/plugins/EventFieldPlot.cpp create mode 100644 src/plugins/event_field_plot.c create mode 100644 src/plugins/event_field_plot.h diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 5e28368..333f0da 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -44,6 +44,11 @@ if (Qt5Widgets_FOUND AND TT_FONT_FILE) SOURCE sched_events.c SchedEvents.cpp) list(APPEND PLUGIN_LIST "sched_events") + BUILD_GUI_PLUGIN(NAME event_field_plot + MOC EventFieldDialog.hpp + SOURCE event_field_plot.c EventFieldDialog.cpp EventFieldPlot.cpp) + list(APPEND PLUGIN_LIST "event_field_plot") + endif (Qt5Widgets_FOUND AND TT_FONT_FILE) BUILD_PLUGIN(NAME missed_events diff --git a/src/plugins/EventFieldDialog.cpp b/src/plugins/EventFieldDialog.cpp new file mode 100644 index 0000000..fbfe4cc --- /dev/null +++ b/src/plugins/EventFieldDialog.cpp @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2020 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@xxxxxxxxx> + */ + +/** + * @file EventFieldDialog.cpp + * @brief Dialog class used by the EventFieldPlot plugin. + */ + +// C++ +#include <iostream> +#include <vector> + +// KernelShark +#include "KsMainWindow.hpp" +#include "EventFieldDialog.hpp" + +/** The name of the menu item used to start the dialog of the plugin. */ +#define DIALOG_NAME "Plot Event Field" + +/** Create plugin dialog widget. */ +KsEFPDialog::KsEFPDialog(QWidget *parent) +: QDialog(parent), + _selectLabel("Show", this), + _applyButton("Apply", this), + _resetButton("Reset", this), + _cancelButton("Cancel", this) +{ + setWindowTitle(DIALOG_NAME); + + _topLayout.addWidget(&_efsWidget); + + _topLayout.addWidget(&_selectLabel); + _setSelectCombo(); + _topLayout.addWidget(&_selectComboBox); + + _buttonLayout.addWidget(&_applyButton); + _applyButton.setAutoDefault(false); + + _buttonLayout.addWidget(&_resetButton); + _resetButton.setAutoDefault(false); + + _buttonLayout.addWidget(&_cancelButton); + _cancelButton.setAutoDefault(false); + + _buttonLayout.setAlignment(Qt::AlignLeft); + _topLayout.addLayout(&_buttonLayout); + + connect(&_applyButton, &QPushButton::pressed, + this, &KsEFPDialog::_apply); + + connect(&_applyButton, &QPushButton::pressed, + this, &QWidget::close); + + connect(&_resetButton, &QPushButton::pressed, + this, &KsEFPDialog::_reset); + + connect(&_resetButton, &QPushButton::pressed, + this, &QWidget::close); + + connect(&_cancelButton, &QPushButton::pressed, + this, &QWidget::close); + + setLayout(&_topLayout); +} + +void KsEFPDialog::_setSelectCombo() +{ + _selectComboBox.clear(); + _selectComboBox.addItem("max. value", 0); + _selectComboBox.addItem("min. value", 1); +} + +/** Select the plotting criteria. */ +void KsEFPDialog::selectCondition(plugin_efp_context *plugin_ctx) +{ + /* In the combo box "max" is 0 and "min" is 1. */ + plugin_ctx->show_max = !_selectComboBox.currentData().toInt(); +} + +/** Update the dialog, using the current settings of the plugin. */ +void KsEFPDialog::update() +{ + _efsWidget.setStreamCombo(); +} + +static KsEFPDialog *efp_dialog(nullptr); + +static int plugin_get_stream_id() +{ + return efp_dialog->_efsWidget.streamId(); +} + +/** Use the Event name selected by the user to update the plugin's context. */ +__hidden void plugin_set_event_name(plugin_efp_context *plugin_ctx) +{ + QString buff = efp_dialog->_efsWidget.eventName(); + char *event; + + if (asprintf(&event, "%s", buff.toStdString().c_str()) >= 0) { + plugin_ctx->event_name = event; + return; + } + + plugin_ctx->event_name = NULL; +} + +/** Use the Field name selected by the user to update the plugin's context. */ +__hidden void plugin_set_field_name(plugin_efp_context *plugin_ctx) +{ + QString buff = efp_dialog->_efsWidget.fieldName(); + char *field; + + if (asprintf(&field, "%s", buff.toStdString().c_str()) >= 0) { + plugin_ctx->field_name = field; + return; + } + + plugin_ctx->field_name = NULL; +} + +/** Use the condition selected by the user to update the plugin's context. */ +__hidden void plugin_set_select_condition(plugin_efp_context *plugin_ctx) +{ + efp_dialog->selectCondition(plugin_ctx); +} + +void KsEFPDialog::_apply() +{ + auto work = KsWidgetsLib::KsDataWork::UpdatePlugins; + + /* The plugin needs to process the data and this may take time + * on large datasets. Show a "Work In Process" warning. + */ + _gui_ptr->wipPtr()->show(work); + _gui_ptr->registerPluginToStream("event_field_plot", + {plugin_get_stream_id()}); + _gui_ptr->wipPtr()->hide(work); +} + +void KsEFPDialog::_reset() +{ + auto work = KsWidgetsLib::KsDataWork::UpdatePlugins; + kshark_context *kshark_ctx(nullptr); + QVector<int> streamIds; + + if (!kshark_instance(&kshark_ctx)) + return; + + streamIds = KsUtils::getStreamIdList(kshark_ctx); + + /* + * The plugin needs to process the data and this may take time + * on large datasets. Show a "Work In Process" warning. + */ + _gui_ptr->wipPtr()->show(work); + _gui_ptr->unregisterPluginFromStream("event_field_plot", + streamIds); + _gui_ptr->wipPtr()->hide(work); +} + +static void showDialog(KsMainWindow *ks) +{ + efp_dialog->update(); + efp_dialog->show(); +} + +/** Add the dialog of the plugin to the KernelShark menus. */ +__hidden void *plugin_efp_add_menu(void *ks_ptr) +{ + if (!efp_dialog) { + efp_dialog = new KsEFPDialog(); + efp_dialog->_gui_ptr = static_cast<KsMainWindow *>(ks_ptr); + } + + QString menu("Tools/"); + menu += DIALOG_NAME; + efp_dialog->_gui_ptr->addPluginMenu(menu, showDialog); + + return efp_dialog; +} diff --git a/src/plugins/EventFieldDialog.hpp b/src/plugins/EventFieldDialog.hpp new file mode 100644 index 0000000..46339b2 --- /dev/null +++ b/src/plugins/EventFieldDialog.hpp @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2020 VMware Inc, Yordan Karadzhov <ykaradzhov@xxxxxxxxxx> + */ + +/** + * @file EventFieldDialog.hpp + * @brief Dialog class used by the EventFieldPlot plugin. + */ + +#ifndef _KS_EFP_DIALOG_H +#define _KS_EFP_DIALOG_H + +// KernelShark +#include "plugins/event_field_plot.h" +#include "KsWidgetsLib.hpp" + +class KsMainWindow; + +/** + * The KsEFPDialog class provides a widget for selecting Trace event field to + * be visualized. + */ + +class KsEFPDialog : public QDialog +{ + Q_OBJECT +public: + explicit KsEFPDialog(QWidget *parent = nullptr); + + void update(); + + void selectCondition(plugin_efp_context *plugin_ctx); + + /** Widget for selecting Treace event. */ + KsWidgetsLib::KsEventFieldSelectWidget _efsWidget; + + /** KernelShark GUI (main window) object. */ + KsMainWindow *_gui_ptr; + +private: + QVBoxLayout _topLayout; + + QHBoxLayout _buttonLayout; + + QComboBox _selectComboBox; + + QLabel _selectLabel; + + QPushButton _applyButton, _resetButton, _cancelButton; + + void _setSelectCombo(); + + void _apply(); + + void _reset(); +}; + +#endif diff --git a/src/plugins/EventFieldPlot.cpp b/src/plugins/EventFieldPlot.cpp new file mode 100644 index 0000000..1938d62 --- /dev/null +++ b/src/plugins/EventFieldPlot.cpp @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2020 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@xxxxxxxxx> + */ + +/** + * @file EventFieldPlot.cpp + * @brief Plugin for visualizing a given data field of a trace event. + */ + +// C++ +#include <vector> + +// KernelShark +#include "plugins/event_field_plot.h" +#include "KsPlotTools.hpp" +#include "KsPlugins.hpp" + +using namespace KsPlot; + +/** + * @brief Plugin's draw function. + * + * @param argv_c: A C pointer to be converted to KsCppArgV (C++ struct). + * @param sd: Data stream identifier. + * @param val: Can be CPU Id or Process Id. + * @param draw_action: Draw action identifier. + */ +__hidden void draw_event_field(kshark_cpp_argv *argv_c, + int sd, int val, int draw_action) +{ + KsCppArgV *argvCpp = KS_ARGV_TO_CPP(argv_c); + Graph *graph = argvCpp->_graph; + plugin_efp_context *plugin_ctx; + IsApplicableFunc checkEntry; + int binSize(0), s0, s1; + int64_t norm; + + if (!(draw_action & KSHARK_CPU_DRAW) && + !(draw_action & KSHARK_TASK_DRAW)) + return; + + plugin_ctx = __get_context(sd); + if (!plugin_ctx) + return; + + /* Get the size of the graph's bins. */ + for (int i = 0; i < graph->size(); ++i) + if (graph->bin(i).mod()) { + binSize = graph->bin(i)._size; + break; + } + + s0 = graph->height() / 3; + s1 = graph->height() / 5; + + norm = plugin_ctx->field_max - plugin_ctx->field_min; + /* Avoid division by zero. */ + if (norm == 0) + ++norm; + + auto lamMakeShape = [=] (std::vector<const Graph *> graph, + std::vector<int> bin, + std::vector<kshark_data_field_int64 *> data, + Color, float) { + int x, y, mod(binSize); + Color c; + + x = graph[0]->bin(bin[0])._val.x(); + y = graph[0]->bin(bin[0])._val.y() - s0; + + if (plugin_ctx->show_max) + mod += s1 * (data[0]->field - plugin_ctx->field_min) / norm; + else + mod += s1 * (plugin_ctx->field_max - data[0]->field) / norm; + + Point p0(x, y + mod), p1(x, y - mod); + Line *l = new Line(p0, p1); + c.setRainbowColor(mod - 1); + l->_size = binSize + 1; + l->_color = c; + + return l; + }; + + if (draw_action & KSHARK_CPU_DRAW) + checkEntry = [=] (kshark_data_container *d, ssize_t i) { + return d->data[i]->entry->cpu == val; + }; + + else if (draw_action & KSHARK_TASK_DRAW) + checkEntry = [=] (kshark_data_container *d, ssize_t i) { + return d->data[i]->entry->pid == val; + }; + + if (plugin_ctx->show_max) + eventFieldPlotMax(argvCpp, + plugin_ctx->data, checkEntry, + lamMakeShape, + {}, // Undefined color + 0); // Undefined size + else + eventFieldPlotMin(argvCpp, + plugin_ctx->data, checkEntry, + lamMakeShape, + {}, // Undefined color + 0); // Undefined size +} diff --git a/src/plugins/event_field_plot.c b/src/plugins/event_field_plot.c new file mode 100644 index 0000000..08b453f --- /dev/null +++ b/src/plugins/event_field_plot.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2020 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@xxxxxxxxx> + */ + +/** + * @file event_field_plot.c + * @brief Plugin for visualizing a given data field of a trace event. + */ + +// C +#include <stdio.h> +#include <assert.h> +#include <limits.h> + +// KernelShark +#include "plugins/event_field_plot.h" + +static void efp_free_context(struct plugin_efp_context *plugin_ctx) +{ + if (!plugin_ctx) + return; + + free(plugin_ctx->event_name); + free(plugin_ctx->field_name); + kshark_free_data_container(plugin_ctx->data); +} + +/** A general purpose macro is used to define plugin context. */ +KS_DEFINE_PLUGIN_CONTEXT(struct plugin_efp_context, efp_free_context); + +static bool plugin_efp_init_context(struct kshark_data_stream *stream, + struct plugin_efp_context *plugin_ctx) +{ + plugin_set_event_name(plugin_ctx); + plugin_set_field_name(plugin_ctx); + plugin_set_select_condition(plugin_ctx); + + plugin_ctx->field_max = INT64_MIN; + plugin_ctx->field_min = INT64_MAX; + + plugin_ctx->event_id = + kshark_find_event_id(stream, plugin_ctx->event_name); + + if (plugin_ctx->event_id < 0) { + fprintf(stderr, "Event %s not found in stream %s:%s\n", + plugin_ctx->event_name, stream->file, stream->name); + return false; + } + + plugin_ctx->data = kshark_init_data_container(); + if (!plugin_ctx->data) + return false; + + return true; +} + +static void plugin_get_field(struct kshark_data_stream *stream, void *rec, + struct kshark_entry *entry) +{ + struct plugin_efp_context *plugin_ctx; + int64_t val; + + plugin_ctx = __get_context(stream->stream_id); + if (!plugin_ctx) + return; + + kshark_read_record_field_int(stream, rec, + plugin_ctx->field_name, + &val); + + kshark_data_container_append(plugin_ctx->data, entry, val); + + if (val > plugin_ctx->field_max) + plugin_ctx->field_max = val; + + if (val < plugin_ctx->field_min) + plugin_ctx->field_min = val; +} + +/** Load this plugin. */ +int KSHARK_PLOT_PLUGIN_INITIALIZER(struct kshark_data_stream *stream) +{ + struct plugin_efp_context *plugin_ctx = __init(stream->stream_id); + + if (!plugin_ctx || !plugin_efp_init_context(stream, plugin_ctx)) { + __close(stream->stream_id); + return 0; + } + + kshark_register_event_handler(stream, + plugin_ctx->event_id, + plugin_get_field); + + kshark_register_draw_handler(stream, draw_event_field); + + return 1; +} + +/** Unload this plugin. */ +int KSHARK_PLOT_PLUGIN_DEINITIALIZER(struct kshark_data_stream *stream) +{ + struct plugin_efp_context *plugin_ctx = __get_context(stream->stream_id); + int ret = 0; + + if (plugin_ctx) { + kshark_unregister_event_handler(stream, + plugin_ctx->event_id, + plugin_get_field); + + kshark_unregister_draw_handler(stream, draw_event_field); + ret = 1; + } + + __close(stream->stream_id); + + return ret; +} + +/** Initialize the control interface of the plugin. */ +void *KSHARK_MENU_PLUGIN_INITIALIZER(void *gui_ptr) +{ + return plugin_efp_add_menu(gui_ptr); +} diff --git a/src/plugins/event_field_plot.h b/src/plugins/event_field_plot.h new file mode 100644 index 0000000..43bbac2 --- /dev/null +++ b/src/plugins/event_field_plot.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2020 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@xxxxxxxxx> + */ + +/** + * @file event_field_plot.h + * @brief Plugin for visualizing a given data field of a trace event. + */ + +#ifndef _KS_PLUGIN_EVENT_FIELD_H +#define _KS_PLUGIN_EVENT_FIELD_H + +// KernelShark +#include "libkshark.h" +#include "libkshark-plugin.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Structure representing a plugin-specific context. */ +struct plugin_efp_context { + /** Trace event name. */ + char *event_name; + + /** Event field name. */ + char *field_name; + + /** The max value of the field in the data. */ + int64_t field_max; + + /** The min value of the field in the data. */ + int64_t field_min; + + /** Trace event identifier. */ + int event_id; + + /** If true, highlight the max field value. Else highlight the min. */ + bool show_max; + + /** Container object to store the trace event field's data. */ + struct kshark_data_container *data; +}; + +KS_DECLARE_PLUGIN_CONTEXT_METHODS(struct plugin_efp_context) + +void draw_event_field(struct kshark_cpp_argv *argv_c, + int sd, int pid, int draw_action); + +void *plugin_efp_add_menu(void *gui_ptr); + +void plugin_set_event_name(struct plugin_efp_context *plugin_ctx); + +void plugin_set_field_name(struct plugin_efp_context *plugin_ctx); + +void plugin_set_select_condition(struct plugin_efp_context *plugin_ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tests/libkshark-gui-tests.cpp b/tests/libkshark-gui-tests.cpp index bc49194..a023c39 100644 --- a/tests/libkshark-gui-tests.cpp +++ b/tests/libkshark-gui-tests.cpp @@ -147,6 +147,7 @@ BOOST_AUTO_TEST_CASE(KsUtils_KsDataStore) BOOST_AUTO_TEST_CASE(KsUtils_getPluginList) { QStringList plugins{"sched_events", + "event_field_plot", "missed_events" }; -- 2.27.0