From patchwork Wed Oct 10 20:10:12 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759515 Return-Path: Received: from mail-dm3nam03on0085.outbound.protection.outlook.com ([104.47.41.85]:42585 "EHLO NAM03-DM3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727567AbeJKDei (ORCPT ); Wed, 10 Oct 2018 23:34:38 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH v2 1/4] kernel-shark-qt: Add Qt as a third party dependency. Date: Wed, 10 Oct 2018 23:10:12 +0300 Message-Id: <20181010201015.23824-2-ykaradzhov@vmware.com> In-Reply-To: <20181010201015.23824-1-ykaradzhov@vmware.com> References: <20181010201015.23824-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 2092 From: Yordan Karadzhov (VMware) This patch prepares the Cmake build infrastructure for the introduction of a KernelShark GUI, baset on Qt. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/CMakeLists.txt | 8 ++++++++ kernel-shark-qt/README | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/kernel-shark-qt/CMakeLists.txt b/kernel-shark-qt/CMakeLists.txt index 4a40b11..71a021e 100644 --- a/kernel-shark-qt/CMakeLists.txt +++ b/kernel-shark-qt/CMakeLists.txt @@ -20,6 +20,14 @@ find_package(Doxygen) find_package(OpenGL) find_package(GLUT) +find_package(Qt5Widgets 5.7.1) +find_package(Qt5Network) +if (Qt5Widgets_FOUND) + + message(STATUS "Found Qt5Widgets: (version ${Qt5Widgets_VERSION})") + +endif (Qt5Widgets_FOUND) + set(LIBRARY_OUTPUT_PATH "${KS_DIR}/lib") set(EXECUTABLE_OUTPUT_PATH "${KS_DIR}/bin") diff --git a/kernel-shark-qt/README b/kernel-shark-qt/README index 84708bd..14bcb77 100644 --- a/kernel-shark-qt/README +++ b/kernel-shark-qt/README @@ -4,10 +4,13 @@ This directory contains the new Qt-based version of the KernelShark GUI. Third Party Software: ------------------------------------------------------------ -The external dependencies: +KernelShark has the following external dependencies: + Cmake, Json-C, OpenGL/Glut, Qt5Base. + 1. In order to install the packages on Ubuntu do the following: sudo apt-get install build-essential git cmake libjson-c-dev -y sudo apt-get install freeglut3-dev libxmu-dev libxi-dev -y + sudo apt-get install qtbase5-dev -y 1.1 I you want to be able to generate Doxygen documentation: sudo apt-get install graphviz doxygen-gui -y @@ -16,6 +19,7 @@ The external dependencies: 2. In order to install the packages on Fedora, as root do the following: dnf install gcc gcc-c++ git cmake json-c-devel -y dnf install freeglut-devel redhat-rpm-config -y + dnf install qt5-qtbase-devel -y 2.1 I you want to be able to generate Doxygen documentation: dnf install graphviz doxygen -y From patchwork Wed Oct 10 20:10:13 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759517 Return-Path: Received: from mail-dm3nam03on0085.outbound.protection.outlook.com ([104.47.41.85]:42585 "EHLO NAM03-DM3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727523AbeJKDe7 (ORCPT ); Wed, 10 Oct 2018 23:34:59 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH v2 2/4] kernel-shark-qt: Add KernalShark Utils Date: Wed, 10 Oct 2018 23:10:13 +0300 Message-Id: <20181010201015.23824-3-ykaradzhov@vmware.com> In-Reply-To: <20181010201015.23824-1-ykaradzhov@vmware.com> References: <20181010201015.23824-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 22314 From: Yordan Karadzhov (VMware) This patch introduces the kshark-gui library and defines some basic components, like Data Store and Plugin Manager, used under the hood of the KernelShark GUI. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/build/deff.h.cmake | 16 + kernel-shark-qt/src/CMakeLists.txt | 20 + kernel-shark-qt/src/KsUtils.cpp | 594 +++++++++++++++++++++++++++++ kernel-shark-qt/src/KsUtils.hpp | 233 +++++++++++ 4 files changed, 863 insertions(+) create mode 100644 kernel-shark-qt/src/KsUtils.cpp create mode 100644 kernel-shark-qt/src/KsUtils.hpp diff --git a/kernel-shark-qt/build/deff.h.cmake b/kernel-shark-qt/build/deff.h.cmake index 44ea08b..d1a1bb7 100644 --- a/kernel-shark-qt/build/deff.h.cmake +++ b/kernel-shark-qt/build/deff.h.cmake @@ -14,7 +14,23 @@ /** KernelShark source code path. */ #cmakedefine KS_DIR "@KS_DIR@" +/** KernelShark configuration directory path. */ +#cmakedefine KS_CONF_DIR "@KS_CONF_DIR@" + /** Location of the trace-cmd executable. */ #cmakedefine TRACECMD_BIN_DIR "@TRACECMD_BIN_DIR@" +#ifdef __cplusplus + + #include + + /** + * String containing semicolon-separated list of plugin names. + * The plugins to be loaded when KernelShark starts are tagged + * with "default". + */ + const QString plugins = "@PLUGINS@"; + +#endif /* __cplusplus */ + #endif // _KS_CONFIG_H diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 305cea7..e897e9a 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -28,6 +28,26 @@ if (OPENGL_FOUND AND GLUT_FOUND) endif (OPENGL_FOUND AND GLUT_FOUND) +if (Qt5Widgets_FOUND AND Qt5Network_FOUND) + + message(STATUS "libkshark-gui") + set (ks-guiLib_hdr KsUtils.hpp) + + QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr}) + + add_library(kshark-gui SHARED ${ks-guiLib_hdr_moc} KsUtils.cpp) + + target_link_libraries(kshark-gui kshark-plot + ${CMAKE_DL_LIBS} + ${TRACEEVENT_LIBRARY} + ${TRACECMD_LIBRARY} + Qt5::Widgets + Qt5::Network) + + set_target_properties(kshark-gui PROPERTIES SUFFIX ".so.${KS_VERSION_STRING}") + +endif (Qt5Widgets_FOUND AND Qt5Network_FOUND) + add_subdirectory(plugins) configure_file( ${KS_DIR}/build/deff.h.cmake diff --git a/kernel-shark-qt/src/KsUtils.cpp b/kernel-shark-qt/src/KsUtils.cpp new file mode 100644 index 0000000..af2ae60 --- /dev/null +++ b/kernel-shark-qt/src/KsUtils.cpp @@ -0,0 +1,594 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsUtils.cpp + * @brief KernelShark Utils. + */ + +// KernelShark +#include "KsUtils.hpp" + +namespace KsUtils { + +/** @brief Get a sorted vector of Task's Pids. */ +QVector getPidList() +{ + kshark_context *kshark_ctx(nullptr); + int nTasks, *tempPids; + QVector pids; + + if (!kshark_instance(&kshark_ctx)) + return pids; + + nTasks = kshark_get_task_pids(kshark_ctx, &tempPids); + for (int r = 0; r < nTasks; ++r) { + pids.append(tempPids[r]); + } + + free(tempPids); + + qSort(pids); + + return pids; +} + +/** + * Set the bit of the filter mask of the kshark session context responsible + * for the visibility of the events in the Table View. + */ +void listFilterSync(bool state) +{ + kshark_context *kshark_ctx(nullptr); + + if (!kshark_instance(&kshark_ctx)) + return; + + if (state) { + kshark_ctx->filter_mask |= KS_TEXT_VIEW_FILTER_MASK; + } else { + kshark_ctx->filter_mask &= ~KS_TEXT_VIEW_FILTER_MASK; + } +} + +/** + * Set the bit of the filter mask of the kshark session context responsible + * for the visibility of the events in the Graph View. + */ +void graphFilterSync(bool state) +{ + kshark_context *kshark_ctx(nullptr); + + if (!kshark_instance(&kshark_ctx)) + return; + + if (state) { + kshark_ctx->filter_mask |= KS_GRAPH_VIEW_FILTER_MASK; + } else { + kshark_ctx->filter_mask &= ~KS_GRAPH_VIEW_FILTER_MASK; + } +} + +/** + * @brief Simple CPU matching function to be user for data collections. + * + * @param kshark_ctx: Input location for the session context pointer. + * @param e: kshark_entry to be checked. + * @param cpu: Matching condition value. + * + * @returns True if the CPU of the entry matches the value of "cpu" and + * the entry is visibility in Graph. Otherwise false. + */ +bool matchCPUVisible(struct kshark_context *kshark_ctx, + struct kshark_entry *e, int cpu) +{ + return (e->cpu == cpu && (e->visible & KS_GRAPH_VIEW_FILTER_MASK)); +} + +}; // KsUtils + +/** A stream operator for converting QColor into KsPlot::Color. */ +KsPlot::Color& operator <<(KsPlot::Color &thisColor, const QColor &c) +{ + thisColor.set(c.red(), c.green(), c.blue()); + + return thisColor; +} + +/** Create a default (empty) KsDataStore. */ +KsDataStore::KsDataStore(QWidget *parent) +: QObject(parent), + _tep(nullptr), + _rows(nullptr), + _dataSize(0) +{} + +/** Destroy the KsDataStore object. */ +KsDataStore::~KsDataStore() +{} + +/** Load trace data for file. */ +void KsDataStore::loadDataFile(const QString &file) +{ + kshark_context *kshark_ctx(nullptr); + + if (!kshark_instance(&kshark_ctx)) + return; + + clear(); + + if (!kshark_open(kshark_ctx, file.toStdString().c_str())) { + qCritical() << "ERROR Loading file " << file; + return; + } + + _tep = kshark_ctx->pevent; + + if (kshark_ctx->event_handlers == nullptr) + kshark_handle_plugins(kshark_ctx, KSHARK_PLUGIN_INIT); + else + kshark_handle_plugins(kshark_ctx, KSHARK_PLUGIN_UPDATE); + + _dataSize = kshark_load_data_entries(kshark_ctx, &_rows); +} + +void KsDataStore::_freeData() +{ + if (_dataSize) { + for (size_t r = 0; r < _dataSize; ++r) + free(_rows[r]); + + free(_rows); + _rows = nullptr; + } +} + +/** Reload the trace data. */ +void KsDataStore::reload() +{ + kshark_context *kshark_ctx(nullptr); + + if (!kshark_instance(&kshark_ctx)) + return; + + _freeData(); + + _dataSize = kshark_load_data_entries(kshark_ctx, &_rows); + _tep = kshark_ctx->pevent; + + emit updateWidgets(this); +} + +/** Free the loaded trace data and close the file. */ +void KsDataStore::clear() +{ + kshark_context *kshark_ctx(nullptr); + + _freeData(); + _tep = nullptr; + + if (kshark_instance(&kshark_ctx) && kshark_ctx->handle) + kshark_close(kshark_ctx); +} + +/** Update the visibility of the entries (filter). */ +void KsDataStore::update() +{ + kshark_context *kshark_ctx(nullptr); + + if (!kshark_instance(&kshark_ctx)) + return; + + if (kshark_filter_is_set(kshark_ctx)) { + kshark_filter_entries(kshark_ctx, _rows, _dataSize); + emit updateWidgets(this); + } +} + +/** Register a collection of visible entries for each CPU. */ +void KsDataStore::registerCPUCollections() +{ + kshark_context *kshark_ctx(nullptr); + + if (!kshark_instance(&kshark_ctx) || + !kshark_filter_is_set(kshark_ctx)) + return; + + int nCPUs = _tep->cpus; + for (int cpu = 0; cpu < nCPUs; ++cpu) { + kshark_register_data_collection(kshark_ctx, + _rows, _dataSize, + KsUtils::matchCPUVisible, + cpu, + 0); + } +} + +void KsDataStore::_unregisterCPUCollections() +{ + kshark_context *kshark_ctx(nullptr); + + if (!kshark_instance(&kshark_ctx)) + return; + + int nCPUs = _tep->cpus; + for (int cpu = 0; cpu < nCPUs; ++cpu) { + kshark_unregister_data_collection(&kshark_ctx->collections, + KsUtils::matchCPUVisible, + cpu); + } +} + +void KsDataStore::_applyIdFilter(int filterId, QVector vec) +{ + kshark_context *kshark_ctx(nullptr); + + if (!kshark_instance(&kshark_ctx)) + return; + + switch (filterId) { + case KS_SHOW_EVENT_FILTER: + case KS_HIDE_EVENT_FILTER: + kshark_filter_clear(kshark_ctx, KS_SHOW_EVENT_FILTER); + kshark_filter_clear(kshark_ctx, KS_HIDE_EVENT_FILTER); + break; + case KS_SHOW_TASK_FILTER: + case KS_HIDE_TASK_FILTER: + kshark_filter_clear(kshark_ctx, KS_SHOW_TASK_FILTER); + kshark_filter_clear(kshark_ctx, KS_HIDE_TASK_FILTER); + break; + default: + return; + } + + for (auto &&pid: vec) + kshark_filter_add_id(kshark_ctx, filterId, pid); + + if (!_tep) + return; + + _unregisterCPUCollections(); + + /* + * If the advanced event filter is set, the data has to be reloaded, + * because the advanced filter uses tep_records. + */ + if (kshark_ctx->advanced_event_filter->filters) + reload(); + else + kshark_filter_entries(kshark_ctx, _rows, _dataSize); + + registerCPUCollections(); + + emit updateWidgets(this); +} + +/** Apply Show Task filter. */ +void KsDataStore::applyPosTaskFilter(QVector vec) +{ + _applyIdFilter(KS_SHOW_TASK_FILTER, vec); +} + +/** Apply Hide Task filter. */ +void KsDataStore::applyNegTaskFilter(QVector vec) +{ + _applyIdFilter(KS_HIDE_TASK_FILTER, vec); +} + +/** Apply Show Event filter. */ +void KsDataStore::applyPosEventFilter(QVector vec) +{ + _applyIdFilter(KS_SHOW_EVENT_FILTER, vec); +} + +/** Apply Hide Event filter. */ +void KsDataStore::applyNegEventFilter(QVector vec) +{ + _applyIdFilter(KS_HIDE_EVENT_FILTER, vec); +} + +/** Disable all filters. */ +void KsDataStore::clearAllFilters() +{ + kshark_context *kshark_ctx(nullptr); + + if (!kshark_instance(&kshark_ctx) || !_tep) + return; + + _unregisterCPUCollections(); + + kshark_filter_clear(kshark_ctx, KS_SHOW_TASK_FILTER); + kshark_filter_clear(kshark_ctx, KS_HIDE_TASK_FILTER); + kshark_filter_clear(kshark_ctx, KS_SHOW_EVENT_FILTER); + kshark_filter_clear(kshark_ctx, KS_HIDE_EVENT_FILTER); + + tep_filter_reset(kshark_ctx->advanced_event_filter); + kshark_clear_all_filters(kshark_ctx, _rows, _dataSize); + + emit updateWidgets(this); +} + +/** + * @brief Create Plugin Manager. Use list of plugins declared in the + * CMake-generated header file. + */ +KsPluginManager::KsPluginManager(QWidget *parent) +: QObject(parent) +{ + kshark_context *kshark_ctx(nullptr); + _parsePluginList(); + + if (!kshark_instance(&kshark_ctx)) + return; + + registerFromList(kshark_ctx); +} + +/** Parse the plugin list declared in the CMake-generated header file. */ +void KsPluginManager::_parsePluginList() +{ + _ksPluginList = KsUtils::getPluginList(); + int nPlugins = _ksPluginList.count(); + + _registeredKsPlugins.resize(nPlugins); + for (int i = 0; i < nPlugins; ++i) { + if (_ksPluginList[i].contains(" default", Qt::CaseInsensitive)) { + _ksPluginList[i].remove(" default", Qt::CaseInsensitive); + _registeredKsPlugins[i] = true; + } else { + _registeredKsPlugins[i] = false; + } + } +} + +/** + * Register the plugins by using the information in "_ksPluginList" and + * "_registeredKsPlugins". + */ +void KsPluginManager::registerFromList(kshark_context *kshark_ctx) +{ + auto lamRegBuiltIn = [&kshark_ctx](const QString &plugin) + { + char *lib; + int n; + + n = asprintf(&lib, "%s/lib/plugin-%s.so", + KS_DIR, plugin.toStdString().c_str()); + if (n <= 0) + return; + + kshark_register_plugin(kshark_ctx, lib); + free(lib); + }; + + auto lamRegUser = [&kshark_ctx](const QString &plugin) + { + const char *lib = plugin.toStdString().c_str(); + kshark_register_plugin(kshark_ctx, lib); + }; + + _forEachInList(_ksPluginList, + _registeredKsPlugins, + lamRegBuiltIn); + + _forEachInList(_userPluginList, + _registeredUserPlugins, + lamRegUser); +} + +/** + * Unegister the plugins by using the information in "_ksPluginList" and + * "_registeredKsPlugins". + */ +void KsPluginManager::unregisterFromList(kshark_context *kshark_ctx) +{ + auto lamUregBuiltIn = [&kshark_ctx](const QString &plugin) + { + char *lib; + int n; + + n = asprintf(&lib, "%s/lib/plugin-%s.so", + KS_DIR, plugin.toStdString().c_str()); + if (n <= 0) + return; + + kshark_unregister_plugin(kshark_ctx, lib); + free(lib); + }; + + auto lamUregUser = [&kshark_ctx](const QString &plugin) + { + const char *lib = plugin.toStdString().c_str(); + kshark_unregister_plugin(kshark_ctx, lib); + }; + + _forEachInList(_ksPluginList, + _registeredKsPlugins, + lamUregBuiltIn); + + _forEachInList(_userPluginList, + _registeredUserPlugins, + lamUregUser); +} + +/** + * @brief Register a Plugin. + * + * @param plugin: provide here the name of the plugin (as in the CMake-generated + * header file) of a name of the plugin's library file (.so). + */ +void KsPluginManager::registerPlugin(const QString &plugin) +{ + kshark_context *kshark_ctx(nullptr); + char *lib; + int n; + + if (!kshark_instance(&kshark_ctx)) + return; + + for (int i = 0; i < _ksPluginList.count(); ++i) { + if (_ksPluginList[i] == plugin) { + /* + * The argument is the name of the plugin. From the + * name get the library .so file. + */ + n = asprintf(&lib, "%s/lib/plugin-%s.so", + KS_DIR, plugin.toStdString().c_str()); + if (n > 0) { + kshark_register_plugin(kshark_ctx, lib); + _registeredKsPlugins[i] = true; + free(lib); + } + + return; + + } else if (plugin.contains("/lib/plugin-" + _ksPluginList[i], + Qt::CaseInsensitive)) { + /* + * The argument is the name of the library .so file. + */ + n = asprintf(&lib, "%s", plugin.toStdString().c_str()); + if (n > 0) { + kshark_register_plugin(kshark_ctx, lib); + _registeredKsPlugins[i] = true; + free(lib); + } + + return; + } + } + + /* No plugin with this name in the list. Try to add it anyway. */ + if (plugin.endsWith(".so") && QFileInfo::exists(plugin)) { + kshark_register_plugin(kshark_ctx, + plugin.toStdString().c_str()); + + _userPluginList.append(plugin); + _registeredUserPlugins.append(true); + } else { + qCritical() << "ERROR: " << plugin << "cannot be registered!"; + } +} + +/** @brief Unregister a Built in KernelShark plugin. + *
+ * WARNING: Do not use this function to unregister User plugins. + * Instead use directly kshark_unregister_plugin(). + * + * @param plugin: provide here the name of the plugin (as in the CMake-generated + * header file) or a name of the plugin's library file (.so). + * + */ +void KsPluginManager::unregisterPlugin(const QString &plugin) +{ + kshark_context *kshark_ctx(nullptr); + char *lib; + int n; + + if (!kshark_instance(&kshark_ctx)) + return; + + for (int i = 0; i < _ksPluginList.count(); ++i) { + if (_ksPluginList[i] == plugin) { + /* + * The argument is the name of the plugin. From the + * name get the library .so file. + */ + n = asprintf(&lib, "%s/lib/plugin-%s.so", KS_DIR, + plugin.toStdString().c_str()); + if (n > 0) { + kshark_unregister_plugin(kshark_ctx, lib); + _registeredKsPlugins[i] = false; + free(lib); + } + + return; + } else if (plugin.contains("/lib/plugin-" + + _ksPluginList[i], Qt::CaseInsensitive)) { + /* + * The argument is the name of the library .so file. + */ + n = asprintf(&lib, "%s", plugin.toStdString().c_str()); + if (n > 0) { + kshark_unregister_plugin(kshark_ctx, lib); + _registeredKsPlugins[i] = false; + free(lib); + } + + return; + } + } +} + +/** Unload all plugins. */ +void KsPluginManager::unloadAll() +{ + kshark_context *kshark_ctx(nullptr); + + if (!kshark_instance(&kshark_ctx)) + return; + + kshark_handle_plugins(kshark_ctx, KSHARK_PLUGIN_CLOSE); + kshark_free_plugin_list(kshark_ctx->plugins); + kshark_ctx->plugins = nullptr; + kshark_free_event_handler_list(kshark_ctx->event_handlers); + + unregisterFromList(kshark_ctx); +} + +/** @brief Update (change) the Plugins. + * + * @param pluginIds: The indexes of the plugins to be loaded. + */ +void KsPluginManager::updatePlugins(QVector pluginIds) +{ + kshark_context *kshark_ctx(nullptr); + + if (!kshark_instance(&kshark_ctx)) + return; + + auto register_plugins = [&] (QVector ids) + { + int nKsPlugins = _registeredKsPlugins.count(); + + /* First clear all registered plugins. */ + for (auto &p: _registeredKsPlugins) + p = false; + for (auto &p: _registeredUserPlugins) + p = false; + + /* The vector contains the indexes of those to register. */ + for (auto const &p: ids) { + if (p < nKsPlugins) + _registeredKsPlugins[p] = true; + else + _registeredUserPlugins[p - nKsPlugins] = true; + } + registerFromList(kshark_ctx); + }; + + if (!kshark_ctx->pevent) { + kshark_free_plugin_list(kshark_ctx->plugins); + kshark_ctx->plugins = nullptr; + + /* + * No data is loaded. For the moment, just register the + * plugins. Handling of the plugins will be done after + * we load a data file. + */ + register_plugins(pluginIds); + return; + } + + /* Clean up all old plugins first. */ + unloadAll(); + + /* Now load. */ + register_plugins(pluginIds); + kshark_handle_plugins(kshark_ctx, KSHARK_PLUGIN_INIT); + + emit dataReload(); +} diff --git a/kernel-shark-qt/src/KsUtils.hpp b/kernel-shark-qt/src/KsUtils.hpp new file mode 100644 index 0000000..b14cd6a --- /dev/null +++ b/kernel-shark-qt/src/KsUtils.hpp @@ -0,0 +1,233 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsUtils.hpp + * @brief KernelShark Utils. + */ + +#ifndef _KS_UTILS_H +#define _KS_UTILS_H + +// C++ 11 +#include + +// Qt +#include + +// KernelShark +#include "libkshark.h" +#include "libkshark-model.h" +#include "KsCmakeDef.hpp" +#include "KsPlotTools.hpp" + +/** Macro providing the height of the screen in pixels. */ +#define SCREEN_HEIGHT QApplication::desktop()->screenGeometry().height() + +/** Macro providing the width of the screen in pixels. */ +#define SCREEN_WIDTH QApplication::desktop()->screenGeometry().width() + +//! @cond Doxygen_Suppress + +auto fontHeight = []() +{ + QFont font; + QFontMetrics fm(font); + + return fm.height(); +}; + +auto stringWidth = [](QString s) +{ + QFont font; + QFontMetrics fm(font); + + return fm.width(s); +}; + +//! @endcond + +/** Macro providing the height of the font in pixels. */ +#define FONT_HEIGHT fontHeight() + +/** Macro providing the width of the font in pixels. */ +#define FONT_WIDTH stringWidth("4") + +/** Macro providing the width of a string in pixels. */ +#define STRING_WIDTH(s) stringWidth(s) + +/** Macro providing the height of the KernelShark graphs in pixels. */ +#define KS_GRAPH_HEIGHT (FONT_HEIGHT*2) + +//! @cond Doxygen_Suppress + +#define KS_JSON_CAST(doc) \ +reinterpret_cast(doc) + +#define KS_C_STR_CAST(doc) \ +reinterpret_cast(doc) + +typedef std::chrono::high_resolution_clock::time_point hd_time; + +#define GET_TIME std::chrono::high_resolution_clock::now() + +#define GET_DURATION(t0) \ +std::chrono::duration_cast>( \ +std::chrono::high_resolution_clock::now() - t0).count() + +//! @endcond + +namespace KsUtils { + +QVector getPidList(); + +/** @brief Geat the list of plugins. */ +inline QStringList getPluginList() {return plugins.split(";");} + +void listFilterSync(bool state); + +void graphFilterSync(bool state); + +/** @brief Convert the timestamp of the trace record into a string showing + * the time in seconds. + * + * @param ts: Input location for the timestamp. + * @param prec: the number of digits after the decimal point in the return + * string. + * + * @returns String showing the time in seconds. + */ +inline QString Ts2String(int64_t ts, int prec) +{ + return QString::number(ts * 1e-9, 'f', prec); +} + +bool matchCPUVisible(struct kshark_context *kshark_ctx, + struct kshark_entry *e, int cpu); +}; // KsUtils + +/** Identifier of the Dual Marker active state. */ +enum class DualMarkerState { + A, + B +}; + +/** + * The KsDataStore class provides the access to trace data for all KernelShark + * widgets. + */ +class KsDataStore : public QObject +{ + Q_OBJECT +public: + explicit KsDataStore(QWidget *parent = nullptr); + + ~KsDataStore(); + + void loadDataFile(const QString &file); + + void clear(); + + /** Get the trace event parser. */ + tep_handle *tep() const {return _tep;} + + /** Get the trace data array.. */ + struct kshark_entry **rows() const {return _rows;} + + /** Get the size of the data array. */ + size_t size() const {return _dataSize;} + + void reload(); + + void update(); + + void registerCPUCollections(); + + void applyPosTaskFilter(QVector); + + void applyNegTaskFilter(QVector); + + void applyPosEventFilter(QVector); + + void applyNegEventFilter(QVector); + + void clearAllFilters(); + +signals: + /** + * This signal is emitted when the data has changed and the View + * widgets have to update. + */ + void updateWidgets(KsDataStore *); + +private: + /** Page event used to parse the page. */ + tep_handle *_tep; + + /** Trace data array. */ + struct kshark_entry **_rows; + + /** The size of the data array. */ + size_t _dataSize; + + void _freeData(); + void _unregisterCPUCollections(); + void _applyIdFilter(int filterId, QVector vec); +}; + +/** A Plugin Manage class. */ +class KsPluginManager : public QObject +{ + Q_OBJECT +public: + explicit KsPluginManager(QWidget *parent = nullptr); + + /** A list of available built-in plugins. */ + QStringList _ksPluginList; + + /** A list of registered built-in plugins. */ + QVector _registeredKsPlugins; + + /** A list of available user plugins. */ + QStringList _userPluginList; + + /** A list of registered user plugins. */ + QVector _registeredUserPlugins; + + void registerFromList(kshark_context *kshark_ctx); + void unregisterFromList(kshark_context *kshark_ctx); + + void registerPlugin(const QString &plugin); + void unregisterPlugin(const QString &plugin); + void unloadAll(); + + void updatePlugins(QVector pluginId); + +signals: + /** This signal is emitted when a plugin is loaded or unloaded. */ + void dataReload(); + +private: + void _parsePluginList(); + + template + void _forEachInList(const QStringList &pl, + const QVector ®, + T action) + { + int nPlugins; + nPlugins = pl.count(); + for (int i = 0; i < nPlugins; ++i) { + if (reg[i]) { + action(pl[i]); + } + } + } +}; + +KsPlot::Color& operator <<(KsPlot::Color &thisColor, const QColor &c); + +#endif From patchwork Wed Oct 10 20:10:14 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759519 Return-Path: Received: from mail-dm3nam03on0085.outbound.protection.outlook.com ([104.47.41.85]:42585 "EHLO NAM03-DM3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727351AbeJKDfD (ORCPT ); Wed, 10 Oct 2018 23:35:03 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH v2 3/4] kernel-shark-qt: Add Widgets Lib Date: Wed, 10 Oct 2018 23:10:14 +0300 Message-Id: <20181010201015.23824-4-ykaradzhov@vmware.com> In-Reply-To: <20181010201015.23824-1-ykaradzhov@vmware.com> References: <20181010201015.23824-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 31362 From: Yordan Karadzhov (VMware) This patch defines various small widgets and dialogues to be used by the KernelShark GUI. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 6 +- kernel-shark-qt/src/KsWidgetsLib.cpp | 878 +++++++++++++++++++++++++++ kernel-shark-qt/src/KsWidgetsLib.hpp | 385 ++++++++++++ 3 files changed, 1267 insertions(+), 2 deletions(-) create mode 100644 kernel-shark-qt/src/KsWidgetsLib.cpp create mode 100644 kernel-shark-qt/src/KsWidgetsLib.hpp diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index e897e9a..2ac79ca 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -31,11 +31,13 @@ endif (OPENGL_FOUND AND GLUT_FOUND) if (Qt5Widgets_FOUND AND Qt5Network_FOUND) message(STATUS "libkshark-gui") - set (ks-guiLib_hdr KsUtils.hpp) + set (ks-guiLib_hdr KsUtils.hpp + KsWidgetsLib.hpp) QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr}) - add_library(kshark-gui SHARED ${ks-guiLib_hdr_moc} KsUtils.cpp) + add_library(kshark-gui SHARED ${ks-guiLib_hdr_moc} KsUtils.cpp + KsWidgetsLib.cpp) target_link_libraries(kshark-gui kshark-plot ${CMAKE_DL_LIBS} diff --git a/kernel-shark-qt/src/KsWidgetsLib.cpp b/kernel-shark-qt/src/KsWidgetsLib.cpp new file mode 100644 index 0000000..905823a --- /dev/null +++ b/kernel-shark-qt/src/KsWidgetsLib.cpp @@ -0,0 +1,878 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +/** + * @file KsWidgetsLib.cpp + * @brief Defines small widgets and dialogues used by the KernelShark GUI. + */ + +// KernelShark +#include "libkshark.h" +#include "KsUtils.hpp" +#include "KsCmakeDef.hpp" +#include "KsPlotTools.hpp" +#include "KsWidgetsLib.hpp" + +/** + * @brief Create KsProgressBar. + * + * @param message: Text to be shown. + * @param parent: The parent of this widget. + */ +KsProgressBar::KsProgressBar(QString message, QWidget *parent) +: QWidget(parent), + _sb(this), + _pb(&_sb) { + resize(KS_BROGBAR_WIDTH, KS_BROGBAR_HEIGHT); + setWindowTitle("KernelShark"); + setLayout(new QVBoxLayout); + + _pb.setOrientation(Qt::Horizontal); + _pb.setTextVisible(false); + _pb.setRange(0, KS_PROGRESS_BAR_MAX); + _pb.setValue(1); + + _sb.addPermanentWidget(&_pb, 1); + + layout()->addWidget(new QLabel(message)); + layout()->addWidget(&_sb); + + setWindowFlags(Qt::WindowStaysOnTopHint); + + show(); +} + +/** @brief Set the state of the progressbar. + * + * @param i: A value ranging from 0 to KS_PROGRESS_BAR_MAX. + */ +void KsProgressBar::setValue(int i) { + _pb.setValue(i); + QApplication::processEvents(); +} + +/** + * @brief Create KsMessageDialog. + * + * @param message: Text to be shown. + * @param parent: The parent of this widget. + */ +KsMessageDialog::KsMessageDialog(QString message, QWidget *parent) +: QDialog(parent), + _text(message, this), + _closeButton("Close", this) +{ + resize(KS_MSG_DIALOG_WIDTH, KS_MSG_DIALOG_HEIGHT); + + _layout.addWidget(&_text); + _layout.addWidget(&_closeButton); + + connect(&_closeButton, &QPushButton::pressed, + this, &QWidget::close); + + this->setLayout(&_layout); +} + +/** + * @brief Create KsCheckBoxWidget. + * + * @param name: The name of this widget. + * @param parent: The parent of this widget. + */ +KsCheckBoxWidget::KsCheckBoxWidget(const QString &name, QWidget *parent) +: QWidget(parent), + _tb(this), + _allCb("all", &_tb), + _cbWidget(this), + _cbLayout(&_cbWidget), + _topLayout(this), + _name(name), + _nameLabel(name + ": ",&_tb) +{ + setWindowTitle(_name); + setMinimumHeight(SCREEN_HEIGHT / 2); + + connect(&_allCb, &QCheckBox::clicked, + this, &KsCheckBoxWidget::_checkAll); + + _cbWidget.setLayout(&_cbLayout); + + _tb.addWidget(&_nameLabel); + _tb.addWidget(&_allCb); + _topLayout.addWidget(&_tb); + + _topLayout.addWidget(&_cbWidget); + _topLayout.setContentsMargins(0, 0, 0, 0); + + setLayout(&_topLayout); + _allCb.setCheckState(Qt::Checked); +} + +/** + * Set the default state for all checkboxes (including the "all" checkbox). + */ +void KsCheckBoxWidget::setDefault(bool st) +{ + Qt::CheckState state = Qt::Unchecked; + + if (st) + state = Qt::Checked; + + _allCb.setCheckState(state); + _checkAll(state); +} + +/** Get a vector containing the indexes of all checked boxes. */ +QVector KsCheckBoxWidget::getCheckedIds() +{ + QVector vec; + int n = _id.size(); + + for (int i = 0; i < n; ++i) + if (_checkState(i) == Qt::Checked) + vec.append(_id[i]); + + return vec; +} + +/** + * @brief Set the state of the checkboxes. + * + * @param v: Vector containing the bool values for all checkboxes. + */ +void KsCheckBoxWidget::set(QVector v) +{ + Qt::CheckState state; + int nChecks; + + nChecks = (v.size() < _id.size()) ? v.size() : _id.size(); + + /* Start with the "all" checkbox being checked. */ + _allCb.setCheckState(Qt::Checked); + for (int i = 0; i < nChecks; ++i) { + if (v[i]) { + state = Qt::Checked; + } else { + /* + * At least one checkbox is unchecked. Uncheck + * "all" as well. + */ + state = Qt::Unchecked; + _allCb.setCheckState(state); + } + + _setCheckState(i, state); + } + _verify(); +} + +void KsCheckBoxWidget::_checkAll(bool st) +{ + Qt::CheckState state = Qt::Unchecked; + int n = _id.size(); + + if (st) state = Qt::Checked; + + for (int i = 0; i < n; ++i) { + _setCheckState(i, state); + } + + _verify(); +} + +/** + * @brief Create KsCheckBoxDialog. + * + * @param cbw: A KsCheckBoxWidget to be nested in this dialog. + * @param parent: The parent of this widget. + */ +KsCheckBoxDialog::KsCheckBoxDialog(KsCheckBoxWidget *cbw, QWidget *parent) +: QDialog(parent), _checkBoxWidget(cbw), + _applyButton("Apply", this), + _cancelButton("Cancel", this) +{ + int buttonWidth; + + setWindowTitle(cbw->name()); + _topLayout.addWidget(_checkBoxWidget); + + buttonWidth = STRING_WIDTH("--Cancel--"); + _applyButton.setFixedWidth(buttonWidth); + _cancelButton.setFixedWidth(buttonWidth); + + _buttonLayout.addWidget(&_applyButton); + _applyButton.setAutoDefault(false); + + _buttonLayout.addWidget(&_cancelButton); + _cancelButton.setAutoDefault(false); + + _buttonLayout.setAlignment(Qt::AlignLeft); + _topLayout.addLayout(&_buttonLayout); + + _applyButtonConnection = + connect(&_applyButton, &QPushButton::pressed, + this, &KsCheckBoxDialog::_applyPress); + + connect(&_applyButton, &QPushButton::pressed, + this, &QWidget::close); + + connect(&_cancelButton, &QPushButton::pressed, + this, &QWidget::close); + + this->setLayout(&_topLayout); +} + +void KsCheckBoxDialog::_applyPress() +{ + QVector vec = _checkBoxWidget->getCheckedIds(); + emit apply(vec); + + /* + * Disconnect _applyButton. This is done in order to protect + * against multiple clicks. + */ + disconnect(_applyButtonConnection); +} + + +/** + * @brief Create KsCheckBoxTable. + * + * @param parent: The parent of this widget. + */ +KsCheckBoxTable::KsCheckBoxTable(QWidget *parent) +: QTableWidget(parent) +{ + setShowGrid(false); + horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); + horizontalHeader()->setStretchLastSection(true); + setSelectionBehavior(QAbstractItemView::SelectRows); + setEditTriggers(QAbstractItemView::NoEditTriggers); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + verticalHeader()->setVisible(false); + + connect(this, &QTableWidget::cellDoubleClicked, + this, &KsCheckBoxTable::_doubleClicked); +} + +/** + * @brief Initialize the table. + * + * @param headers: The headers of the individual columns. + * @param size: The number of rows. + */ +void KsCheckBoxTable::init(QStringList headers, int size) +{ + QHBoxLayout *cbLayout; + QWidget *cbWidget; + + setColumnCount(headers.count()); + setRowCount(size); + setHorizontalHeaderLabels(headers); + + _cb.resize(size); + + for (int i = 0; i < size; ++i) { + cbWidget = new QWidget(); + _cb[i] = new QCheckBox(cbWidget); + cbLayout = new QHBoxLayout(cbWidget); + + cbLayout->addWidget(_cb[i]); + cbLayout->setAlignment(Qt::AlignCenter); + cbLayout->setContentsMargins(0, 0, 0, 0); + + cbWidget->setLayout(cbLayout); + setCellWidget(i, 0, cbWidget); + } +} + +/** Reimplemented event handler used to receive key press events. */ +void KsCheckBoxTable::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Return) { + for (auto &s: selectedItems()) { + if (s->column() == 1) + emit changeState(s->row()); + } + } + + QApplication::processEvents(); + QTableWidget::keyPressEvent(event); +} + +/** Reimplemented event handler used to receive mouse press events. */ +void KsCheckBoxTable::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::RightButton) { + for (auto &i: selectedItems()) + i->setSelected(false); + + return; + } + + QApplication::processEvents(); + QTableWidget::mousePressEvent(event); +} + +void KsCheckBoxTable::_doubleClicked(int row, int col) +{ + emit changeState(row); + for (auto &i: selectedItems()) + i->setSelected(false); +} + +/** + * @brief Create KsCheckBoxTableWidget. + * + * @param name: The name of this widget. + * @param parent: The parent of this widget. + */ +KsCheckBoxTableWidget::KsCheckBoxTableWidget(const QString &name, + QWidget *parent) +: KsCheckBoxWidget(name, parent), + _table(this) +{ + connect(&_table, &KsCheckBoxTable::changeState, + this, &KsCheckBoxTableWidget::_changeState); +} + +/** Initialize the KsCheckBoxTable and its layout. */ +void KsCheckBoxTableWidget::_initTable(QStringList headers, int size) +{ + _table.init(headers, size); + + for (auto const & cb: _table._cb) { + connect(cb, &QCheckBox::clicked, + this, &KsCheckBoxTableWidget::_update); + } + + _cbLayout.setContentsMargins(1, 1, 1, 1); + _cbLayout.addWidget(&_table); +} + +/** Adjust the size of this widget according to its content. */ +void KsCheckBoxTableWidget::_adjustSize() +{ + int width; + + _table.setVisible(false); + _table.resizeColumnsToContents(); + _table.setVisible(true); + + width = _table.horizontalHeader()->length() + + FONT_WIDTH * 3 + + style()->pixelMetric(QStyle::PM_ScrollBarExtent); + + _cbWidget.resize(width, _cbWidget.height()); + + setMinimumWidth(_cbWidget.width() + + _cbLayout.contentsMargins().left() + + _cbLayout.contentsMargins().right() + + _topLayout.contentsMargins().left() + + _topLayout.contentsMargins().right()); +} + +void KsCheckBoxTableWidget::_update(bool state) +{ + /* If a Checkbox is being unchecked. Unchecked "all" as well. */ + if (!state) + _allCb.setCheckState(Qt::Unchecked); +} + +void KsCheckBoxTableWidget::_changeState(int row) +{ + if (_table._cb[row]->checkState() == Qt::Checked) + _table._cb[row]->setCheckState(Qt::Unchecked); + else + _table._cb[row]->setCheckState(Qt::Checked); + + _allCb.setCheckState(Qt::Checked); + for (auto &c: _table._cb) { + if (c->checkState() == Qt::Unchecked) { + _allCb.setCheckState(Qt::Unchecked); + break; + } + } +} + +static void update_r(QTreeWidgetItem *item, Qt::CheckState state) +{ + int n; + + item->setCheckState(0, state); + + n = item->childCount(); + for (int i = 0; i < n; ++i) + update_r(item->child(i), state); +} + +/** + * @brief Create KsCheckBoxTree. + * + * @param parent: The parent of this widget. + */ +KsCheckBoxTree::KsCheckBoxTree(QWidget *parent) +: QTreeWidget(parent) +{ + setColumnCount(2); + setHeaderHidden(true); + setSelectionBehavior(QAbstractItemView::SelectRows); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + connect(this, &KsCheckBoxTree::itemDoubleClicked, + this, &KsCheckBoxTree::_doubleClicked); +} + +/** Reimplemented event handler used to receive key press events. */ +void KsCheckBoxTree::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Return) { + /* Loop over all selected child items and change + * there states. */ + for (auto &s: selectedItems()) { + if(s->childCount()) { + if (s->isExpanded()) + continue; + } + + if (s->checkState(0) == Qt::Unchecked) + s->setCheckState(0, Qt::Checked); + else + s->setCheckState(0, Qt::Unchecked); + + if(s->childCount()) { + update_r(s, s->checkState(0)); + } + } + } + + emit verify(); + QTreeWidget::keyPressEvent(event); +} + +void KsCheckBoxTree::_doubleClicked(QTreeWidgetItem *item, int col) +{ + if (item->checkState(0) == Qt::Unchecked) + item->setCheckState(0, Qt::Checked); + else + item->setCheckState(0, Qt::Unchecked); + + for (auto &i: selectedItems()) + i->setSelected(false); + + emit itemClicked(item, col); +} + +/** Reimplemented event handler used to receive mouse press events. */ +void KsCheckBoxTree::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::RightButton) { + for (auto &i: selectedItems()) + i->setSelected(false); + return; + } + + QApplication::processEvents(); + QTreeWidget::mousePressEvent(event); +} + +/** + * @brief Create KsCheckBoxTreeWidget. + * + * @param name: The name of this widget. + * @param parent: The parent of this widget. + */ +KsCheckBoxTreeWidget::KsCheckBoxTreeWidget(const QString &name, + QWidget *parent) +: KsCheckBoxWidget(name, parent), + _tree(this) +{ + connect(&_tree, &KsCheckBoxTree::verify, + this, &KsCheckBoxTreeWidget::_verify); +} + +/** Initialize the KsCheckBoxTree and its layout. */ +void KsCheckBoxTreeWidget::_initTree() +{ + _tree.setSelectionMode(QAbstractItemView::MultiSelection); + + connect(&_tree, &QTreeWidget::itemClicked, + this, &KsCheckBoxTreeWidget::_update); + + _cbLayout.setContentsMargins(1, 1, 1, 1); + _cbLayout.addWidget(&_tree); +} + +/** Adjust the size of this widget according to its content. */ +void KsCheckBoxTreeWidget::_adjustSize() +{ + int width, n = _tree.topLevelItemCount(); + + if (n == 0) + return; + + for (int i = 0; i < n; ++i) + _tree.topLevelItem(i)->setExpanded(true); + + _tree.resizeColumnToContents(0); + if (_tree.topLevelItem(0)->child(0)) { + width = _tree.visualItemRect(_tree.topLevelItem(0)->child(0)).width(); + } else { + width = _tree.visualItemRect(_tree.topLevelItem(0)).width(); + } + + width += FONT_WIDTH*3 + style()->pixelMetric(QStyle::PM_ScrollBarExtent); + _cbWidget.resize(width, _cbWidget.height()); + + for (int i = 0; i < n; ++i) + _tree.topLevelItem(i)->setExpanded(false); + + setMinimumWidth(_cbWidget.width() + + _cbLayout.contentsMargins().left() + + _cbLayout.contentsMargins().right() + + _topLayout.contentsMargins().left() + + _topLayout.contentsMargins().right()); +} + +void KsCheckBoxTreeWidget::_update(QTreeWidgetItem *item, int column) +{ + /* Get the new state of the item. */ + Qt::CheckState state = item->checkState(0); + + /* Recursively update all items below this one. */ + update_r(item, state); + + /* + * Update all items above this one including the "all" + * check box. + */ + _verify(); +} + +void KsCheckBoxTreeWidget::_verify() +{ + /* + * Set the state of the top level items according to the + * state of the childs. + */ + QTreeWidgetItem *topItem, *childItem; + + for(int t = 0; t < _tree.topLevelItemCount(); ++t) { + topItem = _tree.topLevelItem(t); + if (topItem->childCount() == 0) + continue; + + topItem->setCheckState(0, Qt::Checked); + for (int c = 0; c < topItem->childCount(); ++c) { + childItem = topItem->child(c); + if (childItem->checkState(0) == Qt::Unchecked) + topItem->setCheckState(0, Qt::Unchecked); + } + } + + _allCb.setCheckState(Qt::Checked); + for (auto &c: _cb) { + if (c->checkState(0) == Qt::Unchecked) { + _allCb.setCheckState(Qt::Unchecked); + break; + } + } +} + +/** + * @brief Create KsCPUCheckBoxWidget. + * + * @param pevent: Page event used to parse the page. + * @param parent: The parent of this widget. + */ +KsCPUCheckBoxWidget::KsCPUCheckBoxWidget(struct tep_handle *pevent, + QWidget *parent) +: KsCheckBoxTreeWidget("CPUs", parent) +{ + int nCPUs(0), height(FONT_HEIGHT * 1.5); + KsPlot::Color cpuCol; + QString style; + + style = QString("QTreeView::item { height: %1 ;}").arg(height); + _tree.setStyleSheet(style); + + _initTree(); + + if (pevent) + nCPUs = pevent->cpus; + + _id.resize(nCPUs); + _cb.resize(nCPUs); + + for (int i = 0; i < nCPUs; ++i) { + cpuCol.setRainbowColor(i); + QTreeWidgetItem *cpuItem = new QTreeWidgetItem; + cpuItem->setText(0, " "); + cpuItem->setText(1, QString("CPU %1").arg(i)); + cpuItem->setCheckState(0, Qt::Checked); + cpuItem->setBackgroundColor(0, QColor(cpuCol.r(), + cpuCol.g(), + cpuCol.b())); + _tree.addTopLevelItem(cpuItem); + _id[i] = i; + _cb[i] = cpuItem; + } + + _adjustSize(); +} + +/** + * @brief Create KsEventsCheckBoxWidget. + * + * @param pevent: Page event used to parse the page. + * @param parent: The parent of this widget. + */ +KsEventsCheckBoxWidget::KsEventsCheckBoxWidget(struct tep_handle *pevent, + QWidget *parent) +: KsCheckBoxTreeWidget("Events", parent) +{ + QTreeWidgetItem *sysItem, *evtItem; + QString sysName, evtName; + int nEvts(0), i(0); + + if (pevent) + nEvts = pevent->nr_events; + + _initTree(); + _id.resize(nEvts); + _cb.resize(nEvts); + + while (i < nEvts) { + sysName = pevent->events[i]->system; + sysItem = new QTreeWidgetItem; + sysItem->setText(0, sysName); + sysItem->setCheckState(0, Qt::Checked); + _tree.addTopLevelItem(sysItem); + + while (sysName == pevent->events[i]->system) { + evtName = pevent->events[i]->name; + evtItem = new QTreeWidgetItem; + evtItem->setText(0, evtName); + evtItem->setCheckState(0, Qt::Checked); + evtItem->setFlags(evtItem->flags() | + Qt::ItemIsUserCheckable); + + sysItem->addChild(evtItem); + + _id[i] = pevent->events[i]->id; + _cb[i] = evtItem; + + if (++i == nEvts) + break; + } + } + + _tree.sortItems(0, Qt::AscendingOrder); + _adjustSize(); +} + +/** + * @brief Create KsTasksCheckBoxWidget. + * + * @param pevent: Page event used to parse the page. + * @param cond: If True make a "Show Task" widget. Otherwise make "Hide Task". + * @param parent: The parent of this widget. + */ +KsTasksCheckBoxWidget::KsTasksCheckBoxWidget(struct tep_handle *pevent, + bool cond, QWidget *parent) +: KsCheckBoxTableWidget("Tasks", parent) +{ + kshark_context *kshark_ctx(nullptr); + QTableWidgetItem *pidItem, *comItem; + KsPlot::Color pidCol; + QStringList headers; + const char *comm; + int nTasks; + + if (!kshark_instance(&kshark_ctx)) + return; + + if (_cond) + headers << "Show" << "Pid" << "Task"; + else + headers << "Hide" << "Pid" << "Task"; + + _id = KsUtils::getPidList(); + nTasks = _id.count(); + _initTable(headers, nTasks); + + for (int i = 0; i < nTasks; ++i) { + pidItem = new QTableWidgetItem(tr("%1").arg(_id[i])); + _table.setItem(i, 1, pidItem); + + comm = tep_data_comm_from_pid(kshark_ctx->pevent, _id[i]); + comItem = new QTableWidgetItem(tr(comm)); + + pidItem->setBackgroundColor(QColor(pidCol.r(), + pidCol.g(), + pidCol.b())); + + if (_id[i] == 0) + pidItem->setTextColor(Qt::white); + + _table.setItem(i, 2, comItem); + + pidCol.setRainbowColor(i); + } + + _adjustSize(); +} + +/** + * @brief Create KsPluginCheckBoxWidget. + * + * @param pluginList: A list of plugin names. + * @param parent: The parent of this widget. + */ +KsPluginCheckBoxWidget::KsPluginCheckBoxWidget(QStringList pluginList, + QWidget *parent) +: KsCheckBoxTableWidget("Plugins", parent) +{ + QTableWidgetItem *nameItem, *infoItem; + QStringList headers; + int nPlgins; + + headers << "Load" << "Name" << "Info"; + + nPlgins = pluginList.count(); + _initTable(headers, nPlgins); + _id.resize(nPlgins); + + for (int i = 0; i < nPlgins; ++i) { + nameItem = new QTableWidgetItem(pluginList[i]); + _table.setItem(i, 1, nameItem); + infoItem = new QTableWidgetItem(" -- "); + _table.setItem(i, 2, infoItem); + _id[i] = i; + } + + _adjustSize(); +} + +/** + * @brief Create KsQuickEntryMenu. + * + * @param data: Input location for the KsDataStore object. + * @param row: The index of the entry used to initialize the menu. + * @param parent: The parent of this widget. + */ +KsQuickEntryMenu::KsQuickEntryMenu(KsDataStore *data, size_t row, + QWidget *parent) +: QMenu("Entry menu", parent), + _data(data), + _row(row), + _hideTaskAction(this), + _showTaskAction(this), + _hideEventAction(this), + _showEventAction(this), + _addTaskPlotAction(this) +{ + QString descr; + + addSection("Quick Filter menu"); + + descr = "Hide task ["; + descr += kshark_get_task_easy(_data->rows()[_row]); + descr += "-"; + descr += QString("%1").arg(_data->rows()[_row]->pid); + descr += "]"; + + _hideTaskAction.setText(descr); + + connect(&_hideTaskAction, &QAction::triggered, + this, &KsQuickEntryMenu::_hideTask); + + addAction(&_hideTaskAction); + + descr = "Show task ["; + descr += kshark_get_task_easy(_data->rows()[_row]); + descr += "-"; + descr += QString("%1").arg(_data->rows()[_row]->pid); + descr += "] only"; + + _showTaskAction.setText(descr); + + connect(&_showTaskAction, &QAction::triggered, + this, &KsQuickEntryMenu::_showTask); + + addAction(&_showTaskAction); + + descr = "Hide event ["; + descr += kshark_get_event_name_easy(_data->rows()[_row]); + descr += "]"; + + _hideEventAction.setText(descr); + + connect(&_hideEventAction, &QAction::triggered, + this, &KsQuickEntryMenu::_hideEvent); + + addAction(&_hideEventAction); + + descr = "Show event ["; + descr += kshark_get_event_name_easy(_data->rows()[_row]); + descr += "] only"; + + _showEventAction.setText(descr); + + connect(&_showEventAction, &QAction::triggered, + this, &KsQuickEntryMenu::_showEvent); + + addAction(&_showEventAction); + + addSection("Quick Plot menu"); + descr = "Add ["; + descr += kshark_get_task_easy(_data->rows()[_row]); + descr += "-"; + descr += QString("%1").arg(_data->rows()[_row]->pid); + descr += "] plot"; + + _addTaskPlotAction.setText(descr); + + connect(&_addTaskPlotAction, &QAction::triggered, + this, &KsQuickEntryMenu::_addTaskPlot); + + addAction(&_addTaskPlotAction); +} + +void KsQuickEntryMenu::_hideTask() +{ + int pid = kshark_get_pid_easy(_data->rows()[_row]); + + _data->applyNegTaskFilter(QVector(1, pid)); +} + +void KsQuickEntryMenu::_showTask() +{ + int pid = kshark_get_pid_easy(_data->rows()[_row]); + + _data->applyPosTaskFilter(QVector(1, pid)); +} + +void KsQuickEntryMenu::_hideEvent() +{ + int eventId = kshark_get_event_id_easy(_data->rows()[_row]); + + _data->applyNegEventFilter(QVector(1, eventId)); +} + +void KsQuickEntryMenu::_showEvent() +{ + int eventId = kshark_get_event_id_easy(_data->rows()[_row]); + + _data->applyPosEventFilter(QVector(1, eventId)); +} + +void KsQuickEntryMenu::_addTaskPlot() +{ + int pid = kshark_get_pid_easy(_data->rows()[_row]); + + emit plotTask(pid); +} diff --git a/kernel-shark-qt/src/KsWidgetsLib.hpp b/kernel-shark-qt/src/KsWidgetsLib.hpp new file mode 100644 index 0000000..b9ba35a --- /dev/null +++ b/kernel-shark-qt/src/KsWidgetsLib.hpp @@ -0,0 +1,385 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + + /** + * @file KsWidgetsLib.hpp + * @brief Defines small widgets and dialogues used by the KernelShark GUI. + */ + +#ifndef _KS_WIDGETS_LIB_H +#define _KS_WIDGETS_LIB_H + +// Qt +#include + +/** + * The KsProgressBar class provides a visualization of the progress of a + * running job. + */ +class KsProgressBar : public QWidget +{ + Q_OBJECT + + QStatusBar _sb; + + QProgressBar _pb; + +public: + KsProgressBar(QString message, QWidget *parent = nullptr); + + void setValue(int i); +}; + +/** Defines the progress bar's maximum value. */ +#define KS_PROGRESS_BAR_MAX 200 + +/** The height of the KsProgressBar widget. */ +#define KS_BROGBAR_HEIGHT (FONT_HEIGHT * 5) + +/** The width of the KsProgressBar widget. */ +#define KS_BROGBAR_WIDTH (FONT_WIDTH * 50) + +/** + * The KsMessageDialog class provides a widget showing a message and having + * a "Close" button. + */ +class KsMessageDialog : public QDialog +{ + QVBoxLayout _layout; + + QLabel _text; + + QPushButton _closeButton; + +public: + explicit KsMessageDialog(QWidget *parent) = delete; + + KsMessageDialog(QString message, QWidget *parent = nullptr); +}; + +/** The height of the KsMessageDialog widget. */ +#define KS_MSG_DIALOG_HEIGHT (FONT_HEIGHT * 8) + +/** The width of the KsMessageDialog widget. */ +#define KS_MSG_DIALOG_WIDTH (SCREEN_WIDTH / 10) + +/** + * The KsCheckBoxWidget class is the base class of all CheckBox widget used + * by KernelShark. + */ +class KsCheckBoxWidget : public QWidget +{ + Q_OBJECT +public: + KsCheckBoxWidget(const QString &name = "", + QWidget *parent = nullptr); + + /** Get the name of the widget. */ + QString name() const {return _name;} + + /** Get the state of the "all" checkboxe. */ + bool all() const + { + if(_allCb.checkState() == Qt::Checked) + return true; + return false; + } + + void setDefault(bool); + + void set(QVector v); + + QVector getCheckedIds(); + +private: + QToolBar _tb; + +protected: + /** The "all" checkboxe. */ + QCheckBox _allCb; + + /** A vector of Id numbers coupled to each checkboxe. */ + QVector _id; + + /** A nested widget used to position the checkboxes. */ + QWidget _cbWidget; + + /** The layout of the nested widget. */ + QVBoxLayout _cbLayout; + + /** The top level layout of this widget. */ + QVBoxLayout _topLayout; + + /** The name of this widget. */ + QString _name; + + /** A label to show the name of the widget. */ + QLabel _nameLabel; + +private: + virtual void _setCheckState(int i, Qt::CheckState st) = 0; + + virtual Qt::CheckState _checkState(int i) const = 0; + + virtual void _verify() {}; + + void _checkAll(bool); +}; + +/** + * The KsCheckBoxDialog class is the base class of all CheckBox dialogs + * used by KernelShark. + */ +class KsCheckBoxDialog : public QDialog +{ + Q_OBJECT +public: + KsCheckBoxDialog() = delete; + + KsCheckBoxDialog(KsCheckBoxWidget *cbw, QWidget *parent = nullptr); + +signals: + /** Signal emitted when the "Apply" button is pressed. */ + void apply(QVector); + +private: + void _applyPress(); + + QVBoxLayout _topLayout; + + QHBoxLayout _buttonLayout; + + KsCheckBoxWidget *_checkBoxWidget; + + QPushButton _applyButton, _cancelButton; + + QMetaObject::Connection _applyButtonConnection; +}; + +/** The KsCheckBoxTable class provides a table of checkboxes. */ +class KsCheckBoxTable : public QTableWidget +{ + Q_OBJECT +public: + explicit KsCheckBoxTable(QWidget *parent = nullptr); + + void init(QStringList headers, int size); + + /** A vector of checkboxes. */ + QVector _cb; + +signals: + /** Signal emitted when a checkboxes changes state. */ + void changeState(int row); + +protected: + void keyPressEvent(QKeyEvent *event) override; + + void mousePressEvent(QMouseEvent *event) override; + +private: + void _doubleClicked(int row, int col); +}; + +/** + * The KsCheckBoxTableWidget class provides a widget to hold a table of + * checkboxes. + */ +class KsCheckBoxTableWidget : public KsCheckBoxWidget +{ + Q_OBJECT +public: + KsCheckBoxTableWidget(const QString &name = "", + QWidget *parent = nullptr); + +protected: + void _adjustSize(); + + void _initTable(QStringList headers, int size); + + /** The KsCheckBoxTable, shown by this widget. */ + KsCheckBoxTable _table; + +private: + void _setCheckState(int i, Qt::CheckState st) override + { + _table._cb[i]->setCheckState(st); + } + + Qt::CheckState _checkState(int i) const override + { + return _table._cb[i]->checkState(); + } + + void _update(bool); + + void _changeState(int row); +}; + +/** The KsCheckBoxTree class provides a tree of checkboxes. */ +class KsCheckBoxTree : public QTreeWidget +{ + Q_OBJECT +public: + explicit KsCheckBoxTree(QWidget *parent = nullptr); + +signals: + /** + * Signal emitted when a checkboxes of the tree changes its state + * and the state of all toplevel and child checkboxes has to be + * reprocesed. + */ + void verify(); + +protected: + void keyPressEvent(QKeyEvent *event) override; + + void mousePressEvent(QMouseEvent *event) override; + +private: + void _doubleClicked(QTreeWidgetItem *item, int col); +}; + +/** + * The KsCheckBoxTreeWidget class provides a widget to hold a tree of + * checkboxes. + */ +class KsCheckBoxTreeWidget : public KsCheckBoxWidget +{ + Q_OBJECT +public: + KsCheckBoxTreeWidget() = delete; + + KsCheckBoxTreeWidget(const QString &name = "", + QWidget *parent = nullptr); + +protected: + void _adjustSize(); + + void _initTree(); + + /** The KsCheckBoxTree, shown by this widget. */ + KsCheckBoxTree _tree; + + /** A vector of Tree items (checkboxes). */ + QVector _cb; + +private: + void _setCheckState(int i, Qt::CheckState st) override + { + _cb[i]->setCheckState(0, st); + } + + Qt::CheckState _checkState(int i) const override + { + return _cb[i]->checkState(0); + } + + void _update(QTreeWidgetItem *item, int column); + + void _verify(); +}; + +/** + * The KsCPUCheckBoxWidget class provides a widget for selecting CPU plots to + * show. + */ +struct KsCPUCheckBoxWidget : public KsCheckBoxTreeWidget +{ + KsCPUCheckBoxWidget() = delete; + + KsCPUCheckBoxWidget(struct tep_handle *pe, + QWidget *parent = nullptr); +}; + +/** + * The KsTasksCheckBoxWidget class provides a widget for selecting Tasks + * to show or hide. + */ +struct KsTasksCheckBoxWidget : public KsCheckBoxTableWidget +{ + KsTasksCheckBoxWidget() = delete; + + KsTasksCheckBoxWidget(struct tep_handle *pe, + bool cond = true, + QWidget *parent = nullptr); + +private: + /** + * A positive condition means that you want to show Tasks and + * a negative condition means that you want to hide Tasks. + */ + bool _cond; +}; + +/** + * The KsEventsCheckBoxWidget class provides a widget for selecting Trace + * events to show or hide. + */ +struct KsEventsCheckBoxWidget : public KsCheckBoxTreeWidget +{ + KsEventsCheckBoxWidget() = delete; + + KsEventsCheckBoxWidget(struct tep_handle *pe, + QWidget *parent = nullptr); +}; + +/** + * The KsPluginCheckBoxWidget class provides a widget for selecting plugins. + */ +struct KsPluginCheckBoxWidget : public KsCheckBoxTableWidget +{ + KsPluginCheckBoxWidget() = delete; + + KsPluginCheckBoxWidget(QStringList pluginList, + QWidget *parent = nullptr); +}; + +class KsDataStore; +class KsGLWidget; + +/** + * The KsQuickFilterMenu class provides a menu for easy filtering and plotting. + * The menu is initialized from a single kshark_entry and uses the content of + * this entry to provides quick actions for filtering and plottin. + */ +class KsQuickEntryMenu : public QMenu +{ + Q_OBJECT +public: + KsQuickEntryMenu() = delete; + + explicit KsQuickEntryMenu(KsDataStore *data, + size_t row, + QWidget *parent = nullptr); + +signals: + /** Signal to add a task plot. */ + void plotTask(int); + +private: + void _hideTask(); + + void _showTask(); + + void _hideEvent(); + + void _showEvent(); + + void _addTaskPlot(); + + KsDataStore *_data; + + size_t _row; + + QAction _hideTaskAction, _showTaskAction; + + QAction _hideEventAction, _showEventAction; + + QAction _addTaskPlotAction; +}; + +#endif From patchwork Wed Oct 10 20:10:15 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10759523 Return-Path: Received: from mail-dm3nam03on0085.outbound.protection.outlook.com ([104.47.41.85]:42585 "EHLO NAM03-DM3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727567AbeJKDj5 (ORCPT ); Wed, 10 Oct 2018 23:39:57 -0400 From: Yordan Karadzhov To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, Yordan Karadzhov Subject: [PATCH v2 4/4] kernel-shark-qt: Add widget demo example. Date: Wed, 10 Oct 2018 23:10:15 +0300 Message-Id: <20181010201015.23824-5-ykaradzhov@vmware.com> In-Reply-To: <20181010201015.23824-1-ykaradzhov@vmware.com> References: <20181010201015.23824-1-ykaradzhov@vmware.com> MIME-Version: 1.0 Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 4421 From: Yordan Karadzhov (VMware) This patch introduces a basic example, showing how to use KsUtils and KsWidgetsLib. Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/examples/CMakeLists.txt | 4 + kernel-shark-qt/examples/widgetdemo.cpp | 158 ++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 kernel-shark-qt/examples/widgetdemo.cpp diff --git a/kernel-shark-qt/examples/CMakeLists.txt b/kernel-shark-qt/examples/CMakeLists.txt index 0c83293..e16216e 100644 --- a/kernel-shark-qt/examples/CMakeLists.txt +++ b/kernel-shark-qt/examples/CMakeLists.txt @@ -19,3 +19,7 @@ target_link_libraries(confio kshark) message(STATUS "dataplot") add_executable(dplot dataplot.cpp) target_link_libraries(dplot kshark-plot) + +message(STATUS "widgetdemo") +add_executable(widgetdemo widgetdemo.cpp) +target_link_libraries(widgetdemo kshark-gui) diff --git a/kernel-shark-qt/examples/widgetdemo.cpp b/kernel-shark-qt/examples/widgetdemo.cpp new file mode 100644 index 0000000..73049bf --- /dev/null +++ b/kernel-shark-qt/examples/widgetdemo.cpp @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + +// C +#include +#include +#include + +// C++ +#include + +// Qt +#include + +// KernelShark +#include "KsUtils.hpp" +#include "KsWidgetsLib.hpp" + +#define default_input_file (char*)"trace.dat" + +static char *input_file = nullptr; + +using namespace std; + +void usage(const char *prog) +{ + cout << "Usage: " << prog << endl + << " -h Display this help message\n" + << " -v Display version and exit\n" + << " -i input_file, default is " << default_input_file << endl + << " -p register plugin, use plugin name, absolute or relative path\n" + << " -u unregister plugin, use plugin name or absolute path\n"; +} + +struct TaskPrint : public QObject +{ + tep_handle *_pevent; + + void print(QVector pids) + { + for (auto const &pid: pids) + cout << "task: " + << tep_data_comm_from_pid(_pevent, pid) + << " pid: " << pid << endl; + } +}; + +int main(int argc, char **argv) +{ + kshark_context *kshark_ctx(nullptr); + QApplication a(argc, argv); + KsPluginManager plugins; + KsDataStore data; + size_t nRows(0); + int c; + + if (!kshark_instance(&kshark_ctx)) + return 1; + + while ((c = getopt(argc, argv, "hvi:p:u:")) != -1) { + switch(c) { + case 'v': + printf("kshark-gui %s\n", KS_VERSION_STRING); + return 0; + + case 'i': + input_file = optarg; + break; + + case 'p': + plugins.registerPlugin(QString(optarg)); + break; + + case 'u': + plugins.unregisterPlugin(QString(optarg)); + break; + + case 'h': + usage(argv[0]); + return 0; + } + } + + if (!input_file) { + struct stat st; + if (stat(default_input_file, &st) == 0) + input_file = default_input_file; + } + + if (input_file) { + data.loadDataFile(input_file); + nRows = data.size(); + } else { + cerr << "No input file is provided.\n"; + } + + cout << nRows << " entries loaded\n"; + + auto lamPrintPl = [&]() + { + kshark_plugin_list *pl; + for (pl = kshark_ctx->plugins; pl; pl = pl->next) + cout << pl->file << endl; + }; + + cout << "\n\n"; + lamPrintPl(); + sleep(1); + + QVector registeredPlugins; + QStringList pluginsList; + + pluginsList << plugins._ksPluginList + << plugins._userPluginList; + + registeredPlugins << plugins._registeredKsPlugins + << plugins._registeredUserPlugins; + + KsCheckBoxWidget *pluginCBD + = new KsPluginCheckBoxWidget(pluginsList); + + pluginCBD->set(registeredPlugins); + + KsCheckBoxDialog *dialog1 = new KsCheckBoxDialog(pluginCBD); + QObject::connect(dialog1, &KsCheckBoxDialog::apply, + &plugins, &KsPluginManager::updatePlugins); + + dialog1->show(); + a.exec(); + + cout << "\n\nYou selected\n"; + lamPrintPl(); + sleep(1); + + if (!nRows) + return 1; + + KsCheckBoxWidget *tasks_cbd = + new KsTasksCheckBoxWidget(data.tep(), true); + + tasks_cbd->setDefault(false); + + TaskPrint p; + p._pevent = data.tep(); + + KsCheckBoxDialog *dialog2 = new KsCheckBoxDialog(tasks_cbd); + QObject::connect(dialog2, &KsCheckBoxDialog::apply, + &p, &TaskPrint::print); + + cout << "\n\nYou selected\n"; + dialog2->show(); + a.exec(); + + return 0; +}