Skip to content

Commit b05d3b6

Browse files
committed
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
1 parent 43b9ee3 commit b05d3b6

File tree

9 files changed

+208
-95
lines changed

9 files changed

+208
-95
lines changed

src/perfrecord.cpp

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -70,60 +70,30 @@ bool PerfRecord::runPerf(bool elevatePrivileges, const QStringList& perfOptions,
7070
m_perfControlFifo.close();
7171
m_perfRecordProcess->kill();
7272
}
73-
m_perfRecordProcess = std::make_unique<QProcess>(this);
74-
m_perfRecordProcess->setProcessChannelMode(QProcess::MergedChannels);
7573

76-
const auto outputFileInfo = QFileInfo(outputPath);
77-
const auto folderPath = outputFileInfo.dir().path();
78-
const auto folderInfo = QFileInfo(folderPath);
79-
if (!folderInfo.exists()) {
80-
emit recordingFailed(tr("Folder '%1' does not exist.").arg(folderPath));
81-
return false;
82-
}
83-
if (!folderInfo.isDir()) {
84-
emit recordingFailed(tr("'%1' is not a folder.").arg(folderPath));
85-
return false;
86-
}
87-
if (!folderInfo.isWritable()) {
88-
emit recordingFailed(tr("Folder '%1' is not writable.").arg(folderPath));
89-
return false;
90-
}
91-
92-
connect(m_perfRecordProcess.get(), static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
93-
this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
94-
Q_UNUSED(exitStatus)
74+
m_outputPath = outputPath;
75+
m_userTerminated = false;
9576

96-
const auto outputFileInfo = QFileInfo(m_outputPath);
97-
if ((exitCode == EXIT_SUCCESS || (exitCode == SIGTERM && m_userTerminated) || outputFileInfo.size() > 0)
98-
&& outputFileInfo.exists()) {
99-
if (exitCode != EXIT_SUCCESS && !m_userTerminated) {
100-
emit debuggeeCrashed();
101-
}
102-
emit recordingFinished(m_outputPath);
103-
} else {
104-
emit recordingFailed(tr("Failed to record perf data, error code %1.").arg(exitCode));
105-
}
106-
m_userTerminated = false;
107-
});
77+
if (m_host->isLocal()) {
78+
return runPerfLocal(elevatePrivileges, perfOptions, outputPath, workingDirectory);
79+
} else {
80+
return runPerfRemote(perfOptions, outputPath, workingDirectory);
81+
}
82+
}
10883

109-
connect(m_perfRecordProcess.get(), &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
110-
Q_UNUSED(error)
111-
if (!m_userTerminated) {
112-
emit recordingFailed(m_perfRecordProcess->errorString());
113-
}
114-
});
84+
bool PerfRecord::runPerfLocal(bool elevatePrivileges, const QStringList& perfOptions, const QString& outputPath,
85+
const QString& workingDirectory)
86+
{
87+
m_perfRecordProcess = std::make_unique<QProcess>(this);
88+
m_perfRecordProcess->setProcessChannelMode(QProcess::MergedChannels);
11589

116-
connect(m_perfRecordProcess.get(), &QProcess::started, this,
117-
[this] { emit recordingStarted(m_perfRecordProcess->program(), m_perfRecordProcess->arguments()); });
90+
connectRecordingProcessErrors();
11891

11992
connect(m_perfRecordProcess.get(), &QProcess::readyRead, this, [this]() {
12093
const auto output = QString::fromUtf8(m_perfRecordProcess->readAll());
12194
emit recordingOutput(output);
12295
});
12396

124-
m_outputPath = outputPath;
125-
m_userTerminated = false;
126-
12797
if (!workingDirectory.isEmpty()) {
12898
m_perfRecordProcess->setWorkingDirectory(workingDirectory);
12999
}
@@ -160,6 +130,36 @@ bool PerfRecord::runPerf(bool elevatePrivileges, const QStringList& perfOptions,
160130
return true;
161131
}
162132

133+
bool PerfRecord::runPerfRemote(const QStringList& perfOptions, const QString& outputPath,
134+
const QString& workingDirectory)
135+
{
136+
m_perfRecordProcess = m_host->remoteDevice()->runPerf(workingDirectory, perfOptions);
137+
138+
auto output = new QFile(outputPath, m_perfRecordProcess.get());
139+
if (!output->open(QIODevice::WriteOnly)) {
140+
emit recordingFailed(QStringLiteral("Failed to create output file: %1").arg(outputPath));
141+
return false;
142+
}
143+
144+
connect(m_perfRecordProcess.get(), &QProcess::readyReadStandardOutput, m_perfRecordProcess.get(),
145+
[process = m_perfRecordProcess.get(), output] {
146+
auto data = process->readAllStandardOutput();
147+
output->write(data);
148+
});
149+
connect(m_perfRecordProcess.get(), &QProcess::readyReadStandardError, m_perfRecordProcess.get(),
150+
[this] { emit recordingOutput(QString::fromUtf8(m_perfRecordProcess->readAllStandardError())); });
151+
152+
connect(m_perfRecordProcess.get(), qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, [output] {
153+
output->close();
154+
output->deleteLater();
155+
});
156+
157+
connectRecordingProcessErrors();
158+
159+
m_perfRecordProcess->start();
160+
return true;
161+
}
162+
163163
void PerfRecord::record(const QStringList& perfOptions, const QString& outputPath, bool elevatePrivileges,
164164
const QStringList& pids)
165165
{
@@ -245,3 +245,33 @@ bool PerfRecord::actuallyElevatePrivileges(bool elevatePrivileges) const
245245
const auto capabilities = m_host->perfCapabilities();
246246
return elevatePrivileges && capabilities.canElevatePrivileges && !capabilities.privilegesAlreadyElevated;
247247
}
248+
249+
void PerfRecord::connectRecordingProcessErrors()
250+
{
251+
connect(m_perfRecordProcess.get(), qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this,
252+
[this](int exitCode, QProcess::ExitStatus exitStatus) {
253+
Q_UNUSED(exitStatus)
254+
255+
const auto outputFileInfo = QFileInfo(m_outputPath);
256+
if ((exitCode == EXIT_SUCCESS || (exitCode == SIGTERM && m_userTerminated) || outputFileInfo.size() > 0)
257+
&& outputFileInfo.exists()) {
258+
if (exitCode != EXIT_SUCCESS && !m_userTerminated) {
259+
emit debuggeeCrashed();
260+
}
261+
emit recordingFinished(m_outputPath);
262+
} else {
263+
emit recordingFailed(tr("Failed to record perf data, error code %1.").arg(exitCode));
264+
}
265+
m_userTerminated = false;
266+
});
267+
268+
connect(m_perfRecordProcess.get(), &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
269+
Q_UNUSED(error)
270+
if (!m_userTerminated) {
271+
emit recordingFailed(m_perfRecordProcess->errorString());
272+
}
273+
});
274+
275+
connect(m_perfRecordProcess.get(), &QProcess::started, this,
276+
[this] { emit recordingStarted(m_perfRecordProcess->program(), m_perfRecordProcess->arguments()); });
277+
}

src/perfrecord.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
#include "perfcontrolfifowrapper.h"
1313

1414
#include <QObject>
15+
#include <QProcess>
1516

1617
#include <memory>
1718

18-
class QProcess;
1919
class RecordHost;
2020

2121
class PerfRecord : public QObject
@@ -57,5 +57,12 @@ class PerfRecord : public QObject
5757
bool runPerf(bool elevatePrivileges, const QStringList& perfOptions, const QString& outputPath,
5858
const QString& workingDirectory = QString());
5959

60+
bool runPerfLocal(bool elevatePrivileges, const QStringList& perfOptions, const QString& outputPath,
61+
const QString& workingDirectory = QString());
62+
bool runPerfRemote(const QStringList& perfOptions, const QString& outputPath,
63+
const QString& workingDirectory = QString());
64+
6065
bool runRemotePerf(const QStringList& perfOptions, const QString& outputPath, const QString& workingDirectory = {});
66+
67+
void connectRecordingProcessErrors();
6168
};

src/recordhost.cpp

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,13 @@ RecordHost::PerfCapabilities fetchLocalPerfCapabilities(const QString& perfPath)
141141
return capabilities;
142142
}
143143

144-
RecordHost::PerfCapabilities fetchRemotePerfCapabilities(const RemoteDevice& device)
144+
RecordHost::PerfCapabilities fetchRemotePerfCapabilities(const std::unique_ptr<RemoteDevice>& device)
145145
{
146146
RecordHost::PerfCapabilities capabilities;
147147

148-
const auto buildOptions =
149-
device.getProgramOutput({QStringLiteral("perf"), QStringLiteral("version"), QStringLiteral("--build-options")});
150-
const auto help = device.getProgramOutput({QStringLiteral("perf"), QStringLiteral("--help")});
148+
const auto buildOptions = device->getProgramOutput(
149+
{QStringLiteral("perf"), QStringLiteral("version"), QStringLiteral("--build-options")});
150+
const auto help = device->getProgramOutput({QStringLiteral("perf"), QStringLiteral("--help")});
151151

152152
capabilities.canCompress = Zstd_FOUND && buildOptions.contains("zszd: [ on ]");
153153
capabilities.canSwitchEvents = help.contains("--switch-events");
@@ -168,7 +168,6 @@ RecordHost::RecordHost(QObject* parent)
168168
: QObject(parent)
169169
, m_checkPerfCapabilitiesJob(this)
170170
, m_checkPerfInstalledJob(this)
171-
, m_remoteDevice(this)
172171
{
173172
connect(this, &RecordHost::errorOccurred, this, [this](const QString& message) { m_error = message; });
174173

@@ -183,10 +182,6 @@ RecordHost::RecordHost(QObject* parent)
183182
connectIsReady(&RecordHost::pidsChanged);
184183
connectIsReady(&RecordHost::currentWorkingDirectoryChanged);
185184

186-
connect(&m_remoteDevice, &RemoteDevice::connected, this, &RecordHost::checkRequirements);
187-
188-
connect(&m_remoteDevice, &RemoteDevice::connected, this, [this] { emit isReadyChanged(isReady()); });
189-
190185
setHost(QStringLiteral("localhost"));
191186
}
192187

@@ -201,7 +196,7 @@ bool RecordHost::isReady() const
201196
return false;
202197
break;
203198
case RecordType::LaunchRemoteApplication:
204-
if (!m_remoteDevice.isConnected())
199+
if (!m_remoteDevice || !m_remoteDevice->isConnected())
205200
return false;
206201
if (m_clientApplication.isEmpty() && m_cwd.isEmpty())
207202
return false;
@@ -236,6 +231,15 @@ void RecordHost::setHost(const QString& host)
236231
m_host = host;
237232
emit hostChanged();
238233

234+
if (!isLocal()) {
235+
m_remoteDevice = std::make_unique<RemoteDevice>(this);
236+
237+
connect(m_remoteDevice.get(), &RemoteDevice::connected, this, &RecordHost::checkRequirements);
238+
connect(m_remoteDevice.get(), &RemoteDevice::connected, this, [this] { emit isReadyChanged(isReady()); });
239+
connect(m_remoteDevice.get(), &RemoteDevice::failedToConnect, this,
240+
[this] { emit errorOccurred(tr("Failed to connect to: %1").arg(m_host)); });
241+
}
242+
239243
// invalidate everything
240244
m_cwd.clear();
241245
emit currentWorkingDirectoryChanged(m_cwd);
@@ -249,12 +253,11 @@ void RecordHost::setHost(const QString& host)
249253
m_perfCapabilities = {};
250254
emit perfCapabilitiesChanged(m_perfCapabilities);
251255

252-
m_remoteDevice.disconnect();
253256
if (isLocal()) {
254257
checkRequirements();
255258
} else {
256259
// checkRequirements will be called via RemoteDevice::connected
257-
m_remoteDevice.connectToDevice(m_host);
260+
m_remoteDevice->connectToDevice(m_host);
258261
}
259262
}
260263

@@ -277,7 +280,7 @@ void RecordHost::setCurrentWorkingDirectory(const QString& cwd)
277280
emit currentWorkingDirectoryChanged(cwd);
278281
}
279282
} else {
280-
if (!m_remoteDevice.checkIfDirectoryExists(cwd)) {
283+
if (!m_remoteDevice->checkIfDirectoryExists(cwd)) {
281284
emit errorOccurred(tr("Working directory folder cannot be found: %1").arg(cwd));
282285
} else {
283286
emit errorOccurred({});
@@ -317,7 +320,9 @@ void RecordHost::setClientApplication(const QString& clientApplication)
317320
setCurrentWorkingDirectory(application.dir().absolutePath());
318321
}
319322
} else {
320-
if (!m_remoteDevice.checkIfFileExists(clientApplication)) {
323+
if (!m_remoteDevice->isConnected()) {
324+
emit errorOccurred(tr("Hotspot is not connected to the remote device"));
325+
} else if (!m_remoteDevice->checkIfFileExists(clientApplication)) {
321326
emit errorOccurred(tr("Application file cannot be found: %1").arg(clientApplication));
322327
} else {
323328
emit errorOccurred({});
@@ -427,7 +432,7 @@ void RecordHost::checkRequirements()
427432

428433
return QFileInfo::exists(perfPath);
429434
} else {
430-
return remoteDevice.checkIfProgramExists(QStringLiteral("perf"));
435+
return remoteDevice->checkIfProgramExists(QStringLiteral("perf"));
431436
}
432437

433438
return false;
@@ -440,3 +445,12 @@ void RecordHost::checkRequirements()
440445
emit isPerfInstalledChanged(isInstalled);
441446
});
442447
}
448+
449+
void RecordHost::disconnectFromDevice()
450+
{
451+
if (!isLocal()) {
452+
if (m_remoteDevice->isConnected()) {
453+
m_remoteDevice->disconnect();
454+
}
455+
}
456+
}

src/recordhost.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
#include <QObject>
1414

15+
#include <memory>
16+
1517
enum class RecordType
1618
{
1719
LaunchApplication,
@@ -103,6 +105,15 @@ class RecordHost : public QObject
103105
// list of pids to record
104106
void setPids(const QStringList& pids);
105107

108+
bool isLocal() const;
109+
110+
const RemoteDevice* remoteDevice() const
111+
{
112+
return m_remoteDevice.get();
113+
}
114+
115+
void disconnectFromDevice();
116+
106117
signals:
107118
/// disallow "start" on recordpage until this is ready and that should only be the case when there's no error
108119
void isReadyChanged(bool isReady);
@@ -120,7 +131,6 @@ class RecordHost : public QObject
120131

121132
private:
122133
void checkRequirements();
123-
bool isLocal() const;
124134

125135
QString m_host;
126136
QString m_error;
@@ -134,7 +144,7 @@ class RecordHost : public QObject
134144
RecordType m_recordType = RecordType::LaunchApplication;
135145
bool m_isPerfInstalled = false;
136146
QStringList m_pids;
137-
RemoteDevice m_remoteDevice;
147+
std::unique_ptr<RemoteDevice> m_remoteDevice;
138148
};
139149

140150
Q_DECLARE_METATYPE(RecordHost::PerfCapabilities)

0 commit comments

Comments
 (0)