From 673f9772cec43c7741b637ff75ee7662e8da53f6 Mon Sep 17 00:00:00 2001 From: Lieven Hey Date: Mon, 18 Sep 2023 15:29:36 +0200 Subject: [PATCH] add recording via ssh this patch finally runs perf via ssh on a remote device perf is run with -o - to stream the recording to the host in this case stderr contains the output of the program run --- src/perfrecord.cpp | 86 ++++++++++++++++++++++++++++++++++---------- src/perfrecord.h | 5 +++ src/recordhost.cpp | 7 ++-- src/recordhost.h | 8 ++++- src/recordpage.cpp | 40 +++++++++++++++++---- src/recordpage.h | 2 +- src/recordpage.ui | 19 ++++++++++ src/remotedevice.cpp | 10 +++++- src/remotedevice.h | 2 ++ 9 files changed, 149 insertions(+), 30 deletions(-) diff --git a/src/perfrecord.cpp b/src/perfrecord.cpp index a40d4d41..557c3265 100644 --- a/src/perfrecord.cpp +++ b/src/perfrecord.cpp @@ -72,24 +72,22 @@ bool PerfRecord::runPerf(bool elevatePrivileges, const QStringList& perfOptions, 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; + m_outputPath = outputPath; + m_userTerminated = false; + + if (m_host->isLocal()) { + return runPerfLocal(elevatePrivileges, perfOptions, outputPath, workingDirectory); + } else { + return runPerfRemote(perfOptions, outputPath, workingDirectory); } +} + +bool PerfRecord::runPerfLocal(bool elevatePrivileges, const QStringList& perfOptions, const QString& outputPath, + const QString& workingDirectory) +{ + m_perfRecordProcess = new QProcess(this); + m_perfRecordProcess->setProcessChannelMode(QProcess::MergedChannels); connect(m_perfRecordProcess.data(), static_cast(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) { @@ -123,9 +121,6 @@ bool PerfRecord::runPerf(bool elevatePrivileges, const QStringList& perfOptions, emit recordingOutput(output); }); - m_outputPath = outputPath; - m_userTerminated = false; - if (!workingDirectory.isEmpty()) { m_perfRecordProcess->setWorkingDirectory(workingDirectory); } @@ -162,6 +157,59 @@ 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); + if (!output->open(QIODevice::WriteOnly)) { + emit recordingFailed(QStringLiteral("Failed to create output file: %1").arg(outputPath)); + return false; + } + + connect(m_perfRecordProcess.data(), &QProcess::readyReadStandardOutput, m_perfRecordProcess, + [process = m_perfRecordProcess, output] { + auto data = process->readAllStandardOutput(); + output->write(data); + }); + connect(m_perfRecordProcess.data(), &QProcess::readyReadStandardError, m_perfRecordProcess, + [this] { emit recordingOutput(QString::fromUtf8(m_perfRecordProcess->readAllStandardError())); }); + + connect(m_perfRecordProcess.data(), static_cast(&QProcess::finished), + this, [this, output](int exitCode, QProcess::ExitStatus exitStatus) { + Q_UNUSED(exitStatus) + + output->close(); + output->deleteLater(); + + 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.data(), &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) { + Q_UNUSED(error) + if (!m_userTerminated) { + emit recordingFailed(m_perfRecordProcess->errorString()); + } + }); + + connect(m_perfRecordProcess.data(), &QProcess::started, this, + [this] { emit recordingStarted(m_perfRecordProcess->program(), m_perfRecordProcess->arguments()); }); + + m_perfRecordProcess->start(); + return true; +} + void PerfRecord::record(const QStringList& perfOptions, const QString& outputPath, bool elevatePrivileges, const QStringList& pids) { diff --git a/src/perfrecord.h b/src/perfrecord.h index 9f000691..7767c94f 100644 --- a/src/perfrecord.h +++ b/src/perfrecord.h @@ -56,5 +56,10 @@ 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 = {}); }; diff --git a/src/recordhost.cpp b/src/recordhost.cpp index e9ce1890..9e509bd0 100644 --- a/src/recordhost.cpp +++ b/src/recordhost.cpp @@ -184,8 +184,9 @@ RecordHost::RecordHost(QObject* parent) connectIsReady(&RecordHost::currentWorkingDirectoryChanged); connect(&m_remoteDevice, &RemoteDevice::connected, this, &RecordHost::checkRequirements); - connect(&m_remoteDevice, &RemoteDevice::connected, this, [this] { emit isReadyChanged(isReady()); }); + connect(&m_remoteDevice, &RemoteDevice::failedToConnect, this, + [this] { emit errorOccurred(tr("Failed to connect to: %1").arg(m_host)); }); setHost(QStringLiteral("localhost")); } @@ -317,7 +318,9 @@ void RecordHost::setClientApplication(const QString& clientApplication) setCurrentWorkingDirectory(application.dir().absolutePath()); } } else { - if (!m_remoteDevice.checkIfFileExists(clientApplication)) { + if (!m_remoteDevice.isConnected()) { + emit errorOccurred(tr("Hotspot is not connected to the remote device")); + } else if (!m_remoteDevice.checkIfFileExists(clientApplication)) { emit errorOccurred(tr("Application file cannot be found: %1").arg(clientApplication)); } else { emit errorOccurred({}); diff --git a/src/recordhost.h b/src/recordhost.h index 81a6e022..a2bb4475 100644 --- a/src/recordhost.h +++ b/src/recordhost.h @@ -103,6 +103,13 @@ class RecordHost : public QObject // list of pids to record void setPids(const QStringList& pids); + bool isLocal() const; + + const RemoteDevice& remoteDevice() const + { + return m_remoteDevice; + } + 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); @@ -120,7 +127,6 @@ class RecordHost : public QObject private: void checkRequirements(); - bool isLocal() const; QString m_host; QString m_error; diff --git a/src/recordpage.cpp b/src/recordpage.cpp index e1caa81e..d679737c 100644 --- a/src/recordpage.cpp +++ b/src/recordpage.cpp @@ -43,6 +43,7 @@ #include "perfoutputwidgetkonsole.h" #include "perfoutputwidgettext.h" #include "perfrecord.h" +#include "settings.h" namespace { bool isIntel() @@ -81,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; @@ -207,6 +212,20 @@ RecordPage::RecordPage(QWidget* parent) m_recordHost->setClientApplicationArguments(KShell::splitArgs(ui->applicationParametersBox->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); ui->compressionComboBox->addItem(tr("Level 1 (Fastest)"), 1); @@ -246,6 +265,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); @@ -289,15 +309,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())); @@ -503,7 +526,7 @@ void RecordPage::showRecordPage() { m_resultsFile.clear(); setError({}); - updateRecordType(); + m_recordHost->setRecordType(RecordType::LaunchApplication); ui->viewPerfRecordResultsButton->setEnabled(false); } @@ -740,17 +763,22 @@ 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->attachAppBox->setVisible(recordType == RecordType::AttachToProcess); 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 85b5087f..0ed4fa41 100644 --- a/src/recordpage.h +++ b/src/recordpage.h @@ -59,7 +59,7 @@ private slots: private: void recordingStopped(); - void updateRecordType(); + void updateRecordType(RecordType type); void appendOutput(const QString& text); void setError(const QString& message); diff --git a/src/recordpage.ui b/src/recordpage.ui index 3db5f6dc..16a2b52e 100644 --- a/src/recordpage.ui +++ b/src/recordpage.ui @@ -49,6 +49,25 @@ + + + + Remote Device + + + + + + Device: + + + + + + + + + diff --git a/src/remotedevice.cpp b/src/remotedevice.cpp index 6ec7b496..16496778 100644 --- a/src/remotedevice.cpp +++ b/src/remotedevice.cpp @@ -96,7 +96,6 @@ void RemoteDevice::connectToDevice(const QString& device) } else { emit disconnected(); } - qDebug() << exitCode << connection->readAll(); connection->deleteLater(); }); @@ -186,3 +185,12 @@ QProcess* RemoteDevice::sshProcess(const QStringList& args) const return process; } + +QProcess* 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 index 9c9de552..b91b2ed5 100644 --- a/src/remotedevice.h +++ b/src/remotedevice.h @@ -33,6 +33,8 @@ class RemoteDevice : public QObject bool checkIfFileExists(const QString& file) const; QByteArray getProgramOutput(const QStringList& args) const; + QProcess* runPerf(const QString& cdw, const QStringList& perfOptions) const; + signals: void connected(); void disconnected();