blob: 1272c2ebee62a71eed5618f1b21293eb69a57e0f [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2017 VMware Inc, Yordan Karadzhov <ykaradzhov@vmware.com>
*/
/**
* @file KsCaptureDialog.cpp
* @brief Dialog for trace data recording.
*/
// Qt
#include <QLocalSocket>
// KernelShark
#include "libkshark.h"
#include "KsUtils.hpp"
#include "KsCmakeDef.hpp"
#include "KsCaptureDialog.hpp"
static inline tep_handle *local_events()
{
return tracecmd_local_events(tracecmd_get_tracing_dir());
}
/** @brief Create KsCaptureControl widget. */
KsCaptureControl::KsCaptureControl(QWidget *parent)
: QWidget(parent),
_localTEP(local_events()),
_eventsWidget(_localTEP, this),
_pluginsLabel("Plugin: ", this),
_outputLabel("Output file: ", this),
_commandLabel("Command: ", this),
_outputLineEdit("trace.dat", this),
_commandLineEdit("sleep 0.1", this),
_settingsToolBar(this),
_controlToolBar(this),
_pluginsComboBox(this),
_importSettingsButton("Import Settings", this),
_exportSettingsButton("Export Settings", this),
_outputBrowseButton("Browse", this),
_commandCheckBox("Display output", this),
_captureButton("Capture", &_controlToolBar),
_applyButton("Apply", &_controlToolBar),
_closeButton("Close", &_controlToolBar)
{
QStringList pluginList = _getPlugins();
int row(0);
auto lamAddLine = [&] {
QFrame* line = new QFrame();
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);
_topLayout.addWidget(line);
};
if (pluginList.count() == 0) {
/*
* No plugins have been found. Most likely this is because
* the process has no Root privileges.
*/
QString message("Error: No events or plugins found.\n");
message += "Root privileges are required.";
QLabel *errorLabel = new QLabel(message);
errorLabel->setStyleSheet("QLabel {color : red;}");
_topLayout.addWidget(errorLabel);
lamAddLine();
}
pluginList.prepend("nop");
_settingsToolBar.addWidget(&_importSettingsButton);
_settingsToolBar.addSeparator();
_settingsToolBar.addWidget(&_exportSettingsButton);
_topLayout.addWidget(&_settingsToolBar);
lamAddLine();
_eventsWidget.setDefault(false);
_eventsWidget.setMinimumHeight(25 * FONT_HEIGHT);
_eventsWidget.removeSystem("ftrace");
_topLayout.addWidget(&_eventsWidget);
_pluginsLabel.adjustSize();
_execLayout.addWidget(&_pluginsLabel, row, 0);
_pluginsComboBox.addItems(pluginList);
_execLayout.addWidget(&_pluginsComboBox, row++, 1);
_outputLabel.adjustSize();
_execLayout.addWidget(&_outputLabel, row, 0);
_outputLineEdit.setFixedWidth(FONT_WIDTH * 30);
_execLayout.addWidget(&_outputLineEdit, row, 1);
_outputBrowseButton.adjustSize();
_execLayout.addWidget(&_outputBrowseButton, row++, 2);
_commandLabel.adjustSize();
_commandLabel.setFixedWidth(_outputLabel.width());
_execLayout.addWidget(&_commandLabel, row, 0);
_commandLineEdit.setFixedWidth(FONT_WIDTH * 30);
_execLayout.addWidget(&_commandLineEdit, row, 1);
_commandCheckBox.setCheckState(Qt::Unchecked);
_commandCheckBox.adjustSize();
_execLayout.addWidget(&_commandCheckBox, row++, 2);
_topLayout.addLayout(&_execLayout);
lamAddLine();
_captureButton.setFixedWidth(STRING_WIDTH("_Capture_") + FONT_WIDTH * 2);
_applyButton.setFixedWidth(_captureButton.width());
_closeButton.setFixedWidth(_captureButton.width());
_controlToolBar.addWidget(&_captureButton);
_controlToolBar.addWidget(&_applyButton);
_controlToolBar.addWidget(&_closeButton);
_topLayout.addWidget(&_controlToolBar);
setLayout(&_topLayout);
connect(&_importSettingsButton, &QPushButton::pressed,
this, &KsCaptureControl::_importSettings);
connect(&_exportSettingsButton, &QPushButton::pressed,
this, &KsCaptureControl::_exportSettings);
connect(&_outputBrowseButton, &QPushButton::pressed,
this, &KsCaptureControl::_browse);
connect(&_applyButton, &QPushButton::pressed,
this, &KsCaptureControl::_apply);
}
/**
* Use the settings of the Control panel to generate a list of command line
* arguments for trace-cmd.
*/
QStringList KsCaptureControl::getArgs()
{
QStringList argv;
argv << "record";
argv << "-p" << _pluginsComboBox.currentText();
if (_eventsWidget.all()) {
argv << "-e" << "all";
} else {
QVector<int> evtIds = _eventsWidget.getCheckedIds();
tep_event *event;
for (auto const &id: evtIds) {
event = tep_find_event(_localTEP, id);
if (!event)
continue;
argv << "-e" + QString(event->system) +
":" + QString(event->name);
}
}
argv << "-o" << outputFileName();
argv << _commandLineEdit.text().split(" ");
return argv;
}
QStringList KsCaptureControl::_getPlugins()
{
QStringList pluginList;
char **all_plugins;
all_plugins = tracecmd_local_plugins(tracecmd_get_tracing_dir());
if (!all_plugins)
return pluginList;
for (int i = 0; all_plugins[i]; ++i) {
/*
* TODO plugin selection here.
* printf("plugin %i %s\n", i, all_plugins[i]);
*/
pluginList << all_plugins[i];
free(all_plugins[i]);
}
free (all_plugins);
qSort(pluginList);
return pluginList;
}
void KsCaptureControl::_importSettings()
{
int nEvts = tep_get_events_count(_localTEP);
kshark_config_doc *conf, *jevents, *temp;
QVector<bool> v(nEvts, false);
tracecmd_filter_id *eventHash;
tep_event **events;
QString fileName;
/** Get all available events. */
events = tep_list_events(_localTEP, TEP_EVENT_SORT_SYSTEM);
/* Get the configuration document. */
fileName = QFileDialog::getOpenFileName(this,
"Import from Filter",
KS_DIR,
"Kernel Shark Config files (*.json);;");
if (fileName.isEmpty())
return;
conf = kshark_open_config_file(fileName.toStdString().c_str(),
"kshark.config.record");
if (!conf)
return;
/*
* Load the hash table of selected events from the configuration
* document.
*/
jevents = kshark_config_alloc(KS_CONFIG_JSON);
if (!kshark_config_doc_get(conf, "Events", jevents))
return;
eventHash = tracecmd_filter_id_hash_alloc();
kshark_import_event_filter(_localTEP, eventHash, "Events", jevents);
for (int i = 0; i < nEvts; ++i) {
if (tracecmd_filter_id_find(eventHash, events[i]->id))
v[i] = true;
}
_eventsWidget.set(v);
tracecmd_filter_id_hash_free(eventHash);
/** Get all available plugins. */
temp = kshark_string_config_alloc();
if (kshark_config_doc_get(conf, "Plugin", temp))
_pluginsComboBox.setCurrentText(KS_C_STR_CAST(temp->conf_doc));
if (kshark_config_doc_get(conf, "Output", temp))
_outputLineEdit.setText(KS_C_STR_CAST(temp->conf_doc));
if (kshark_config_doc_get(conf, "Command", temp))
_commandLineEdit.setText(KS_C_STR_CAST(temp->conf_doc));
}
void KsCaptureControl::_exportSettings()
{
kshark_config_doc *conf, *events;
json_object *jplugin;
QString plugin, out, comm;
QVector<int> ids;
QString fileName =
QFileDialog::getSaveFileName(this,
"Export to File",
KS_DIR,
"Kernel Shark Config files (*.json);;");
if (fileName.isEmpty())
return;
if (!fileName.endsWith(".json")) {
fileName += ".json";
if (QFileInfo(fileName).exists()) {
if (!KsWidgetsLib::fileExistsDialog(fileName))
return;
}
}
/* Create a configuration document. */
conf = kshark_record_config_new(KS_CONFIG_JSON);
events = kshark_filter_config_new(KS_CONFIG_JSON);
/*
* Use the tracecmd_filter_id to save all selected events in the
* configuration file.
*/
ids = _eventsWidget.getCheckedIds();
tracecmd_filter_id *eventHash = tracecmd_filter_id_hash_alloc();
for (auto const &id: ids)
tracecmd_filter_id_add(eventHash, id);
kshark_export_event_filter(_localTEP, eventHash, "Events", events);
kshark_config_doc_add(conf, "Events", events);
tracecmd_filter_id_hash_free(eventHash);
/* Save the plugin. */
plugin = _pluginsComboBox.currentText();
jplugin = json_object_new_string(plugin.toStdString().c_str());
kshark_config_doc_add(conf, "Plugin", kshark_json_to_conf(jplugin));
/* Save the output file. */
out = outputFileName();
json_object *jout = json_object_new_string(out.toStdString().c_str());
kshark_config_doc_add(conf, "Output", kshark_json_to_conf(jout));
/* Save the command. */
comm = _commandLineEdit.text();
json_object *jcomm = json_object_new_string(comm.toStdString().c_str());
kshark_config_doc_add(conf, "Command", kshark_json_to_conf(jcomm));
kshark_save_config_file(fileName.toStdString().c_str(), conf);
}
void KsCaptureControl::_browse()
{
QString fileName =
QFileDialog::getSaveFileName(this,
"Save File",
KS_DIR,
"trace-cmd files (*.dat);;All files (*)");
if (!fileName.isEmpty())
_outputLineEdit.setText(fileName);
}
void KsCaptureControl::_apply()
{
emit argsReady(getArgs().join(" "));
}
/** @brief Create KsCaptureMonitor widget. */
KsCaptureMonitor::KsCaptureMonitor(QWidget *parent)
: QWidget(parent),
_mergedChannels(false),
_argsModified(false),
_panel(this),
_name("Output display", this),
_space("max size ", this),
_readOnlyCB("read only", this),
_maxLinNumEdit(QString("%1").arg(KS_CAP_MON_MAX_LINE_NUM), this),
_consolOutput("", this)
{
_panel.setMaximumHeight(FONT_HEIGHT * 1.75);
_panel.addWidget(&_name);
_space.setAlignment(Qt::AlignRight);
_panel.addWidget(&_space);
_maxLinNumEdit.setFixedWidth(FONT_WIDTH * 7);
_panel.addWidget(&_maxLinNumEdit);
_panel.addSeparator();
_readOnlyCB.setCheckState(Qt::Checked);
_panel.addWidget(&_readOnlyCB);
_layout.addWidget(&_panel);
_consolOutput.setStyleSheet("QLabel {background-color : white;}");
_consolOutput.setMinimumWidth(FONT_WIDTH * 60);
_consolOutput.setMinimumHeight(FONT_HEIGHT * 10);
_consolOutput.setMaximumBlockCount(KS_CAP_MON_MAX_LINE_NUM);
_space.setMinimumWidth(FONT_WIDTH * 50 - _name.width() - _readOnlyCB.width());
_consolOutput.setReadOnly(true);
_layout.addWidget(&_consolOutput);
this->setLayout(&_layout);
connect(&_maxLinNumEdit, &QLineEdit::textChanged,
this, &KsCaptureMonitor::_maxLineNumber);
connect(&_readOnlyCB, &QCheckBox::stateChanged,
this, &KsCaptureMonitor::_readOnly);
connect(&_consolOutput, &QPlainTextEdit::textChanged,
this, &KsCaptureMonitor::_argVModified);
this->show();
}
void KsCaptureMonitor::_maxLineNumber(const QString &test)
{
bool ok;
int max = test.toInt(&ok);
if (ok)
_consolOutput.setMaximumBlockCount(max);
}
void KsCaptureMonitor::_readOnly(int state)
{
if (state == Qt::Checked)
_consolOutput.setReadOnly(true);
else
_consolOutput.setReadOnly(false);
}
void KsCaptureMonitor::_argsReady(const QString &args)
{
_name.setText("Capture options:");
_consolOutput.setPlainText(args);
_argsModified = false;
}
void KsCaptureMonitor::_argVModified()
{
_argsModified = true;
}
void KsCaptureMonitor::_printAllStandardError()
{
QProcess *_capture = (QProcess*) sender();
_consolOutput.moveCursor(QTextCursor::End);
_consolOutput.insertPlainText(_capture->readAllStandardError());
_consolOutput.moveCursor(QTextCursor::End);
QCoreApplication::processEvents();
}
void KsCaptureMonitor::_printAllStandardOutput()
{
QProcess *_capture = (QProcess*) sender();
if (!_mergedChannels)
return;
_consolOutput.appendPlainText(_capture->readAllStandardOutput());
QCoreApplication::processEvents();
}
/**
* Connect the Capture monitor widget to the signals of the recording process.
*/
void KsCaptureMonitor::connectMe(QProcess *proc, KsCaptureControl *ctrl)
{
connect(proc, &QProcess::started,
this, &KsCaptureMonitor::_captureStarted);
/* Using the old Signal-Slot syntax because QProcess::finished has overloads. */
connect(proc, SIGNAL(finished(int, QProcess::ExitStatus)),
this, SLOT(_captureFinished(int, QProcess::ExitStatus)));
connect(proc, &QProcess::readyReadStandardError,
this, &KsCaptureMonitor::_printAllStandardError);
connect(proc, &QProcess::readyReadStandardOutput,
this, &KsCaptureMonitor::_printAllStandardOutput);
connect(ctrl, &KsCaptureControl::argsReady,
this, &KsCaptureMonitor::_argsReady);
}
void KsCaptureMonitor::_captureStarted()
{
_name.setText("Terminal output:");
_readOnlyCB.setCheckState(Qt::Checked);
QCoreApplication::processEvents();
}
void KsCaptureMonitor::_captureFinished(int exit, QProcess::ExitStatus status)
{
QProcess *_capture = (QProcess *)sender();
if (exit != 0 || status != QProcess::NormalExit) {
QString errMessage("Capture process failed: ");
errMessage += _capture->errorString();
_consolOutput.appendPlainText(errMessage);
QCoreApplication::processEvents();
}
}
/** Print a message. */
void KsCaptureMonitor::print(const QString &message)
{
_consolOutput.appendPlainText(message);
}
/** @brief Create KsCaptureDialog widget. */
KsCaptureDialog::KsCaptureDialog(QWidget *parent)
: QWidget(parent),
_captureCtrl(this),
_captureMon(this),
_captureProc(this)
{
QString captureExe(TRACECMD_BIN_DIR);
this->setWindowTitle("Capture");
_layout.addWidget(&_captureCtrl);
_layout.addWidget(&_captureMon);
this->setLayout(&_layout);
connect(&_captureCtrl._commandCheckBox, &QCheckBox::stateChanged,
this, &KsCaptureDialog::_setChannelMode);
connect(&_captureCtrl._captureButton, &QPushButton::pressed,
this, &KsCaptureDialog::_capture);
connect(&_captureCtrl._closeButton, &QPushButton::pressed,
this, &KsCaptureDialog::close);
captureExe += "/trace-cmd";
_captureProc.setProgram(captureExe);
_captureMon.connectMe(&_captureProc, &_captureCtrl);
}
void KsCaptureDialog::_capture()
{
QStringList argv;
int argc;
if(_captureMon._argsModified) {
argv = _captureMon.text().split(" ");
} else {
argv = _captureCtrl.getArgs();
}
_captureMon.print("\n");
_captureMon.print(QString("trace-cmd " + argv.join(" ") + "\n"));
_captureProc.setArguments(argv);
_captureProc.start();
_captureProc.waitForFinished();
argc = argv.count();
for (int i = 0; i < argc; ++i) {
if (argv[i] == "-o") {
_sendOpenReq(argv[i + 1]);
break;
}
}
/* Reset the _argsModified flag. */
_captureMon._argsModified = false;
}
void KsCaptureDialog::_setChannelMode(int state)
{
if (state > 0) {
_captureMon._mergedChannels = true;
} else {
_captureMon._mergedChannels = false;
}
}
void KsCaptureDialog::_sendOpenReq(const QString &fileName)
{
QLocalSocket *socket = new QLocalSocket(this);
socket->connectToServer("KSCapture", QIODevice::WriteOnly);
if (socket->waitForConnected()) {
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
const QString message = fileName;
out << quint32(message.size());
out << message;
socket->write(block);
socket->flush();
socket->disconnectFromServer();
} else {
_captureMon.print(socket->errorString());
}
}