diff --git a/CMakeLists.txt b/CMakeLists.txt index ca790783..caba3e9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ if(NOT CMAKE_BUILD_TYPE) ) endif() -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) option(APPIMAGE_BUILD "configure build for bundling in an appimage" OFF) @@ -58,6 +58,13 @@ find_package( REQUIRED ) +find_package( + QCoro${QT_MAJOR_VERSION} + COMPONENTS Core + REQUIRED +) +qcoro_enable_coroutines() + find_package(LibElf REQUIRED) find_package(ElfUtils REQUIRED) find_package(ECM 1.0.0 NO_MODULE REQUIRED) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 66e453ab..11212e10 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,6 +55,7 @@ set(HOTSPOT_SRCS errnoutil.cpp recordhost.cpp copyabletreeview.cpp + remotedevice.cpp # ui files: mainwindow.ui aboutdialog.ui @@ -119,6 +120,7 @@ target_link_libraries( models PrefixTickLabels KDAB::kddockwidgets + QCoro::Core ) if(KFArchive_FOUND) diff --git a/src/devicesettings.ui b/src/devicesettings.ui new file mode 100644 index 00000000..06ba06c0 --- /dev/null +++ b/src/devicesettings.ui @@ -0,0 +1,124 @@ + + + DeviceSettings + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Hostname: + + + + + + + + + + Username: + + + + + + + + + + Password: + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QLineEdit::Password + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Copy SSH Key + + + + + + + + + + Options: + + + + + + + + + + + diff --git a/src/multiconfigwidget.cpp b/src/multiconfigwidget.cpp index 5225111f..29d572b6 100644 --- a/src/multiconfigwidget.cpp +++ b/src/multiconfigwidget.cpp @@ -1,119 +1,208 @@ /* SPDX-FileCopyrightText: Lieven Hey - SPDX-FileCopyrightText: Milian Wolff - SPDX-FileCopyrightText: 2016-2022 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + SPDX-FileCopyrightText: 2022-2023 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com SPDX-License-Identifier: GPL-2.0-or-later */ #include "multiconfigwidget.h" +#include + #include +#include #include #include #include +#include + +#include "ui_multiconfigwidget.h" + MultiConfigWidget::MultiConfigWidget(QWidget* parent) : QWidget(parent) + , m_configWidget(new Ui::MultiConfigWidget) { - auto* layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - - m_comboBox = new QComboBox(this); - m_comboBox->setEditable(true); - m_comboBox->setInsertPolicy(QComboBox::InsertAtCurrent); - m_comboBox->setDisabled(true); - layout->addWidget(m_comboBox); - - connect(m_comboBox->lineEdit(), &QLineEdit::editingFinished, this, [this] { - m_config.deleteGroup(m_comboBox->currentData().toString()); - saveConfigAs(m_comboBox->currentText()); - m_comboBox->setItemData(m_comboBox->currentIndex(), m_comboBox->currentText()); - m_config.sync(); - }); + m_configWidget->setupUi(this); + + connect(m_configWidget->currentConfigComboBox, qOverload(&QComboBox::currentIndexChanged), this, + [this](int) { loadConfig(currentConfig()); }); - connect(m_comboBox, QOverload::of(&QComboBox::currentIndexChanged), this, - [this] { selectConfig(m_comboBox->currentData().toString()); }); + connect(this, &MultiConfigWidget::configsChanged, this, [this]() { + m_configWidget->currentConfigComboBox->setDisabled(m_configWidget->currentConfigComboBox->count() == 0); + }); - m_copyButton = new QPushButton(this); - m_copyButton->setText(tr("Copy Config")); - layout->addWidget(m_copyButton); + m_configWidget->currentConfigComboBox->setEnabled(true); - connect(m_copyButton, &QPushButton::clicked, this, [this] { - const QString name = tr("Config %1").arg(m_comboBox->count() + 1); - saveConfigAs(name); - m_comboBox->addItem(name, name); - m_comboBox->setDisabled(false); + connect(m_configWidget->copyButton, &QPushButton::pressed, this, [this] { + const auto name = m_configWidget->currentConfigComboBox->currentText(); + auto copyName = QStringLiteral("Copy of %1").arg(name); + if (name.isEmpty()) { + copyName = QStringLiteral("Config"); + } + saveConfig(copyName); + m_configWidget->currentConfigComboBox->addItem(copyName, copyName); + m_configWidget->currentConfigComboBox->setCurrentIndex(m_configWidget->currentConfigComboBox->count() - 1); + emit configsChanged(); }); - m_removeButton = new QPushButton(this); - m_removeButton->setText(tr("Remove Config")); - layout->addWidget(m_removeButton); + connect(m_configWidget->deleteButton, &QPushButton::pressed, this, [this] { + const auto currentConfig = m_configWidget->currentConfigComboBox->currentText(); + m_configWidget->currentConfigComboBox->removeItem(m_configWidget->currentConfigComboBox->currentIndex()); + m_group.deleteGroup(currentConfig); + emit configsChanged(); + }); - connect(m_removeButton, &QPushButton::clicked, this, [this] { - m_config.deleteGroup(m_comboBox->currentData().toString()); - m_comboBox->removeItem(m_comboBox->currentIndex()); + connect(m_configWidget->currentConfigComboBox->lineEdit(), &QLineEdit::returnPressed, this, [this] { + const auto oldName = m_configWidget->currentConfigComboBox->currentData().toString(); + if (!oldName.isEmpty()) { + m_group.deleteGroup(oldName); + } - if (m_comboBox->count() == 0) { - m_comboBox->setDisabled(true); - } else { - selectConfig(m_comboBox->currentData().toString()); + auto newName = m_configWidget->currentConfigComboBox->currentText(); + if (newName.isEmpty()) { + newName = QStringLiteral("Config %1").arg(m_configWidget->currentConfigComboBox->currentIndex()); } + m_configWidget->currentConfigComboBox->setItemData(m_configWidget->currentConfigComboBox->currentIndex(), + newName); + saveConfig(newName); }); - - setLayout(layout); } MultiConfigWidget::~MultiConfigWidget() = default; -QString MultiConfigWidget::currentConfig() const +void MultiConfigWidget::setChildWidget(QWidget* widget, const QVector& formWidgets) { - return m_comboBox->currentData().toString(); + m_formWidgets = formWidgets; + m_child = widget; + m_child->setParent(this); + auto placeholder = m_configWidget->layout->replaceWidget(m_configWidget->placeholder, m_child); + Q_ASSERT(placeholder); + delete placeholder; } -void MultiConfigWidget::setConfig(const KConfigGroup& group) +void MultiConfigWidget::setConfigGroup(const KConfigGroup& group) { - m_comboBox->clear(); - m_config = group; + m_group = group; + if (!m_group.isValid()) { + return; + } + + m_configWidget->currentConfigComboBox->clear(); + auto configGroups = configs(); + for (const auto& config : configGroups) { + m_configWidget->currentConfigComboBox->addItem(config, config); + } + + m_configWidget->currentConfigComboBox->setCurrentIndex(0); - if (!m_config.isValid()) + if (!configGroups.isEmpty()) { + loadConfig(currentConfig()); + } + emit configsChanged(); +} + +void MultiConfigWidget::loadConfig(const QString& name) +{ + if (m_saving) { return; + } - const auto groups = m_config.groupList(); - for (const auto& config : groups) { - if (m_config.hasGroup(config)) { - // item data is used to get the old name after renaming - m_comboBox->addItem(config, config); - m_comboBox->setDisabled(false); + if (m_child == nullptr) { + return; + } + + if (name.isEmpty() || !m_group.isValid()) { + return; + } + + if (!m_group.hasGroup(name)) { + return; + } + + const auto& group = m_group.group(name); + + for (auto formWidget : std::as_const(m_formWidgets)) { + const auto& name = formWidget->objectName(); + if (auto widget = qobject_cast(formWidget)) { + auto text = group.readEntry(name, QString {}); + widget->setText(text); + } else if (auto widget = qobject_cast(formWidget)) { + auto text = group.readEntry(name, QString {}); + widget->setText(text); + } else if (auto widget = qobject_cast(formWidget)) { + auto items = group.readEntry(name, QString {}).split(QLatin1Char(':')); + widget->setItems(items); + } else if (auto widget = qobject_cast(formWidget)) { + auto value = group.readEntry(name, QString {}); + if (value.isEmpty()) { + continue; + } + auto index = widget->findText(value); + if (index == -1) { + index = widget->count(); + widget->addItem(value); + } + widget->setCurrentIndex(index); + } else { + qWarning() << formWidget->metaObject()->className() << "is not supported in MultiConfigWidget"; } } } -void MultiConfigWidget::saveConfigAs(const QString& name) +void MultiConfigWidget::saveConfig(const QString& name) { - if (!name.isEmpty()) { - emit saveConfig(m_config.group(name)); + if (!m_child) { + return; + } + + if (name.isEmpty() || !m_group.isValid()) { + return; + } + + m_saving = true; + + auto group = m_group.group(name); + + for (const auto formWidget : std::as_const(m_formWidgets)) { + const auto& name = formWidget->objectName(); + if (auto widget = qobject_cast(formWidget)) { + group.writeEntry(name, widget->text()); + } else if (auto widget = qobject_cast(formWidget)) { + group.writeEntry(name, widget->text()); + } else if (auto widget = qobject_cast(formWidget)) { + auto data = widget->items().join(QLatin1Char(':')); + group.writeEntry(name, data); + } else if (auto widget = qobject_cast(formWidget)) { + group.writeEntry(name, widget->currentText()); + } else { + qWarning() << formWidget->metaObject()->className() << "is not supported in MultiConfigWidget"; + } } + + m_saving = false; } -void MultiConfigWidget::updateCurrentConfig() +void MultiConfigWidget::saveCurrentConfig() { - if (m_comboBox->currentIndex() != -1) { - saveConfigAs(m_comboBox->currentData().toString()); - } + saveConfig(currentConfig()); } -void MultiConfigWidget::selectConfig(const QString& name) +QString MultiConfigWidget::currentConfig() const { - m_config.sync(); - if (!name.isEmpty() && m_config.hasGroup(name)) { - emit restoreConfig(m_config.group(name)); - } + return m_configWidget->currentConfigComboBox->currentText(); } -void MultiConfigWidget::restoreCurrent() +QStringList MultiConfigWidget::configs() const { - if (m_comboBox->currentIndex() != -1) { - selectConfig(m_comboBox->currentData().toString()); - } + auto configs = m_group.groupList(); + // KConfig is weird in regards to deleted groups + // they are still in groupList but are no longer valid + // TODO: C++20 use filter + configs.erase(std::remove_if(configs.begin(), configs.end(), + [group = m_group](const QString& groupName) { + return !group.hasGroup(groupName) || !group.group(groupName).exists(); + }), + configs.end()); + return configs; } diff --git a/src/multiconfigwidget.h b/src/multiconfigwidget.h index 5f7b71f0..d51cc9a2 100644 --- a/src/multiconfigwidget.h +++ b/src/multiconfigwidget.h @@ -1,7 +1,6 @@ /* SPDX-FileCopyrightText: Lieven Hey - SPDX-FileCopyrightText: Milian Wolff - SPDX-FileCopyrightText: 2016-2022 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + SPDX-FileCopyrightText: 2022-2023 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com SPDX-License-Identifier: GPL-2.0-or-later */ @@ -11,32 +10,46 @@ #include #include -class QComboBox; -class QPushButton; +namespace Ui { +class MultiConfigWidget; +} +/** this widget allows fast switching between different configurations + * of m_child + * saveConfig will automatically save all changes to m_group + * use setChildWidget for an unparented one and moveWidgetToChild for a parented one + * */ class MultiConfigWidget : public QWidget { Q_OBJECT public: MultiConfigWidget(QWidget* parent = nullptr); - ~MultiConfigWidget(); + ~MultiConfigWidget() override; - QString currentConfig() const; + /** widget is the widget containing the form which will be shown inside the MultiConfigWidget + * formWidgets is a list of all user editable widgets in widget that will automatically be saved and restored + * each widget in formWidgets needs a unique name **/ + void setChildWidget(QWidget* widget, const QVector& formWidgets); -signals: - void saveConfig(KConfigGroup group); - void restoreConfig(const KConfigGroup& group); + /** set group where everything should be saved in **/ + void setConfigGroup(const KConfigGroup& group); + + QString currentConfig() const; public slots: - void setConfig(const KConfigGroup& group); - void saveConfigAs(const QString& name); - void updateCurrentConfig(); - void selectConfig(const QString& name); - void restoreCurrent(); + void loadConfig(const QString& name); + void saveConfig(const QString& name); + void saveCurrentConfig(); + +signals: + void configsChanged(); + void currentConfigChanged(); private: - KConfigGroup m_config; - QComboBox* m_comboBox = nullptr; - QPushButton* m_copyButton = nullptr; - QPushButton* m_removeButton = nullptr; + QStringList configs() const; + std::unique_ptr m_configWidget; + QWidget* m_child = nullptr; + QVector m_formWidgets; + KConfigGroup m_group; + bool m_saving = false; }; diff --git a/src/multiconfigwidget.ui b/src/multiconfigwidget.ui new file mode 100644 index 00000000..736c75e8 --- /dev/null +++ b/src/multiconfigwidget.ui @@ -0,0 +1,115 @@ + + + MultiConfigWidget + + + + 0 + 0 + 400 + 300 + + + + + 0 + 0 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + true + + + QComboBox::InsertAtCurrent + + + QComboBox::AdjustToContents + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Copy + + + + + + + Delete + + + + + + + + + + + + + + diff --git a/src/perfrecord.cpp b/src/perfrecord.cpp index e3adc589..f04f5c76 100644 --- a/src/perfrecord.cpp +++ b/src/perfrecord.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #if KWINDOWSYSTEM_VERSION >= QT_VERSION_CHECK(5, 101, 0) #include @@ -57,7 +58,6 @@ PerfRecord::~PerfRecord() stopRecording(); if (m_perfRecordProcess) { m_perfRecordProcess->waitForFinished(100); - delete m_perfRecordProcess; } } @@ -69,62 +69,31 @@ bool PerfRecord::runPerf(bool elevatePrivileges, const QStringList& perfOptions, m_perfControlFifo.requestStop(); m_perfControlFifo.close(); m_perfRecordProcess->kill(); - m_perfRecordProcess->deleteLater(); } - m_perfRecordProcess = new QProcess(this); - m_perfRecordProcess->setProcessChannelMode(QProcess::MergedChannels); - const auto outputFileInfo = QFileInfo(outputPath); - const auto folderPath = outputFileInfo.dir().path(); - const auto folderInfo = QFileInfo(folderPath); - if (!folderInfo.exists()) { - emit recordingFailed(tr("Folder '%1' does not exist.").arg(folderPath)); - return false; - } - if (!folderInfo.isDir()) { - emit recordingFailed(tr("'%1' is not a folder.").arg(folderPath)); - return false; - } - if (!folderInfo.isWritable()) { - emit recordingFailed(tr("Folder '%1' is not writable.").arg(folderPath)); - return false; - } - - connect(m_perfRecordProcess.data(), static_cast(&QProcess::finished), - this, [this](int exitCode, QProcess::ExitStatus exitStatus) { - Q_UNUSED(exitStatus) + m_outputPath = outputPath; + m_userTerminated = false; - const auto outputFileInfo = QFileInfo(m_outputPath); - if ((exitCode == EXIT_SUCCESS || (exitCode == SIGTERM && m_userTerminated) || outputFileInfo.size() > 0) - && outputFileInfo.exists()) { - if (exitCode != EXIT_SUCCESS && !m_userTerminated) { - emit debuggeeCrashed(); - } - emit recordingFinished(m_outputPath); - } else { - emit recordingFailed(tr("Failed to record perf data, error code %1.").arg(exitCode)); - } - m_userTerminated = false; - }); + if (m_host->isLocal()) { + return runPerfLocal(elevatePrivileges, perfOptions, outputPath, workingDirectory); + } else { + return runPerfRemote(perfOptions, outputPath, workingDirectory); + } +} - connect(m_perfRecordProcess.data(), &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) { - Q_UNUSED(error) - if (!m_userTerminated) { - emit recordingFailed(m_perfRecordProcess->errorString()); - } - }); +bool PerfRecord::runPerfLocal(bool elevatePrivileges, const QStringList& perfOptions, const QString& outputPath, + const QString& workingDirectory) +{ + m_perfRecordProcess = std::make_unique(this); + m_perfRecordProcess->setProcessChannelMode(QProcess::MergedChannels); - connect(m_perfRecordProcess.data(), &QProcess::started, this, - [this] { emit recordingStarted(m_perfRecordProcess->program(), m_perfRecordProcess->arguments()); }); + connectRecordingProcessErrors(); - connect(m_perfRecordProcess.data(), &QProcess::readyRead, this, [this]() { + connect(m_perfRecordProcess.get(), &QProcess::readyRead, this, [this]() { const auto output = QString::fromUtf8(m_perfRecordProcess->readAll()); emit recordingOutput(output); }); - m_outputPath = outputPath; - m_userTerminated = false; - if (!workingDirectory.isEmpty()) { m_perfRecordProcess->setWorkingDirectory(workingDirectory); } @@ -161,6 +130,36 @@ bool PerfRecord::runPerf(bool elevatePrivileges, const QStringList& perfOptions, return true; } +bool PerfRecord::runPerfRemote(const QStringList& perfOptions, const QString& outputPath, + const QString& workingDirectory) +{ + m_perfRecordProcess = m_host->remoteDevice()->runPerf(workingDirectory, perfOptions); + + auto output = new QFile(outputPath, m_perfRecordProcess.get()); + if (!output->open(QIODevice::WriteOnly)) { + emit recordingFailed(QStringLiteral("Failed to create output file: %1").arg(outputPath)); + return false; + } + + connect(m_perfRecordProcess.get(), &QProcess::readyReadStandardOutput, m_perfRecordProcess.get(), + [process = m_perfRecordProcess.get(), output] { + auto data = process->readAllStandardOutput(); + output->write(data); + }); + connect(m_perfRecordProcess.get(), &QProcess::readyReadStandardError, m_perfRecordProcess.get(), + [this] { emit recordingOutput(QString::fromUtf8(m_perfRecordProcess->readAllStandardError())); }); + + connect(m_perfRecordProcess.get(), qOverload(&QProcess::finished), this, [output] { + output->close(); + output->deleteLater(); + }); + + connectRecordingProcessErrors(); + + m_perfRecordProcess->start(); + return true; +} + void PerfRecord::record(const QStringList& perfOptions, const QString& outputPath, bool elevatePrivileges, const QStringList& pids) { @@ -174,27 +173,11 @@ void PerfRecord::record(const QStringList& perfOptions, const QString& outputPat runPerf(actuallyElevatePrivileges(elevatePrivileges), options, outputPath, {}); } -void PerfRecord::record(const QStringList& perfOptions, const QString& outputPath, bool elevatePrivileges, - const QString& exePath, const QStringList& exeOptions, const QString& workingDirectory) +void PerfRecord::record(const QStringList& perfOptions, const QString& outputPath, bool elevatePrivileges) { - QFileInfo exeFileInfo(exePath); - - if (!exeFileInfo.exists()) { - exeFileInfo.setFile(QStandardPaths::findExecutable(exePath)); - } - - if (!exeFileInfo.exists()) { - emit recordingFailed(tr("File '%1' does not exist.").arg(exePath)); - return; - } - if (!exeFileInfo.isFile()) { - emit recordingFailed(tr("'%1' is not a file.").arg(exePath)); - return; - } - if (!exeFileInfo.isExecutable()) { - emit recordingFailed(tr("File '%1' is not executable.").arg(exePath)); - return; - } + auto exePath = m_host->clientApplication(); + auto exeOptions = m_host->clientApplicationArguments(); + auto workingDirectory = m_host->currentWorkingDirectory(); QStringList options = perfOptions; if (actuallyElevatePrivileges(elevatePrivileges)) { @@ -211,7 +194,7 @@ void PerfRecord::record(const QStringList& perfOptions, const QString& outputPat m_perfControlFifo.requestStart(); } else { - options.append(exeFileInfo.absoluteFilePath()); + options.append(exePath); options += exeOptions; runPerf(false, options, outputPath, workingDirectory); } @@ -262,3 +245,33 @@ bool PerfRecord::actuallyElevatePrivileges(bool elevatePrivileges) const const auto capabilities = m_host->perfCapabilities(); return elevatePrivileges && capabilities.canElevatePrivileges && !capabilities.privilegesAlreadyElevated; } + +void PerfRecord::connectRecordingProcessErrors() +{ + connect(m_perfRecordProcess.get(), qOverload(&QProcess::finished), this, + [this](int exitCode, QProcess::ExitStatus exitStatus) { + Q_UNUSED(exitStatus) + + const auto outputFileInfo = QFileInfo(m_outputPath); + if ((exitCode == EXIT_SUCCESS || (exitCode == SIGTERM && m_userTerminated) || outputFileInfo.size() > 0) + && outputFileInfo.exists()) { + if (exitCode != EXIT_SUCCESS && !m_userTerminated) { + emit debuggeeCrashed(); + } + emit recordingFinished(m_outputPath); + } else { + emit recordingFailed(tr("Failed to record perf data, error code %1.").arg(exitCode)); + } + m_userTerminated = false; + }); + + connect(m_perfRecordProcess.get(), &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) { + Q_UNUSED(error) + if (!m_userTerminated) { + emit recordingFailed(m_perfRecordProcess->errorString()); + } + }); + + connect(m_perfRecordProcess.get(), &QProcess::started, this, + [this] { emit recordingStarted(m_perfRecordProcess->program(), m_perfRecordProcess->arguments()); }); +} diff --git a/src/perfrecord.h b/src/perfrecord.h index ebcc4ce2..429dc1d5 100644 --- a/src/perfrecord.h +++ b/src/perfrecord.h @@ -12,9 +12,10 @@ #include "perfcontrolfifowrapper.h" #include -#include +#include + +#include -class QProcess; class RecordHost; class PerfRecord : public QObject @@ -24,8 +25,8 @@ class PerfRecord : public QObject explicit PerfRecord(const RecordHost* host, QObject* parent = nullptr); ~PerfRecord(); - void record(const QStringList& perfOptions, const QString& outputPath, bool elevatePrivileges, - const QString& exePath, const QStringList& exeOptions, const QString& workingDirectory = QString()); + void record(const QStringList& perfOptions, const QString& outputPath, bool elevatePrivileges); + void record(const QStringList& perfOptions, const QString& outputPath, bool elevatePrivileges, const QStringList& pids); void recordSystem(const QStringList& perfOptions, const QString& outputPath); @@ -45,7 +46,7 @@ class PerfRecord : public QObject private: const RecordHost* m_host = nullptr; - QPointer m_perfRecordProcess; + std::unique_ptr m_perfRecordProcess; InitiallyStoppedProcess m_targetProcessForPrivilegedPerf; PerfControlFifoWrapper m_perfControlFifo; QString m_outputPath; @@ -55,4 +56,13 @@ class PerfRecord : public QObject bool runPerf(bool elevatePrivileges, const QStringList& perfOptions, const QString& outputPath, const QString& workingDirectory = QString()); + + bool runPerfLocal(bool elevatePrivileges, const QStringList& perfOptions, const QString& outputPath, + const QString& workingDirectory = QString()); + bool runPerfRemote(const QStringList& perfOptions, const QString& outputPath, + const QString& workingDirectory = QString()); + + bool runRemotePerf(const QStringList& perfOptions, const QString& outputPath, const QString& workingDirectory = {}); + + void connectRecordingProcessErrors(); }; diff --git a/src/recordhost.cpp b/src/recordhost.cpp index e588948c..ff503b0c 100644 --- a/src/recordhost.cpp +++ b/src/recordhost.cpp @@ -11,6 +11,9 @@ #include #include #include +#include + +#include #include #include @@ -142,6 +145,28 @@ RecordHost::PerfCapabilities fetchLocalPerfCapabilities(const QString& perfPath) return capabilities; } + +RecordHost::PerfCapabilities fetchRemotePerfCapabilities(const std::unique_ptr& device) +{ + RecordHost::PerfCapabilities capabilities; + + const auto buildOptions = device->getProgramOutput( + {QStringLiteral("perf"), QStringLiteral("version"), QStringLiteral("--build-options")}); + const auto help = device->getProgramOutput({QStringLiteral("perf"), QStringLiteral("--help")}); + + capabilities.canCompress = Zstd_FOUND && buildOptions.contains("zszd: [ on ]"); + capabilities.canSwitchEvents = help.contains("--switch-events"); + capabilities.canSampleCpu = help.contains("--sample-cpu"); + + // TODO: implement + capabilities.canProfileOffCpu = false; + capabilities.privilegesAlreadyElevated = false; + + capabilities.canUseAio = false; // AIO doesn't work with perf streaming + capabilities.canElevatePrivileges = false; // we currently don't support this + + return capabilities; +} } RecordHost::RecordHost(QObject* parent) @@ -171,7 +196,13 @@ bool RecordHost::isReady() const { switch (m_recordType) { case RecordType::LaunchApplication: - // client application is already validated in the setter + // client application is already validated in the setter + if (m_clientApplication.isEmpty() && m_cwd.isEmpty()) + return false; + break; + case RecordType::LaunchRemoteApplication: + if (!m_remoteDevice || !m_remoteDevice->isConnected()) + return false; if (m_clientApplication.isEmpty() && m_cwd.isEmpty()) return false; break; @@ -205,6 +236,17 @@ void RecordHost::setHost(const QString& host) m_host = host; emit hostChanged(); + if (!isLocal()) { + m_remoteDevice = std::make_unique(this); + + connect(m_remoteDevice.get(), &RemoteDevice::connected, this, &RecordHost::checkRequirements); + connect(m_remoteDevice.get(), &RemoteDevice::connected, this, [this] { emit isReadyChanged(isReady()); }); + connect(m_remoteDevice.get(), &RemoteDevice::failedToConnect, this, + [this] { emit errorOccurred(tr("Failed to connect to: %1").arg(m_host)); }); + } else { + m_remoteDevice = nullptr; + } + // invalidate everything m_cwd.clear(); emit currentWorkingDirectoryChanged(m_cwd); @@ -212,38 +254,18 @@ void RecordHost::setHost(const QString& host) m_clientApplication.clear(); emit clientApplicationChanged(m_clientApplication); + m_clientApplicationArguments.clear(); + emit clientApplicationArgumentsChanged(m_clientApplicationArguments); + m_perfCapabilities = {}; emit perfCapabilitiesChanged(m_perfCapabilities); - const auto perfPath = perfBinaryPath(); - m_checkPerfCapabilitiesJob.startJob([perfPath](auto&&) { return fetchLocalPerfCapabilities(perfPath); }, - [this](RecordHost::PerfCapabilities capabilities) { - Q_ASSERT(QThread::currentThread() == thread()); - - m_perfCapabilities = capabilities; - emit perfCapabilitiesChanged(m_perfCapabilities); - }); - - m_checkPerfInstalledJob.startJob( - [isLocal = isLocal(), perfPath](auto&&) { - if (isLocal) { - if (perfPath.isEmpty()) { - return !QStandardPaths::findExecutable(QStringLiteral("perf")).isEmpty(); - } - - return QFileInfo::exists(perfPath); - } - - qWarning() << "remote is not implemented"; - return false; - }, - [this](bool isInstalled) { - if (!isInstalled) { - emit errorOccurred(tr("perf is not installed")); - } - m_isPerfInstalled = isInstalled; - emit isPerfInstalledChanged(isInstalled); - }); + if (isLocal()) { + checkRequirements(); + } else { + // checkRequirements will be called via RemoteDevice::connected + m_remoteDevice->connectToDevice(m_host); + } } void RecordHost::setCurrentWorkingDirectory(const QString& cwd) @@ -264,16 +286,29 @@ void RecordHost::setCurrentWorkingDirectory(const QString& cwd) m_cwd = cwd; emit currentWorkingDirectoryChanged(cwd); } - return; + } else { + QTimer::singleShot(0, this, [this, cwd]() -> QCoro::Task { + bool exists = co_await m_remoteDevice->checkIfDirectoryExists(cwd); + if (!exists) { + emit errorOccurred(tr("Working directory folder cannot be found: %1").arg(cwd)); + } else { + emit errorOccurred({}); + m_cwd = cwd; + emit currentWorkingDirectoryChanged(m_cwd); + } + co_return; + }); } - - qWarning() << "is not implemented for remote"; } void RecordHost::setClientApplication(const QString& clientApplication) { Q_ASSERT(QThread::currentThread() == thread()); + if (m_clientApplication == clientApplication) { + return; + } + if (isLocal()) { QFileInfo application(KShell::tildeExpand(clientApplication)); if (!application.exists()) { @@ -295,38 +330,55 @@ void RecordHost::setClientApplication(const QString& clientApplication) if (m_cwd.isEmpty()) { setCurrentWorkingDirectory(application.dir().absolutePath()); } - return; + } else { + if (!m_remoteDevice->isConnected()) { + emit errorOccurred(tr("Hotspot is not connected to the remote device")); + } else { + QTimer::singleShot(0, this, [this, clientApplication]() -> QCoro::Task { + bool exists = co_await m_remoteDevice->checkIfFileExists(clientApplication); + if (!exists) { + emit errorOccurred(tr("Application file cannot be found: %1").arg(clientApplication)); + } else { + emit errorOccurred({}); + m_clientApplication = clientApplication; + emit clientApplicationChanged(m_clientApplication); + } + }); + } } - - qWarning() << "is not implemented for remote"; } -void RecordHost::setOutputFileName(const QString& filePath) +void RecordHost::setClientApplicationArguments(const QStringList& arguments) { - if (isLocal()) { - const auto perfDataExtension = QStringLiteral(".data"); - - const QFileInfo file(filePath); - const QFileInfo folder(file.absolutePath()); - - if (!folder.exists()) { - emit errorOccurred(tr("Output file directory folder cannot be found: %1").arg(folder.path())); - } else if (!folder.isDir()) { - emit errorOccurred(tr("Output file directory folder is not valid: %1").arg(folder.path())); - } else if (!folder.isWritable()) { - emit errorOccurred(tr("Output file directory folder is not writable: %1").arg(folder.path())); - } else if (!file.absoluteFilePath().endsWith(perfDataExtension)) { - emit errorOccurred(tr("Output file must end with %1").arg(perfDataExtension)); - } else { - emit errorOccurred({}); - m_outputFileName = filePath; - emit outputFileNameChanged(m_outputFileName); - } + Q_ASSERT(QThread::currentThread() == thread()); - return; + if (m_clientApplicationArguments != arguments) { + m_clientApplicationArguments = arguments; + emit clientApplicationArgumentsChanged(m_clientApplicationArguments); } +} - qWarning() << "is not implemented for remote"; +void RecordHost::setOutputFileName(const QString& filePath) +{ + const auto perfDataExtension = QStringLiteral(".data"); + const QFileInfo file(filePath); + const QFileInfo folder(file.absolutePath()); + + // the recording data is streamed from the device (currently) so there is no need to use different logic for + // local vs remote + if (!folder.exists()) { + emit errorOccurred(tr("Output file directory folder cannot be found: %1").arg(folder.path())); + } else if (!folder.isDir()) { + emit errorOccurred(tr("Output file directory folder is not valid: %1").arg(folder.path())); + } else if (!folder.isWritable()) { + emit errorOccurred(tr("Output file directory folder is not writable: %1").arg(folder.path())); + } else if (!file.absoluteFilePath().endsWith(perfDataExtension)) { + emit errorOccurred(tr("Output file must end with %1").arg(perfDataExtension)); + } else { + emit errorOccurred({}); + m_outputFileName = filePath; + emit outputFileNameChanged(m_outputFileName); + } } void RecordHost::setRecordType(RecordType type) @@ -368,3 +420,53 @@ QString RecordHost::perfBinaryPath() const } return {}; } + +void RecordHost::checkRequirements() +{ + const auto perfPath = perfBinaryPath(); + m_checkPerfCapabilitiesJob.startJob( + [isLocal = isLocal(), &remoteDevice = m_remoteDevice, perfPath](auto&&) { + if (isLocal) { + return fetchLocalPerfCapabilities(perfPath); + } else { + return fetchRemotePerfCapabilities(remoteDevice); + } + }, + [this](RecordHost::PerfCapabilities capabilities) { + Q_ASSERT(QThread::currentThread() == thread()); + + m_perfCapabilities = capabilities; + emit perfCapabilitiesChanged(m_perfCapabilities); + }); + + m_checkPerfInstalledJob.startJob( + [isLocal = isLocal(), &remoteDevice = m_remoteDevice, perfPath](auto&&) { + if (isLocal) { + if (perfPath.isEmpty()) { + return !QStandardPaths::findExecutable(QStringLiteral("perf")).isEmpty(); + } + + return QFileInfo::exists(perfPath); + } else { + return remoteDevice->checkIfProgramExists(QStringLiteral("perf")); + } + + return false; + }, + [this](bool isInstalled) { + if (!isInstalled) { + emit errorOccurred(tr("perf is not installed")); + } + m_isPerfInstalled = isInstalled; + emit isPerfInstalledChanged(isInstalled); + }); +} + +void RecordHost::disconnectFromDevice() +{ + if (!isLocal()) { + if (m_remoteDevice->isConnected()) { + m_remoteDevice->disconnect(); + } + } +} diff --git a/src/recordhost.h b/src/recordhost.h index c914193c..6bdce773 100644 --- a/src/recordhost.h +++ b/src/recordhost.h @@ -8,12 +8,16 @@ #pragma once #include "jobtracker.h" +#include "remotedevice.h" #include +#include + enum class RecordType { LaunchApplication, + LaunchRemoteApplication, AttachToProcess, ProfileSystem, NUM_RECORD_TYPES @@ -59,6 +63,12 @@ class RecordHost : public QObject } void setClientApplication(const QString& clientApplication); + QStringList clientApplicationArguments() const + { + return m_clientApplicationArguments; + } + void setClientApplicationArguments(const QStringList& arguments); + QString outputFileName() const { return m_outputFileName; @@ -96,6 +106,15 @@ class RecordHost : public QObject // list of pids to record void setPids(const QStringList& pids); + bool isLocal() const; + + const RemoteDevice* remoteDevice() const + { + return m_remoteDevice.get(); + } + + void disconnectFromDevice(); + signals: /// disallow "start" on recordpage until this is ready and that should only be the case when there's no error void isReadyChanged(bool isReady); @@ -104,6 +123,7 @@ class RecordHost : public QObject void hostChanged(); void currentWorkingDirectoryChanged(const QString& cwd); // Maybe QUrl void clientApplicationChanged(const QString& clientApplication); + void clientApplicationArgumentsChanged(const QStringList& arguments); void perfCapabilitiesChanged(RecordHost::PerfCapabilities perfCapabilities); void isPerfInstalledChanged(bool isInstalled); void outputFileNameChanged(const QString& outputFileName); @@ -111,12 +131,13 @@ class RecordHost : public QObject void pidsChanged(); private: - bool isLocal() const; + void checkRequirements(); QString m_host; QString m_error; QString m_cwd; QString m_clientApplication; + QStringList m_clientApplicationArguments; QString m_outputFileName; PerfCapabilities m_perfCapabilities; JobTracker m_checkPerfCapabilitiesJob; @@ -124,6 +145,7 @@ class RecordHost : public QObject RecordType m_recordType = RecordType::LaunchApplication; bool m_isPerfInstalled = false; QStringList m_pids; + std::unique_ptr m_remoteDevice; }; Q_DECLARE_METATYPE(RecordHost::PerfCapabilities) diff --git a/src/recordpage.cpp b/src/recordpage.cpp index a6935e4f..6f919118 100644 --- a/src/recordpage.cpp +++ b/src/recordpage.cpp @@ -40,10 +40,10 @@ #include #include -#include "multiconfigwidget.h" #include "perfoutputwidgetkonsole.h" #include "perfoutputwidgettext.h" #include "perfrecord.h" +#include "settings.h" namespace { bool isIntel() @@ -82,11 +82,15 @@ void updateStartRecordingButtonState(const RecordHost* host, const std::unique_p return; } + // TODO: move stuff to RecordHost bool enabled = false; switch (selectedRecordType(ui)) { case RecordType::LaunchApplication: enabled = ui->applicationName->url().isValid(); break; + case RecordType::LaunchRemoteApplication: + enabled = host->isReady(); + break; case RecordType::AttachToProcess: enabled = ui->processesTableView->selectionModel()->hasSelection(); break; @@ -192,13 +196,35 @@ RecordPage::RecordPage(QWidget* parent) } }); - connect(m_recordHost, &RecordHost::clientApplicationChanged, this, [this](const QString& filePath) { - const auto config = applicationConfig(filePath); - ui->workingDirectory->setText(config.readEntry("workingDir", QString())); - ui->applicationParametersBox->setText(config.readEntry("params", QString())); + ui->multiConfig->setChildWidget(ui->launchWidget, {ui->applicationParametersBox, ui->workingDirectory}); + + connect(m_recordHost, &RecordHost::clientApplicationChanged, this, + [this](const QString& app) { ui->multiConfig->setConfigGroup(applicationConfig(app)); }); + + connect(ui->workingDirectory, qOverload(&KUrlRequester::returnPressed), this, + [this](const QString& cwd) { + ui->multiConfig->saveCurrentConfig(); + m_recordHost->setCurrentWorkingDirectory(cwd); + }); + + connect(ui->applicationParametersBox, &QLineEdit::editingFinished, this, [this] { + ui->multiConfig->saveCurrentConfig(); + m_recordHost->setClientApplicationArguments(KShell::splitArgs(ui->applicationParametersBox->text())); + }); - m_multiConfig->setConfig(applicationConfig(ui->applicationName->text())); + auto settings = Settings::instance(); + connect(settings, &Settings::devicesChanged, this, [this](const QStringList& devices) { + ui->deviceComboBox->clear(); + ui->deviceComboBox->insertItem(0, QStringLiteral("localhost")); + ui->deviceComboBox->insertItems(1, devices); + ui->deviceComboBox->setCurrentIndex(0); }); + ui->deviceComboBox->insertItem(0, QStringLiteral("localhost")); + ui->deviceComboBox->insertItems(1, settings->devices()); + ui->deviceComboBox->setCurrentIndex(0); + + connect(ui->deviceComboBox, qOverload(&QComboBox::currentIndexChanged), this, + [this] { m_recordHost->setHost(ui->deviceComboBox->currentText()); }); ui->compressionComboBox->addItem(tr("Disabled"), -1); ui->compressionComboBox->addItem(tr("Enabled (Default Level)"), 0); @@ -247,6 +273,7 @@ RecordPage::RecordPage(QWidget* parent) }); m_recordHost->setHost(QStringLiteral("localhost")); + m_recordHost->setRecordType(RecordType::LaunchApplication); ui->applicationName->comboBox()->setEditable(true); ui->applicationName->setMode(KFile::File | KFile::ExistingOnly | KFile::LocalOnly); @@ -271,30 +298,6 @@ RecordPage::RecordPage(QWidget* parent) connect(m_perfOutput, &PerfOutputWidget::sendInput, this, [this](const QByteArray& input) { m_perfRecord->sendInput(input); }); - auto saveFunction = [this](KConfigGroup group) { - group.writeEntry("params", ui->applicationParametersBox->text()); - group.writeEntry("workingDir", ui->workingDirectory->text()); - }; - - auto restoreFunction = [this](const KConfigGroup& group) { - ui->applicationParametersBox->setText(group.readEntry("params", "")); - ui->workingDirectory->setText(group.readEntry("workingDir", "")); - setError({}); - }; - - m_multiConfig = new MultiConfigWidget(this); - - connect(m_multiConfig, &MultiConfigWidget::saveConfig, this, saveFunction); - connect(m_multiConfig, &MultiConfigWidget::restoreConfig, this, restoreFunction); - - m_multiConfig->setConfig(applicationConfig(ui->applicationName->text())); - - ui->launchAppBox->layout()->addWidget(m_multiConfig); - - connect(ui->applicationParametersBox, &QLineEdit::editingFinished, m_multiConfig, - &MultiConfigWidget::updateCurrentConfig); - connect(ui->workingDirectory, &KUrlRequester::textChanged, m_multiConfig, &MultiConfigWidget::updateCurrentConfig); - auto columnResizer = new KColumnResizer(this); columnResizer->addWidgetsFromLayout(ui->formLayout); columnResizer->addWidgetsFromLayout(ui->formLayout_1); @@ -306,7 +309,11 @@ RecordPage::RecordPage(QWidget* parent) connect(ui->startRecordingButton, &QPushButton::toggled, this, &RecordPage::onStartRecordingButtonClicked); connect(ui->workingDirectory, &KUrlRequester::textChanged, m_recordHost, &RecordHost::currentWorkingDirectoryChanged); - connect(ui->viewPerfRecordResultsButton, &QPushButton::clicked, this, [this] { emit openFile(m_resultsFile); }); + connect(ui->viewPerfRecordResultsButton, &QPushButton::clicked, this, [this] { + // we are done recording, disconnect from device + m_recordHost->disconnectFromDevice(); + emit openFile(m_resultsFile); + }); connect(ui->outputFile, &KUrlRequester::textChanged, this, &RecordPage::onOutputFileNameChanged); connect(ui->outputFile, static_cast(&KUrlRequester::returnPressed), this, &RecordPage::onOutputFileNameSelected); @@ -314,15 +321,18 @@ RecordPage::RecordPage(QWidget* parent) ui->recordTypeComboBox->addItem(QIcon::fromTheme(QStringLiteral("run-build")), tr("Launch Application"), QVariant::fromValue(RecordType::LaunchApplication)); + ui->recordTypeComboBox->addItem(QIcon::fromTheme(QStringLiteral("run-build")), tr("Launch Remote Application"), + QVariant::fromValue(RecordType::LaunchRemoteApplication)); ui->recordTypeComboBox->addItem(QIcon::fromTheme(QStringLiteral("run-install")), tr("Attach To Process(es)"), QVariant::fromValue(RecordType::AttachToProcess)); ui->recordTypeComboBox->addItem(QIcon::fromTheme(QStringLiteral("run-build-install-root")), tr("Profile System"), QVariant::fromValue(RecordType::ProfileSystem)); - connect(ui->recordTypeComboBox, qOverload(&QComboBox::currentIndexChanged), this, - &RecordPage::updateRecordType); + connect(ui->recordTypeComboBox, qOverload(&QComboBox::currentIndexChanged), m_recordHost, [this] { m_recordHost->setRecordType(ui->recordTypeComboBox->currentData().value()); }); - connect(m_recordHost, &RecordHost::clientApplicationChanged, this, &RecordPage::updateRecordType); + + connect(m_recordHost, &RecordHost::recordTypeChanged, this, &RecordPage::updateRecordType); + updateRecordType(RecordType::LaunchApplication); { ui->callGraphComboBox->addItem(tr("None"), QVariant::fromValue(QString())); @@ -528,7 +538,7 @@ void RecordPage::showRecordPage() { m_resultsFile.clear(); setError({}); - updateRecordType(); + m_recordHost->setRecordType(RecordType::LaunchApplication); ui->viewPerfRecordResultsButton->setEnabled(false); } @@ -645,14 +655,27 @@ void RecordPage::onStartRecordingButtonClicked(bool checked) switch (recordType) { case RecordType::LaunchApplication: { const auto applicationName = m_recordHost->clientApplication(); - const auto appParameters = ui->applicationParametersBox->text(); + const auto appParameters = m_recordHost->clientApplicationArguments(); auto workingDir = m_recordHost->currentWorkingDirectory(); if (workingDir.isEmpty()) { workingDir = ui->workingDirectory->placeholderText(); } - rememberApplication(applicationName, appParameters, workingDir, ui->applicationName->comboBox()); - m_perfRecord->record(perfOptions, outputFile, elevatePrivileges, applicationName, - KShell::splitArgs(appParameters), workingDir); + rememberApplication(applicationName, appParameters.join(QLatin1Char(' ')), workingDir, + ui->applicationName->comboBox()); + m_perfRecord->record(perfOptions, outputFile, elevatePrivileges); + break; + } + case RecordType::LaunchRemoteApplication: { + // TODO: network record + const auto applicationName = m_recordHost->clientApplication(); + const auto appParameters = m_recordHost->clientApplicationArguments(); + auto workingDir = m_recordHost->currentWorkingDirectory(); + if (workingDir.isEmpty()) { + workingDir = ui->workingDirectory->placeholderText(); + } + rememberApplication(applicationName, appParameters.join(QLatin1Char(' ')), workingDir, + ui->applicationName->comboBox()); + m_perfRecord->record(perfOptions, outputFile, elevatePrivileges); break; } case RecordType::AttachToProcess: { @@ -752,17 +775,24 @@ void RecordPage::setError(const QString& message) ui->applicationRecordErrorMessage->setVisible(!message.isEmpty()); } -void RecordPage::updateRecordType() +void RecordPage::updateRecordType(RecordType recordType) { setError({}); - const auto recordType = selectedRecordType(ui); - ui->launchAppBox->setVisible(recordType == RecordType::LaunchApplication); + ui->launchAppBox->setVisible(recordType == RecordType::LaunchApplication + || recordType == RecordType::LaunchRemoteApplication); ui->attachAppBox->setVisible(recordType == RecordType::AttachToProcess); + ui->remoteDeviceBox->setVisible(recordType == RecordType::LaunchRemoteApplication); m_perfOutput->setInputVisible(recordType == RecordType::LaunchApplication); m_perfOutput->clear(); + if (recordType == RecordType::LaunchRemoteApplication) { + ui->applicationName->clear(); + } else if (recordType == RecordType::LaunchApplication) { + restoreCombobox(config(), QStringLiteral("applications"), ui->applicationName->comboBox()); + } + if (recordType == RecordType::AttachToProcess) { updateProcesses(); } diff --git a/src/recordpage.h b/src/recordpage.h index ad2c09e8..0ed4fa41 100644 --- a/src/recordpage.h +++ b/src/recordpage.h @@ -27,7 +27,6 @@ class RecordPage; class PerfRecord; class ProcessModel; class ProcessFilterModel; -class MultiConfigWidget; class PerfOutputWidget; namespace KParts { @@ -60,7 +59,7 @@ private slots: private: void recordingStopped(); - void updateRecordType(); + void updateRecordType(RecordType type); void appendOutput(const QString& text); void setError(const QString& message); @@ -73,7 +72,6 @@ private slots: QTimer* m_updateRuntimeTimer; KParts::ReadOnlyPart* m_konsolePart = nullptr; QTemporaryFile* m_konsoleFile = nullptr; - MultiConfigWidget* m_multiConfig; PerfOutputWidget* m_perfOutput; ProcessModel* m_processModel; diff --git a/src/recordpage.ui b/src/recordpage.ui index 0c53b30f..16a2b52e 100644 --- a/src/recordpage.ui +++ b/src/recordpage.ui @@ -50,78 +50,118 @@ - + - Launch Application + Remote Device - - - QFormLayout::AllNonFixedFieldsGrow - + - - - Path to the application to be recorded - + - App&lication: - - - applicationName + Device: - - - Path to the application to be recorded - - - - - + - - - - Optional parameters to pass to the application being recorded - - - Parame&ters: - - - applicationParametersBox - - - - - - - Qt::NoContextMenu - - - Optional parameters to pass to the application being recorded - - - - - - - Directory to store the perf data file while recording - - - Wor&king Directory: - - - workingDirectory - - + + + + + + + Launch Application + + + + - - - - Directory to store the perf data file while recording - + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Path to the application to be recorded + + + + + + + + + + Path to the application to be recorded + + + App&lication: + + + applicationName + + + + + + + Optional parameters to pass to the application being recorded + + + Parame&ters: + + + applicationParametersBox + + + + + + + Directory to store the perf data file while recording + + + Wor&king Directory: + + + workingDirectory + + + + + + + Directory to store the perf data file while recording + + + + + + + Qt::NoContextMenu + + + Optional parameters to pass to the application being recorded + + + + @@ -620,6 +660,12 @@
kmessagewidget.h
1 + + MultiConfigWidget + QWidget +
multiconfigwidget.h
+ 1 +
diff --git a/src/remotedevice.cpp b/src/remotedevice.cpp new file mode 100644 index 00000000..8cc89ecf --- /dev/null +++ b/src/remotedevice.cpp @@ -0,0 +1,191 @@ +/* + SPDX-FileCopyrightText: Lieven Hey + SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "remotedevice.h" + +#include +#include +#include + +#include + +#include + +#include "settings.h" + +namespace { +QStringList sshArgs(const QString& dir) +{ + return {QStringLiteral("-o"), QStringLiteral("ControlMaster=auto"), QStringLiteral("-o"), + QStringLiteral("ControlPath=%1/ssh").arg(dir)}; +} + +void setupProcess(const std::unique_ptr& process, const QString& sshBinary, const KConfigGroup& config, + const QString& path, const QStringList& args) +{ + process->setProgram(sshBinary); + const auto options = config.readEntry("options", QString {}); + const auto username = config.readEntry("username", QString {}); + const auto hostname = config.readEntry("hostname", QString {}); + + auto arguments = sshArgs(path); + if (!options.isEmpty()) { + arguments.append(options.split(QLatin1Char(' '))); + } + + arguments.append(QStringLiteral("%1@%2").arg(username, hostname)); + if (!args.isEmpty()) { + arguments.append(args); + } + process->setArguments(arguments); +} +} + +RemoteDevice::RemoteDevice(QObject* parent) + : QObject(parent) +{ + Q_ASSERT(m_tempDir.isValid()); + m_watcher.addPath(m_tempDir.path()); + + connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, [this](const QString& path) { + // this could also be a delete so we need to check if the socket exists + if (QFile::exists(path + QStringLiteral("/ssh"))) { + // ssh only creates that file when the connection was established + emit connected(); + } + }); + + auto findSSHBinary = [this](const QString& binary) { + if (binary.isEmpty()) { + m_sshBinary = QStandardPaths::findExecutable(QStringLiteral("ssh")); + } else { + m_sshBinary = binary; + } + }; + + auto settings = Settings::instance(); + findSSHBinary(settings->sshPath()); + connect(settings, &Settings::sshPathChanged, this, findSSHBinary); +} + +RemoteDevice::~RemoteDevice() +{ + if (m_connection) { + disconnect(); + QTimer::singleShot(0, this, [connection = m_connection.release()]() -> QCoro::Task { + co_await qCoro(connection).waitForFinished(); + delete connection; + }); + } +} + +void RemoteDevice::connectToDevice(const QString& device) +{ + if (m_connection) { + disconnect(); + } + auto config = KSharedConfig::openConfig()->group("SSH"); + if (!config.hasGroup(device) && config.group(device).exists()) { + emit failedToConnect(); + return; + } + m_config = config.group(device); + + m_connection = sshProcess({}); + + connect(m_connection.get(), qOverload(&QProcess::finished), this, + [path = m_tempDir.path(), this](int exitCode, QProcess::ExitStatus) { + if (exitCode != 0) { + emit failedToConnect(); + } else { + emit disconnected(); + } + m_connection = nullptr; + }); + + m_connection->start(); +} + +void RemoteDevice::disconnect() +{ + if (m_connection) { + if (m_connection->state() == QProcess::ProcessState::Running) { + // ssh stops once you close the write channel + // we then use the finished signal for cleanup + m_connection->closeWriteChannel(); + } + } +} + +bool RemoteDevice::isConnected() const +{ + return QFile(m_tempDir.path() + QStringLiteral("/ssh")).exists(); +} + +bool RemoteDevice::checkIfProgramExists(const QString& program) const +{ + Q_ASSERT(QThread::currentThread() != thread()); + // this is only used to check if perf is installed and is run in a background thread + auto ssh = sshProcess({QStringLiteral("command"), program}); + ssh->start(); + ssh->waitForFinished(); + auto exitCode = ssh->exitCode(); + // 128 -> not found + // perf will return 1 and display the help message + return exitCode != 128; +} + +QCoro::Task RemoteDevice::checkIfDirectoryExists(const QString& directory) const +{ + auto _ssh = sshProcess({QStringLiteral("test"), QStringLiteral("-d"), directory}); + auto ssh = qCoro(_ssh.get()); + co_await ssh.start(); + co_await ssh.waitForFinished(); + auto exitCode = _ssh->exitCode(); + co_return exitCode == 0; +} + +QCoro::Task RemoteDevice::checkIfFileExists(const QString& file) const +{ + auto _ssh = sshProcess({QStringLiteral("test"), QStringLiteral("-f"), file}); + + auto ssh = qCoro(_ssh.get()); + co_await ssh.start(); + co_await ssh.waitForFinished(); + auto exitCode = _ssh->exitCode(); + co_return exitCode == 0; +} + +QByteArray RemoteDevice::getProgramOutput(const QStringList& args) const +{ + auto ssh = sshProcess(args); + ssh->start(); + ssh->waitForFinished(); + auto output = ssh->readAllStandardOutput(); + return output; +} + +std::unique_ptr RemoteDevice::sshProcess(const QStringList& args) const +{ + if (!m_config.isValid()) { + return nullptr; + } + + auto process = std::make_unique(nullptr); + setupProcess(process, m_sshBinary, m_config, m_tempDir.path(), args); + + return process; +} + +std::unique_ptr RemoteDevice::runPerf(const QString& cwd, const QStringList& perfOptions) const +{ + const auto perfCommand = QStringLiteral("perf record -o - %1 ").arg(perfOptions.join(QLatin1Char(' '))); + const QString command = QStringLiteral("cd %1 ; %2").arg(cwd, perfCommand); + auto process = sshProcess({QStringLiteral("sh"), QStringLiteral("-c"), QStringLiteral("\"%1\"").arg(command)}); + + return process; +} diff --git a/src/remotedevice.h b/src/remotedevice.h new file mode 100644 index 00000000..5bc19a96 --- /dev/null +++ b/src/remotedevice.h @@ -0,0 +1,55 @@ +/* + SPDX-FileCopyrightText: Lieven Hey + SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include + +#include +#include + +#include + +#include + +#include + +class QProcess; + +class RemoteDevice : public QObject +{ + Q_OBJECT +public: + RemoteDevice(QObject* parent = nullptr); + ~RemoteDevice() override; + + void connectToDevice(const QString& device); + void disconnect(); + + bool isConnected() const; + + bool checkIfProgramExists(const QString& program) const; + QCoro::Task checkIfDirectoryExists(const QString& directory) const; + QCoro::Task checkIfFileExists(const QString& file) const; + QByteArray getProgramOutput(const QStringList& args) const; + + std::unique_ptr runPerf(const QString& cdw, const QStringList& perfOptions) const; + +signals: + void connected(); + void disconnected(); + void failedToConnect(); + +private: + std::unique_ptr sshProcess(const QStringList& args) const; + + std::unique_ptr m_connection = nullptr; + QTemporaryDir m_tempDir; + QFileSystemWatcher m_watcher; + KConfigGroup m_config; + QString m_sshBinary; +}; diff --git a/src/settings.cpp b/src/settings.cpp index 03c33a9c..bed0bc97 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -243,6 +243,20 @@ void Settings::loadFromFile() connect(this, &Settings::showBranchesChanged, [sharedConfig](bool showBranches) { sharedConfig->group("Disassembly").writeEntry("showBranches", showBranches); }); + + setSSHPath(sharedConfig->group("SSH").readEntry("ssh")); + setSSHCopyKeyPath(sharedConfig->group("SSH").readEntry("ssh-copy-id")); + + const auto& ssh = sharedConfig->group("SSH"); + setSSHPath(ssh.readEntry("ssh")); + setSSHCopyKeyPath(ssh.readEntry("ssh-copy-id")); + + connect(this, &Settings::sshPathChanged, this, + [sharedConfig](const QString& path) { sharedConfig->group("SSH").writeEntry("ssh", path); }); + connect(this, &Settings::sshCopyIdPathChanged, this, + [sharedConfig](const QString& path) { sharedConfig->group("SSH").writeEntry("ssh-copy-id", path); }); + + setDevices(ssh.groupList()); } void Settings::setSourceCodePaths(const QString& paths) @@ -268,3 +282,27 @@ void Settings::setShowBranches(bool showBranches) emit showBranchesChanged(m_showBranches); } } + +void Settings::setSSHPath(const QString& sshPath) +{ + if (m_sshPath != sshPath) { + m_sshPath = sshPath; + emit sshPathChanged(m_sshPath); + } +} + +void Settings::setSSHCopyKeyPath(const QString& sshCopyIdPath) +{ + if (m_sshCopyIdPath != sshCopyIdPath) { + m_sshCopyIdPath = sshCopyIdPath; + emit sshCopyIdPathChanged(m_sshCopyIdPath); + } +} + +void Settings::setDevices(const QStringList& devices) +{ + if (m_devices != devices) { + m_devices = devices; + emit devicesChanged(m_devices); + } +} diff --git a/src/settings.h b/src/settings.h index b038a5af..fd42be0a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -159,6 +159,21 @@ class Settings : public QObject return m_showBranches; } + QString sshPath() const + { + return m_sshPath; + } + + QString sshCopyIdPath() const + { + return m_sshCopyIdPath; + } + + QStringList devices() const + { + return m_devices; + } + void loadFromFile(); signals: @@ -182,6 +197,9 @@ class Settings : public QObject void sourceCodePathsChanged(const QString& paths); void perfPathChanged(const QString& perfPath); void showBranchesChanged(bool showBranches); + void sshPathChanged(const QString& sshPath); + void sshCopyIdPathChanged(const QString& sshCopyIdPath); + void devicesChanged(const QStringList& devices); public slots: void setPrettifySymbols(bool prettifySymbols); @@ -206,6 +224,9 @@ public slots: void setSourceCodePaths(const QString& paths); void setPerfPath(const QString& path); void setShowBranches(bool showBranches); + void setSSHPath(const QString& path); + void setSSHCopyKeyPath(const QString& path); + void setDevices(const QStringList& devices); private: Settings() = default; @@ -239,4 +260,8 @@ public slots: QColor m_callgraphColor; QString m_perfPath; + QString m_sshPath; + QString m_sshCopyIdPath; + + QStringList m_devices; }; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 92b1a73a..e257af40 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -13,6 +13,8 @@ #include "ui_disassemblysettingspage.h" #include "ui_flamegraphsettingspage.h" #include "ui_perfsettingspage.h" +#include "ui_sourcepathsettings.h" +#include "ui_sshsettingspage.h" #include "ui_unwindsettingspage.h" #include "multiconfigwidget.h" @@ -26,15 +28,11 @@ #include #include #include +#include #include namespace { -KConfigGroup config() -{ - return KSharedConfig::openConfig()->group("PerfPaths"); -} - QPushButton* setupMultiPath(KEditListWidget* listWidget, QLabel* buddy, QWidget* previous) { auto editor = new KUrlRequester(listWidget); @@ -68,6 +66,7 @@ SettingsDialog::SettingsDialog(QWidget* parent) #if KGraphViewerPart_FOUND , callgraphPage(new Ui::CallgraphSettingsPage) #endif + , sshSettingsPage(new Ui::SSHSettingsPage) { addPerfSettingsPage(); addPathSettingsPage(); @@ -77,6 +76,7 @@ SettingsDialog::SettingsDialog(QWidget* parent) addCallgraphPage(); #endif addSourcePathPage(); + addSSHPage(); } SettingsDialog::~SettingsDialog() = default; @@ -85,44 +85,18 @@ void SettingsDialog::initSettings() { const auto configName = Settings::instance()->lastUsedEnvironment(); if (!configName.isEmpty()) { - m_configs->selectConfig(configName); + unwindPage->multiConfig->loadConfig(configName); } } -void SettingsDialog::initSettings(const QString& sysroot, const QString& appPath, const QString& extraLibPaths, - const QString& debugPaths, const QString& kallsyms, const QString& arch, - const QString& objdump) -{ - auto fromPathString = [](KEditListWidget* listWidget, const QString& string) { - listWidget->setItems(string.split(QLatin1Char(':'), Qt::SkipEmptyParts)); - }; - fromPathString(unwindPage->extraLibraryPaths, extraLibPaths); - fromPathString(unwindPage->debugPaths, debugPaths); - - unwindPage->lineEditSysroot->setText(sysroot); - unwindPage->lineEditApplicationPath->setText(appPath); - unwindPage->lineEditKallsyms->setText(kallsyms); - unwindPage->lineEditObjdump->setText(objdump); - - int itemIndex = 0; - if (!arch.isEmpty()) { - itemIndex = unwindPage->comboBoxArchitecture->findText(arch); - if (itemIndex == -1) { - itemIndex = unwindPage->comboBoxArchitecture->count(); - unwindPage->comboBoxArchitecture->addItem(arch); - } - } - unwindPage->comboBoxArchitecture->setCurrentIndex(itemIndex); -} - QString SettingsDialog::sysroot() const { - return unwindPage->lineEditSysroot->text(); + return unwindPage->sysroot->text(); } QString SettingsDialog::appPath() const { - return unwindPage->lineEditApplicationPath->text(); + return unwindPage->appPath->text(); } QString SettingsDialog::extraLibPaths() const @@ -137,18 +111,18 @@ QString SettingsDialog::debugPaths() const QString SettingsDialog::kallsyms() const { - return unwindPage->lineEditKallsyms->text(); + return unwindPage->kallsyms->text(); } QString SettingsDialog::arch() const { - const auto sArch = unwindPage->comboBoxArchitecture->currentText(); + const auto sArch = unwindPage->arch->currentText(); return (sArch == QLatin1String("auto-detect")) ? QString() : sArch; } QString SettingsDialog::objdump() const { - return unwindPage->lineEditObjdump->text(); + return unwindPage->objdump->text(); } QString SettingsDialog::perfMapPath() const @@ -178,61 +152,22 @@ void SettingsDialog::addPathSettingsPage() auto item = addPage(page, tr("Unwinding")); item->setHeader(tr("Unwind Options")); item->setIcon(icon()); - unwindPage->setupUi(page); - auto lastExtraLibsWidget = setupMultiPath(unwindPage->extraLibraryPaths, unwindPage->extraLibraryPathsLabel, - unwindPage->lineEditApplicationPath); - setupMultiPath(unwindPage->debugPaths, unwindPage->debugPathsLabel, lastExtraLibsWidget); - - auto* label = new QLabel(this); - label->setText(tr("Config:")); - - auto saveFunction = [this](KConfigGroup group) { - group.writeEntry("sysroot", sysroot()); - group.writeEntry("appPath", appPath()); - group.writeEntry("extraLibPaths", extraLibPaths()); - group.writeEntry("debugPaths", debugPaths()); - group.writeEntry("kallsyms", kallsyms()); - group.writeEntry("arch", arch()); - group.writeEntry("objdump", objdump()); - }; - - auto restoreFunction = [this](const KConfigGroup& group) { - const auto sysroot = group.readEntry("sysroot"); - const auto appPath = group.readEntry("appPath"); - const auto extraLibPaths = group.readEntry("extraLibPaths"); - const auto debugPaths = group.readEntry("debugPaths"); - const auto kallsyms = group.readEntry("kallsyms"); - const auto arch = group.readEntry("arch"); - const auto objdump = group.readEntry("objdump"); - initSettings(sysroot, appPath, extraLibPaths, debugPaths, kallsyms, arch, objdump); - ::config().writeEntry("lastUsed", m_configs->currentConfig()); - }; - - m_configs = new MultiConfigWidget(this); - m_configs->setConfig(config()); - m_configs->restoreCurrent(); - - connect(m_configs, &MultiConfigWidget::saveConfig, this, saveFunction); - connect(m_configs, &MultiConfigWidget::restoreConfig, this, restoreFunction); - - unwindPage->formLayout->insertRow(0, label, m_configs); - - connect(this, &KPageDialog::accepted, this, [this] { m_configs->updateCurrentConfig(); }); - - for (auto field : {unwindPage->lineEditSysroot, unwindPage->lineEditApplicationPath, unwindPage->lineEditKallsyms, - unwindPage->lineEditObjdump}) { - connect(field, &KUrlRequester::textEdited, m_configs, &MultiConfigWidget::updateCurrentConfig); - connect(field, &KUrlRequester::urlSelected, m_configs, &MultiConfigWidget::updateCurrentConfig); - } + unwindPage->multiConfig->setConfigGroup(KSharedConfig::openConfig()->group("PerfPaths")); + unwindPage->multiConfig->setChildWidget(unwindPage->widget, + {unwindPage->sysroot, unwindPage->appPath, unwindPage->extraLibraryPaths, + unwindPage->debugPaths, unwindPage->kallsyms, unwindPage->arch, + unwindPage->objdump}); - connect(unwindPage->comboBoxArchitecture, QOverload::of(&QComboBox::currentIndexChanged), m_configs, - &MultiConfigWidget::updateCurrentConfig); + auto lastExtraLibsWidget = + setupMultiPath(unwindPage->extraLibraryPaths, unwindPage->extraLibraryPathsLabel, unwindPage->appPath); + setupMultiPath(unwindPage->debugPaths, unwindPage->debugPathsLabel, lastExtraLibsWidget); - connect(unwindPage->debugPaths, &KEditListWidget::changed, m_configs, &MultiConfigWidget::updateCurrentConfig); - connect(unwindPage->extraLibraryPaths, &KEditListWidget::changed, m_configs, - &MultiConfigWidget::updateCurrentConfig); + connect(buttonBox(), &QDialogButtonBox::accepted, this, [this] { + unwindPage->multiConfig->saveCurrentConfig(); + Settings::instance()->setLastUsedEnvironment(unwindPage->multiConfig->currentConfig()); + }); } void SettingsDialog::keyPressEvent(QKeyEvent* event) @@ -342,3 +277,74 @@ void SettingsDialog::addSourcePathPage() settings->setShowBranches(disassemblyPage->showBranches->isChecked()); }); } + +void SettingsDialog::addSSHPage() +{ + auto page = new QWidget(this); + auto item = addPage(page, tr("SSH Settings")); + item->setHeader(tr("SSH Settings Page")); + item->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-behavior"))); + sshSettingsPage->setupUi(page); + sshSettingsPage->messageWidget->hide(); + sshSettingsPage->errorWidget->hide(); + + bool ksshaskpassFound = !QStandardPaths()::findExe(QStringLiteral("ksshaskpass")).isEmpty(); + if (!qEnvironmentVariableIsSet("SSH_ASKPASS") && !ksshaskpassFound) { + sshSettingsPage->errorWidget->setText( + tr("SSH_ASKPASS is not set please install ksshaskpass or set SSH_ASKPASS")); + sshSettingsPage->errorWidget->show(); + } + + auto configGroup = KSharedConfig::openConfig()->group("SSH"); + sshSettingsPage->deviceConfig->setChildWidget( + sshSettingsPage->deviceSettings, + {sshSettingsPage->username, sshSettingsPage->hostname, sshSettingsPage->options}); + sshSettingsPage->deviceConfig->setConfigGroup(configGroup); + + connect(sshSettingsPage->copySshKeyButton, &QPushButton::pressed, this, [this] { + auto* copyKey = new QProcess(this); + + auto path = sshSettingsPage->sshCopyIdPath->text(); + if (path.isEmpty()) { + path = QStandardPaths::findExecutable(QStringLiteral("ssh-copy-id")); + } + if (path.isEmpty()) { + sshSettingsPage->messageWidget->setText(tr("Could not find ssh-copy-id")); + sshSettingsPage->messageWidget->show(); + return; + } + + copyKey->setProgram(path); + + QStringList arguments = {}; + auto options = sshSettingsPage->options->text(); + if (!options.isEmpty()) { + arguments.append(options.split(QLatin1Char(' '))); + } + arguments.append( + QStringLiteral("%1@%2").arg(sshSettingsPage->username->text(), sshSettingsPage->hostname->text())); + copyKey->setArguments(arguments); + + connect(copyKey, qOverload(&QProcess::finished), this, + [this, copyKey](int code, QProcess::ExitStatus) { + if (code == 0) { + sshSettingsPage->messageWidget->setText(QStringLiteral("Copy key successfully")); + sshSettingsPage->messageWidget->show(); + } else { + sshSettingsPage->errorWidget->setText(QStringLiteral("Failed to copy key")); + sshSettingsPage->errorWidget->show(); + } + copyKey->deleteLater(); + }); + copyKey->start(); + }); + + connect(buttonBox(), &QDialogButtonBox::accepted, this, [this, configGroup] { + sshSettingsPage->deviceConfig->saveCurrentConfig(); + + auto settings = Settings::instance(); + settings->setDevices(configGroup.groupList()); + settings->setSSHPath(sshSettingsPage->sshBinary->text()); + settings->setSSHCopyKeyPath(sshSettingsPage->sshCopyIdPath->text()); + }); +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 0b396d1a..1288093f 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -19,6 +19,7 @@ class DebuginfodPage; class CallgraphSettingsPage; class DisassemblySettingsPage; class PerfSettingsPage; +class SSHSettingsPage; } class MultiConfigWidget; @@ -51,6 +52,7 @@ class SettingsDialog : public KPageDialog void addDebuginfodPage(); void addCallgraphPage(); void addSourcePathPage(); + void addSSHPage(); std::unique_ptr perfPage; std::unique_ptr unwindPage; @@ -58,5 +60,5 @@ class SettingsDialog : public KPageDialog std::unique_ptr debuginfodPage; std::unique_ptr disassemblyPage; std::unique_ptr callgraphPage; - MultiConfigWidget* m_configs; + std::unique_ptr sshSettingsPage; }; diff --git a/src/sshsettingspage.ui b/src/sshsettingspage.ui new file mode 100644 index 00000000..7151bfd8 --- /dev/null +++ b/src/sshsettingspage.ui @@ -0,0 +1,188 @@ + + + SSHSettingsPage + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + KMessageWidget::Error + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Hostname: + + + + + + + + + + Username: + + + + + + + + + + Options: + + + + + + + + + + Copy SSH Key + + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + ssh: + + + + + + + ssh + + + + + + + ssh-copy-id: + + + + + + + ssh-copy-id + + + + + + + + + + + KUrlRequester + QWidget +
kurlrequester.h
+
+ + KMessageWidget + QFrame +
kmessagewidget.h
+ 1 +
+ + MultiConfigWidget + QWidget +
multiconfigwidget.h
+ 1 +
+
+ + +
diff --git a/src/unwindsettingspage.ui b/src/unwindsettingspage.ui index 5c044135..066a7164 100644 --- a/src/unwindsettingspage.ui +++ b/src/unwindsettingspage.ui @@ -16,186 +16,210 @@ true - - - - - Path to the sysroot. Leave empty to use the local machine. - - - local machine - - - - - - - Path to the application binary and library. - - - Application Path: - - - lineEditApplicationPath - - - - - - - Path to the application binary and library. - - - auto-detect - - - - - - - Qt::TabFocus - - - List of paths that contain additional libraries. - - - - - - - List of that contain debug information. - - - Debug Paths: - - - - - - - Qt::TabFocus - - - List of paths that contain debug information. - - - - - - - Path to the kernel symbol mapping. - - - Kallsyms: - - - lineEditKallsyms - - - - - - - Path to the kernel symbol mapping. - - - auto-detect - - - - - - - - - - System architecture, e.g. x86_64, arm, aarch64 etc. - - - Architecture: - - - comboBoxArchitecture - - - - - - - System architecture, e.g. x86_64, arm, aarch64 etc. - - - true - - - - auto-detect - - - - - x64 - - - - - ARMv7 (32 bit) - - - - - ARMv8 (64 bit) - - - - - - - - <qt>The <tt>objdump</tt> binary that will be used for the disassembly view. Leave empty to let it be auto-detected.</qt> - - - Objdump: - - - lineEditObjdump - - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + - - - - <qt>The <tt>objdump</tt> binary that will be used for the disassembly view. Leave empty to let it be auto-detected.</qt> - - - auto-detect - - - - - - - - - - List of paths that contain additional libraries. - - - Extra Library Paths: - - - - - - - Path to the sysroot. Leave empty to use the local machine. - - - Sysroot: - - - lineEditSysroot - + + + + + + + Path to the application binary and library. + + + auto-detect + + + + + + + Path to the sysroot. Leave empty to use the local machine. + + + local machine + + + + + + + Qt::TabFocus + + + List of paths that contain additional libraries. + + + + + + + List of paths that contain additional libraries. + + + Extra Library Paths: + + + + + + + Qt::TabFocus + + + List of paths that contain debug information. + + + + + + + Path to the kernel symbol mapping. + + + auto-detect + + + + + + + + + + Path to the kernel symbol mapping. + + + Kallsyms: + + + kallsyms + + + + + + + List of that contain debug information. + + + Debug Paths: + + + + + + + Path to the application binary and library. + + + Application Path: + + + appPath + + + + + + + Path to the sysroot. Leave empty to use the local machine. + + + Sysroot: + + + sysroot + + + + + + + System architecture, e.g. x86_64, arm, aarch64 etc. + + + Architecture: + + + arch + + + + + + + System architecture, e.g. x86_64, arm, aarch64 etc. + + + true + + + + auto-detect + + + + + x64 + + + + + ARMv7 (32 bit) + + + + + ARMv8 (64 bit) + + + + + + + + <qt>The <tt>objdump</tt> binary that will be used for the disassembly view. Leave empty to let it be auto-detected.</qt> + + + auto-detect + + + + + + + + + + <qt>The <tt>objdump</tt> binary that will be used for the disassembly view. Leave empty to let it be auto-detected.</qt> + + + Objdump: + + + objdump + + + + @@ -225,16 +249,13 @@ QWidget
keditlistwidget.h
+ + MultiConfigWidget + QWidget +
multiconfigwidget.h
+ 1 +
- - lineEditSysroot - lineEditApplicationPath - extraLibraryPaths - debugPaths - lineEditKallsyms - comboBoxArchitecture - lineEditObjdump - diff --git a/tests/integrationtests/CMakeLists.txt b/tests/integrationtests/CMakeLists.txt index 48e751f9..06b51f86 100644 --- a/tests/integrationtests/CMakeLists.txt +++ b/tests/integrationtests/CMakeLists.txt @@ -7,6 +7,7 @@ ecm_add_test( ../../src/perfcontrolfifowrapper.cpp ../../src/perfrecord.cpp ../../src/recordhost.cpp + ../../src/remotedevice.cpp ../../src/settings.cpp ../../src/util.cpp ../../src/errnoutil.cpp @@ -21,6 +22,7 @@ ecm_add_test( KF${QT_MAJOR_VERSION}::WindowSystem KF${QT_MAJOR_VERSION}::KIOCore KF${QT_MAJOR_VERSION}::Parts + QCoro::Core TEST_NAME tst_perfparser ) diff --git a/tests/integrationtests/tst_perfparser.cpp b/tests/integrationtests/tst_perfparser.cpp index 184bff7e..43d14b51 100644 --- a/tests/integrationtests/tst_perfparser.cpp +++ b/tests/integrationtests/tst_perfparser.cpp @@ -19,8 +19,6 @@ #include "perfparser.h" #include "perfrecord.h" #include "recordhost.h" -#include "unistd.h" -#include "util.h" #include "../testutils.h" #include @@ -429,7 +427,9 @@ private slots: QSignalSpy recordingFinishedSpy(&perf, &PerfRecord::recordingFinished); QSignalSpy recordingFailedSpy(&perf, &PerfRecord::recordingFailed); - perf.record({QStringLiteral("--no-buildid-cache")}, tempFile.fileName(), false, exePath, exeOptions); + host.setClientApplication(exePath); + host.setClientApplicationArguments(exeOptions); + perf.record({QStringLiteral("--no-buildid-cache")}, tempFile.fileName(), false); perf.sendInput(QByteArrayLiteral("some input\n")); QVERIFY(recordingFinishedSpy.wait()); @@ -735,11 +735,13 @@ private slots: QSignalSpy recordingFinishedSpy(&perf, &PerfRecord::recordingFinished); QSignalSpy recordingFailedSpy(&perf, &PerfRecord::recordingFailed); + host.setClientApplication(exePath); + host.setClientApplicationArguments(exeOptions); // always add `-c 1000000`, as perf's frequency mode is too unreliable for testing purposes perf.record( perfOptions + QStringList {QStringLiteral("-c"), QStringLiteral("1000000"), QStringLiteral("--no-buildid-cache")}, - fileName, false, exePath, exeOptions); + fileName, false); VERIFY_OR_THROW(recordingFinishedSpy.wait(10000));