blob: 24f7178ac9f21ff7bf429d9b7ae26f8272b43c30 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
*/
/**
* @file KsUtils.cpp
* @brief KernelShark Utils.
*/
// KernelShark
#include "KsUtils.hpp"
#include "KsWidgetsLib.hpp"
namespace KsUtils {
/** @brief Get a sorted vector of CPU Ids. */
QVector<int> getCPUList()
{
kshark_context *kshark_ctx(nullptr);
int nCPUs;
if (!kshark_instance(&kshark_ctx))
return {};
nCPUs = tep_get_cpus(kshark_ctx->pevent);
QVector<int> allCPUs = QVector<int>(nCPUs);
std::iota(allCPUs.begin(), allCPUs.end(), 0);
return allCPUs;
}
/** @brief Get a sorted vector of Task's Pids. */
QVector<int> getPidList()
{
kshark_context *kshark_ctx(nullptr);
int nTasks, *tempPids;
QVector<int> 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);
std::sort(pids.begin(), pids.end());
return pids;
}
/**
* @brief Get a sorted vector of Event Ids.
*/
QVector<int> getEventIdList(tep_event_sort_type sortType)
{
kshark_context *kshark_ctx(nullptr);
tep_event **events;
int nEvts;
if (!kshark_instance(&kshark_ctx))
return {};
nEvts = tep_get_events_count(kshark_ctx->pevent);
events = tep_list_events(kshark_ctx->pevent, sortType);
QVector<int> allEvts(nEvts);
for (int i = 0; i < nEvts; ++i)
allEvts[i] = events[i]->id;
return allEvts;
}
/** @brief Get a sorted vector of Id values of a filter. */
QVector<int> getFilterIds(tracecmd_filter_id *filter)
{
kshark_context *kshark_ctx(nullptr);
int *cpuFilter, n;
QVector<int> v;
if (!kshark_instance(&kshark_ctx))
return v;
cpuFilter = tracecmd_filter_ids(filter);
n = filter->count;
for (int i = 0; i < n; ++i)
v.append(cpuFilter[i]);
std::sort(v.begin(), v.end());
free(cpuFilter);
return v;
}
/**
* 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;
kshark_ctx->filter_mask |= KS_EVENT_VIEW_FILTER_MASK;
} else {
kshark_ctx->filter_mask &= ~KS_GRAPH_VIEW_FILTER_MASK;
kshark_ctx->filter_mask &= ~KS_EVENT_VIEW_FILTER_MASK;
}
}
/**
* @brief Add a checkbox to a menu.
*
* @param menu: Input location for the menu object, to which the checkbox will be added.
* @param name: The name of the checkbox.
*
* @returns The checkbox object;
*/
QCheckBox *addCheckBoxToMenu(QMenu *menu, QString name)
{
QWidget *containerWidget = new QWidget(menu);
containerWidget->setLayout(new QHBoxLayout());
containerWidget->layout()->setContentsMargins(FONT_WIDTH, FONT_HEIGHT/5,
FONT_WIDTH, FONT_HEIGHT/5);
QCheckBox *checkBox = new QCheckBox(name, menu);
containerWidget->layout()->addWidget(checkBox);
QWidgetAction *action = new QWidgetAction(menu);
action->setDefaultWidget(containerWidget);
menu->addAction(action);
return checkBox;
}
/**
* @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));
}
/**
* @brief Check if the application runs from its installation location.
*/
bool isInstalled()
{
QString appPath = QCoreApplication::applicationDirPath();
QString installPath(_INSTALL_PREFIX);
installPath += "/bin";
installPath = QDir::cleanPath(installPath);
return appPath == installPath;
}
static QString getFileDialog(QWidget *parent,
const QString &windowName,
const QString &filter,
QString &lastFilePath,
bool forSave)
{
QString fileName;
if (lastFilePath.isEmpty()) {
lastFilePath = isInstalled() ? QDir::homePath() :
QDir::currentPath();
}
if (forSave) {
fileName = QFileDialog::getSaveFileName(parent,
windowName,
lastFilePath,
filter);
} else {
fileName = QFileDialog::getOpenFileName(parent,
windowName,
lastFilePath,
filter);
}
if (!fileName.isEmpty())
lastFilePath = QFileInfo(fileName).path();
return fileName;
}
static QStringList getFilesDialog(QWidget *parent,
const QString &windowName,
const QString &filter,
QString &lastFilePath)
{
QStringList fileNames;
if (lastFilePath.isEmpty()) {
lastFilePath = isInstalled() ? QDir::homePath() :
QDir::currentPath();
}
fileNames = QFileDialog::getOpenFileNames(parent,
windowName,
lastFilePath,
filter);
if (!fileNames.isEmpty())
lastFilePath = QFileInfo(fileNames[0]).path();
return fileNames;
}
/**
* @brief Open a standard Qt getFileName dialog and return the name of the
* selected file. Only one file can be selected.
*/
QString getFile(QWidget *parent,
const QString &windowName,
const QString &filter,
QString &lastFilePath)
{
return getFileDialog(parent, windowName, filter, lastFilePath, false);
}
/**
* @brief Open a standard Qt getFileName dialog and return the names of the
* selected files. Multiple files can be selected.
*/
QStringList getFiles(QWidget *parent,
const QString &windowName,
const QString &filter,
QString &lastFilePath)
{
return getFilesDialog(parent, windowName, filter, lastFilePath);
}
/**
* @brief Open a standard Qt getFileName dialog and return the name of the
* selected file. Only one file can be selected.
*/
QString getSaveFile(QWidget *parent,
const QString &windowName,
const QString &filter,
const QString &extension,
QString &lastFilePath)
{
QString fileName = getFileDialog(parent,
windowName,
filter,
lastFilePath,
true);
if (!fileName.isEmpty() && !fileName.endsWith(extension)) {
fileName += extension;
if (QFileInfo(fileName).exists()) {
if (!KsWidgetsLib::fileExistsDialog(fileName))
fileName.clear();
}
}
return fileName;
}
/**
* Separate the command line arguments inside the string taking into account
* possible shell quoting and new lines.
*/
QStringList splitArguments(QString cmd)
{
QString::SplitBehavior opt = QString::SkipEmptyParts;
int i, progress = 0, size;
QStringList argv;
QChar quote = 0;
/* Remove all new lines. */
cmd.replace("\\\n", " ");
size = cmd.count();
auto lamMid = [&] () {return cmd.mid(progress, i - progress);};
for (i = 0; i < size; ++i) {
if (cmd[i] == '\\') {
cmd.remove(i, 1);
size --;
continue;
}
if (cmd[i] == '\'' || cmd[i] == '"') {
if (quote.isNull()) {
argv << lamMid().split(" ", opt);
quote = cmd[i++];
progress = i;
} else if (quote == cmd[i]) {
argv << lamMid();
quote = 0;
progress = ++i;
}
}
}
argv << cmd.right(size - progress).split(" ", opt);
return argv;
}
/** Parse a string containing Ids. The string can be of the form "1 4-7 9". */
QVector<int> parseIdList(QString v_str)
{
QStringList list = v_str.split(",", QString::SkipEmptyParts);
QVector<int> v;
for (auto item: list) {
int i = item.indexOf('-');
if (i > 0) {
/* This item is an interval. */
int to = item.right(item.size() - i - 1).toInt();
int from = item.left(i).toInt();
int s = v.size();
v.resize(s + to - from + 1);
std::iota(v.begin() + s, v.end(), from);
} else {
v.append(item.toInt());
}
}
return v;
}
}; // 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 > 0) {
for (ssize_t r = 0; r < _dataSize; ++r)
free(_rows[r]);
free(_rows);
}
_rows = nullptr;
_dataSize = 0;
}
/** 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;
_unregisterCPUCollections();
if (kshark_filter_is_set(kshark_ctx)) {
kshark_filter_entries(kshark_ctx, _rows, _dataSize);
emit updateWidgets(this);
}
registerCPUCollections();
}
/** 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_get_cpus(_tep);
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_get_cpus(_tep);
for (int cpu = 0; cpu < nCPUs; ++cpu) {
kshark_unregister_data_collection(&kshark_ctx->collections,
KsUtils::matchCPUVisible,
cpu);
}
}
void KsDataStore::_applyIdFilter(int filterId, QVector<int> 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;
case KS_SHOW_CPU_FILTER:
case KS_HIDE_CPU_FILTER:
kshark_filter_clear(kshark_ctx, KS_SHOW_CPU_FILTER);
kshark_filter_clear(kshark_ctx, KS_HIDE_CPU_FILTER);
break;
default:
return;
}
for (auto &&val: vec)
kshark_filter_add_id(kshark_ctx, filterId, val);
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<int> vec)
{
_applyIdFilter(KS_SHOW_TASK_FILTER, vec);
}
/** Apply Hide Task filter. */
void KsDataStore::applyNegTaskFilter(QVector<int> vec)
{
_applyIdFilter(KS_HIDE_TASK_FILTER, vec);
}
/** Apply Show Event filter. */
void KsDataStore::applyPosEventFilter(QVector<int> vec)
{
_applyIdFilter(KS_SHOW_EVENT_FILTER, vec);
}
/** Apply Hide Event filter. */
void KsDataStore::applyNegEventFilter(QVector<int> vec)
{
_applyIdFilter(KS_HIDE_EVENT_FILTER, vec);
}
/** Apply Show CPU filter. */
void KsDataStore::applyPosCPUFilter(QVector<int> vec)
{
_applyIdFilter(KS_SHOW_CPU_FILTER, vec);
}
/** Apply Hide CPU filter. */
void KsDataStore::applyNegCPUFilter(QVector<int> vec)
{
_applyIdFilter(KS_HIDE_CPU_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);
kshark_filter_clear(kshark_ctx, KS_SHOW_CPU_FILTER);
kshark_filter_clear(kshark_ctx, KS_HIDE_CPU_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, this](const QString &plugin)
{
char *lib;
int n;
lib = _pluginLibFromName(plugin, n);
if (n <= 0)
return;
kshark_register_plugin(kshark_ctx, lib);
free(lib);
};
auto lamRegUser = [&kshark_ctx](const QString &plugin)
{
std::string lib = plugin.toStdString();
kshark_register_plugin(kshark_ctx, lib.c_str());
};
_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, this](const QString &plugin)
{
char *lib;
int n;
lib = _pluginLibFromName(plugin, n);
if (n <= 0)
return;
kshark_unregister_plugin(kshark_ctx, lib);
free(lib);
};
auto lamUregUser = [&kshark_ctx](const QString &plugin)
{
std::string lib = plugin.toStdString();
kshark_unregister_plugin(kshark_ctx, lib.c_str());
};
_forEachInList(_ksPluginList,
_registeredKsPlugins,
lamUregBuiltIn);
_forEachInList(_userPluginList,
_registeredUserPlugins,
lamUregUser);
}
char *KsPluginManager::_pluginLibFromName(const QString &plugin, int &n)
{
QString appPath = QCoreApplication::applicationDirPath();
QString libPath = appPath + "/../../kernel-shark/lib";
std::string pluginStr = plugin.toStdString();
char *lib;
libPath = QDir::cleanPath(libPath);
if (!KsUtils::isInstalled() && QDir(libPath).exists()) {
std::string pathStr = libPath.toStdString();
n = asprintf(&lib, "%s/plugin-%s.so",
pathStr.c_str(), pluginStr.c_str());
} else {
n = asprintf(&lib, "%s/plugin-%s.so",
KS_PLUGIN_INSTALL_PREFIX, pluginStr.c_str());
}
return lib;
}
/**
* @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.
*/
lib = _pluginLibFromName(plugin, n);
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.
*<br>
* 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.
*/
lib = _pluginLibFromName(plugin, n);
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;
}
}
}
/** @brief Add to the list and initialize user-provided plugins. All other
* previously loaded plugins will be reinitialized and the data will be
* reloaded.
*
* @param fileNames: the library files (.so) of the plugins.
*/
void KsPluginManager::addPlugins(const QStringList &fileNames)
{
kshark_context *kshark_ctx(nullptr);
if (!kshark_instance(&kshark_ctx))
return;
kshark_handle_plugins(kshark_ctx, KSHARK_PLUGIN_CLOSE);
for (auto const &p: fileNames)
registerPlugin(p);
kshark_handle_plugins(kshark_ctx, KSHARK_PLUGIN_INIT);
emit dataReload();
}
/** 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<int> pluginIds)
{
kshark_context *kshark_ctx(nullptr);
if (!kshark_instance(&kshark_ctx))
return;
auto register_plugins = [&] (QVector<int> 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();
}