Skip to content

Commit 4b08990

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 2689f1b commit 4b08990

File tree

9 files changed

+213
-99
lines changed

9 files changed

+213
-99
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: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,13 @@ RecordHost::PerfCapabilities fetchLocalPerfCapabilities(const QString& perfPath)
143143
return capabilities;
144144
}
145145

146-
RecordHost::PerfCapabilities fetchRemotePerfCapabilities(const RemoteDevice& device)
146+
RecordHost::PerfCapabilities fetchRemotePerfCapabilities(const std::unique_ptr<RemoteDevice>& device)
147147
{
148148
RecordHost::PerfCapabilities capabilities;
149149

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

154154
capabilities.canCompress = Zstd_FOUND && buildOptions.contains("zszd: [ on ]");
155155
capabilities.canSwitchEvents = help.contains("--switch-events");
@@ -170,7 +170,6 @@ RecordHost::RecordHost(QObject* parent)
170170
: QObject(parent)
171171
, m_checkPerfCapabilitiesJob(this)
172172
, m_checkPerfInstalledJob(this)
173-
, m_remoteDevice(this)
174173
{
175174
connect(this, &RecordHost::errorOccurred, this, [this](const QString& message) { m_error = message; });
176175

@@ -185,10 +184,6 @@ RecordHost::RecordHost(QObject* parent)
185184
connectIsReady(&RecordHost::pidsChanged);
186185
connectIsReady(&RecordHost::currentWorkingDirectoryChanged);
187186

188-
connect(&m_remoteDevice, &RemoteDevice::connected, this, &RecordHost::checkRequirements);
189-
190-
connect(&m_remoteDevice, &RemoteDevice::connected, this, [this] { emit isReadyChanged(isReady()); });
191-
192187
setHost(QStringLiteral("localhost"));
193188
}
194189

@@ -203,7 +198,7 @@ bool RecordHost::isReady() const
203198
return false;
204199
break;
205200
case RecordType::LaunchRemoteApplication:
206-
if (!m_remoteDevice.isConnected())
201+
if (!m_remoteDevice || !m_remoteDevice->isConnected())
207202
return false;
208203
if (m_clientApplication.isEmpty() && m_cwd.isEmpty())
209204
return false;
@@ -238,6 +233,17 @@ void RecordHost::setHost(const QString& host)
238233
m_host = host;
239234
emit hostChanged();
240235

236+
if (!isLocal()) {
237+
m_remoteDevice = std::make_unique<RemoteDevice>(this);
238+
239+
connect(m_remoteDevice.get(), &RemoteDevice::connected, this, &RecordHost::checkRequirements);
240+
connect(m_remoteDevice.get(), &RemoteDevice::connected, this, [this] { emit isReadyChanged(isReady()); });
241+
connect(m_remoteDevice.get(), &RemoteDevice::failedToConnect, this,
242+
[this] { emit errorOccurred(tr("Failed to connect to: %1").arg(m_host)); });
243+
} else {
244+
m_remoteDevice = nullptr;
245+
}
246+
241247
// invalidate everything
242248
m_cwd.clear();
243249
emit currentWorkingDirectoryChanged(m_cwd);
@@ -251,12 +257,11 @@ void RecordHost::setHost(const QString& host)
251257
m_perfCapabilities = {};
252258
emit perfCapabilitiesChanged(m_perfCapabilities);
253259

254-
m_remoteDevice.disconnect();
255260
if (isLocal()) {
256261
checkRequirements();
257262
} else {
258263
// checkRequirements will be called via RemoteDevice::connected
259-
m_remoteDevice.connectToDevice(m_host);
264+
m_remoteDevice->connectToDevice(m_host);
260265
}
261266
}
262267

@@ -279,7 +284,7 @@ void RecordHost::setCurrentWorkingDirectory(const QString& cwd)
279284
emit currentWorkingDirectoryChanged(cwd);
280285
}
281286
} else {
282-
if (!m_remoteDevice.checkIfDirectoryExists(cwd)) {
287+
if (!m_remoteDevice->checkIfDirectoryExists(cwd)) {
283288
emit errorOccurred(tr("Working directory folder cannot be found: %1").arg(cwd));
284289
} else {
285290
emit errorOccurred({});
@@ -319,7 +324,9 @@ void RecordHost::setClientApplication(const QString& clientApplication)
319324
setCurrentWorkingDirectory(application.dir().absolutePath());
320325
}
321326
} else {
322-
if (!m_remoteDevice.checkIfFileExists(clientApplication)) {
327+
if (!m_remoteDevice->isConnected()) {
328+
emit errorOccurred(tr("Hotspot is not connected to the remote device"));
329+
} else if (!m_remoteDevice->checkIfFileExists(clientApplication)) {
323330
emit errorOccurred(tr("Application file cannot be found: %1").arg(clientApplication));
324331
} else {
325332
emit errorOccurred({});
@@ -345,8 +352,8 @@ void RecordHost::setOutputFileName(const QString& filePath)
345352
const QFileInfo file(filePath);
346353
const QFileInfo folder(file.absolutePath());
347354

348-
// the recording data is streamed from the device (currently) so there is no need to use different logic for local
349-
// vs remote
355+
// the recording data is streamed from the device (currently) so there is no need to use different logic for
356+
// local vs remote
350357
if (!folder.exists()) {
351358
emit errorOccurred(tr("Output file directory folder cannot be found: %1").arg(folder.path()));
352359
} else if (!folder.isDir()) {
@@ -429,7 +436,7 @@ void RecordHost::checkRequirements()
429436

430437
return QFileInfo::exists(perfPath);
431438
} else {
432-
return remoteDevice.checkIfProgramExists(QStringLiteral("perf"));
439+
return remoteDevice->checkIfProgramExists(QStringLiteral("perf"));
433440
}
434441

435442
return false;
@@ -442,3 +449,12 @@ void RecordHost::checkRequirements()
442449
emit isPerfInstalledChanged(isInstalled);
443450
});
444451
}
452+
453+
void RecordHost::disconnectFromDevice()
454+
{
455+
if (!isLocal()) {
456+
if (m_remoteDevice->isConnected()) {
457+
m_remoteDevice->disconnect();
458+
}
459+
}
460+
}

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,
@@ -104,6 +106,15 @@ class RecordHost : public QObject
104106
// list of pids to record
105107
void setPids(const QStringList& pids);
106108

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

122133
private:
123134
void checkRequirements();
124-
bool isLocal() const;
125135

126136
QString m_host;
127137
QString m_error;
@@ -135,7 +145,7 @@ class RecordHost : public QObject
135145
RecordType m_recordType = RecordType::LaunchApplication;
136146
bool m_isPerfInstalled = false;
137147
QStringList m_pids;
138-
RemoteDevice m_remoteDevice;
148+
std::unique_ptr<RemoteDevice> m_remoteDevice;
139149
};
140150

141151
Q_DECLARE_METATYPE(RecordHost::PerfCapabilities)

0 commit comments

Comments
 (0)