Skip to content

Commit

Permalink
add recording via ssh
Browse files Browse the repository at this point in the history
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
  • Loading branch information
lievenhey committed Sep 19, 2023
1 parent 18835dc commit 673f977
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 30 deletions.
86 changes: 67 additions & 19 deletions src/perfrecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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<void (QProcess::*)(int, QProcess::ExitStatus)>(&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)
{
Expand Down
5 changes: 5 additions & 0 deletions src/perfrecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {});
};
7 changes: 5 additions & 2 deletions src/recordhost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
Expand Down Expand Up @@ -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({});
Expand Down
8 changes: 7 additions & 1 deletion src/recordhost.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -120,7 +127,6 @@ class RecordHost : public QObject

private:
void checkRequirements();
bool isLocal() const;

QString m_host;
QString m_error;
Expand Down
40 changes: 34 additions & 6 deletions src/recordpage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include "perfoutputwidgetkonsole.h"
#include "perfoutputwidgettext.h"
#include "perfrecord.h"
#include "settings.h"

namespace {
bool isIntel()
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<int>(&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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<int>(&QComboBox::currentIndexChanged), this,
&RecordPage::updateRecordType);

connect(ui->recordTypeComboBox, qOverload<int>(&QComboBox::currentIndexChanged), m_recordHost,
[this] { m_recordHost->setRecordType(ui->recordTypeComboBox->currentData().value<RecordType>()); });
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()));
Expand Down Expand Up @@ -503,7 +526,7 @@ void RecordPage::showRecordPage()
{
m_resultsFile.clear();
setError({});
updateRecordType();
m_recordHost->setRecordType(RecordType::LaunchApplication);
ui->viewPerfRecordResultsButton->setEnabled(false);
}

Expand Down Expand Up @@ -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();
}
Expand Down
2 changes: 1 addition & 1 deletion src/recordpage.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
19 changes: 19 additions & 0 deletions src/recordpage.ui
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,25 @@
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="remoteDeviceBox">
<property name="title">
<string>Remote Device</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Device:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="deviceComboBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="launchAppBox">
<property name="title">
Expand Down
10 changes: 9 additions & 1 deletion src/remotedevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ void RemoteDevice::connectToDevice(const QString& device)
} else {
emit disconnected();
}
qDebug() << exitCode << connection->readAll();
connection->deleteLater();
});

Expand Down Expand Up @@ -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;
}
2 changes: 2 additions & 0 deletions src/remotedevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 673f977

Please sign in to comment.