Skip to content

Commit d8dc19a

Browse files
committed
update record host to work with remote devices
Changes to existing logic: RecordHost now saved the client app args RecordHost no longer differentiate between local and remote when setting the output file name, since the file is always saved on the host New class RemoteDevice, this class handles communication between the host and the remote device. It does this by using a multiplexed ssh connection. This is faster since we only need to do authentification once and can then reuse the existing connection.
1 parent 36ec6e5 commit d8dc19a

File tree

8 files changed

+390
-61
lines changed

8 files changed

+390
-61
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ set(HOTSPOT_SRCS
5454
perfcontrolfifowrapper.cpp
5555
errnoutil.cpp
5656
recordhost.cpp
57+
remotedevice.cpp
5758
# ui files:
5859
mainwindow.ui
5960
aboutdialog.ui

src/recordhost.cpp

Lines changed: 132 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,35 @@ RecordHost::PerfCapabilities fetchLocalPerfCapabilities(const QString& perfPath)
140140

141141
return capabilities;
142142
}
143+
144+
RecordHost::PerfCapabilities fetchRemotePerfCapabilities(const RemoteDevice& device)
145+
{
146+
RecordHost::PerfCapabilities capabilities;
147+
148+
const auto buildOptions =
149+
device.getProgramOutput({QStringLiteral("perf"), QStringLiteral("version"), QStringLiteral("--build-options")});
150+
const auto help = device.getProgramOutput({QStringLiteral("perf"), QStringLiteral("--help")});
151+
152+
capabilities.canCompress = Zstd_FOUND && buildOptions.contains("zszd: [ on ]");
153+
capabilities.canSwitchEvents = help.contains("--switch-events");
154+
capabilities.canSampleCpu = help.contains("--sample-cpu");
155+
156+
// TODO: implement
157+
capabilities.canProfileOffCpu = false;
158+
capabilities.privilegesAlreadyElevated = false;
159+
160+
capabilities.canUseAio = false; // AIO doesn't work with perf streaming
161+
capabilities.canElevatePrivileges = false; // we currently don't support this
162+
163+
return capabilities;
164+
}
143165
}
144166

145167
RecordHost::RecordHost(QObject* parent)
146168
: QObject(parent)
147169
, m_checkPerfCapabilitiesJob(this)
148170
, m_checkPerfInstalledJob(this)
171+
, m_remoteDevice(this)
149172
{
150173
connect(this, &RecordHost::errorOccurred, this, [this](const QString& message) { m_error = message; });
151174

@@ -160,6 +183,10 @@ RecordHost::RecordHost(QObject* parent)
160183
connectIsReady(&RecordHost::pidsChanged);
161184
connectIsReady(&RecordHost::currentWorkingDirectoryChanged);
162185

186+
connect(&m_remoteDevice, &RemoteDevice::connected, this, &RecordHost::checkRequirements);
187+
188+
connect(&m_remoteDevice, &RemoteDevice::connected, this, [this] { emit isReadyChanged(isReady()); });
189+
163190
setHost(QStringLiteral("localhost"));
164191
}
165192

@@ -169,7 +196,13 @@ bool RecordHost::isReady() const
169196
{
170197
switch (m_recordType) {
171198
case RecordType::LaunchApplication:
172-
// client application is already validated in the setter
199+
// client application is already validated in the setter
200+
if (m_clientApplication.isEmpty() && m_cwd.isEmpty())
201+
return false;
202+
break;
203+
case RecordType::LaunchRemoteApplication:
204+
if (!m_remoteDevice.isConnected())
205+
return false;
173206
if (m_clientApplication.isEmpty() && m_cwd.isEmpty())
174207
return false;
175208
break;
@@ -210,38 +243,19 @@ void RecordHost::setHost(const QString& host)
210243
m_clientApplication.clear();
211244
emit clientApplicationChanged(m_clientApplication);
212245

246+
m_clientApplicationArguments.clear();
247+
emit clientApplicationArgumentsChanged(m_clientApplicationArguments);
248+
213249
m_perfCapabilities = {};
214250
emit perfCapabilitiesChanged(m_perfCapabilities);
215251

216-
const auto perfPath = perfBinaryPath();
217-
m_checkPerfCapabilitiesJob.startJob([perfPath](auto&&) { return fetchLocalPerfCapabilities(perfPath); },
218-
[this](RecordHost::PerfCapabilities capabilities) {
219-
Q_ASSERT(QThread::currentThread() == thread());
220-
221-
m_perfCapabilities = capabilities;
222-
emit perfCapabilitiesChanged(m_perfCapabilities);
223-
});
224-
225-
m_checkPerfInstalledJob.startJob(
226-
[isLocal = isLocal(), perfPath](auto&&) {
227-
if (isLocal) {
228-
if (perfPath.isEmpty()) {
229-
return !QStandardPaths::findExecutable(QStringLiteral("perf")).isEmpty();
230-
}
231-
232-
return QFileInfo::exists(perfPath);
233-
}
234-
235-
qWarning() << "remote is not implemented";
236-
return false;
237-
},
238-
[this](bool isInstalled) {
239-
if (!isInstalled) {
240-
emit errorOccurred(tr("perf is not installed"));
241-
}
242-
m_isPerfInstalled = isInstalled;
243-
emit isPerfInstalledChanged(isInstalled);
244-
});
252+
m_remoteDevice.disconnect();
253+
if (isLocal()) {
254+
checkRequirements();
255+
} else {
256+
// checkRequirements will be called via RemoteDevice::connected
257+
m_remoteDevice.connectToDevice(m_host);
258+
}
245259
}
246260

247261
void RecordHost::setCurrentWorkingDirectory(const QString& cwd)
@@ -262,16 +276,25 @@ void RecordHost::setCurrentWorkingDirectory(const QString& cwd)
262276
m_cwd = cwd;
263277
emit currentWorkingDirectoryChanged(cwd);
264278
}
265-
return;
279+
} else {
280+
if (!m_remoteDevice.checkIfDirectoryExists(cwd)) {
281+
emit errorOccurred(tr("Working directory folder cannot be found: %1").arg(cwd));
282+
} else {
283+
emit errorOccurred({});
284+
m_cwd = cwd;
285+
emit currentWorkingDirectoryChanged(m_cwd);
286+
}
266287
}
267-
268-
qWarning() << "is not implemented for remote";
269288
}
270289

271290
void RecordHost::setClientApplication(const QString& clientApplication)
272291
{
273292
Q_ASSERT(QThread::currentThread() == thread());
274293

294+
if (m_clientApplication == clientApplication) {
295+
return;
296+
}
297+
275298
if (isLocal()) {
276299
QFileInfo application(KShell::tildeExpand(clientApplication));
277300
if (!application.exists()) {
@@ -293,38 +316,48 @@ void RecordHost::setClientApplication(const QString& clientApplication)
293316
if (m_cwd.isEmpty()) {
294317
setCurrentWorkingDirectory(application.dir().absolutePath());
295318
}
296-
return;
319+
} else {
320+
if (!m_remoteDevice.checkIfFileExists(clientApplication)) {
321+
emit errorOccurred(tr("Application file cannot be found: %1").arg(clientApplication));
322+
} else {
323+
emit errorOccurred({});
324+
m_clientApplication = clientApplication;
325+
emit clientApplicationChanged(m_clientApplication);
326+
}
297327
}
298-
299-
qWarning() << "is not implemented for remote";
300328
}
301329

302-
void RecordHost::setOutputFileName(const QString& filePath)
330+
void RecordHost::setClientApplicationArguments(const QString& arguments)
303331
{
304-
if (isLocal()) {
305-
const auto perfDataExtension = QStringLiteral(".data");
306-
307-
const QFileInfo file(filePath);
308-
const QFileInfo folder(file.absolutePath());
309-
310-
if (!folder.exists()) {
311-
emit errorOccurred(tr("Output file directory folder cannot be found: %1").arg(folder.path()));
312-
} else if (!folder.isDir()) {
313-
emit errorOccurred(tr("Output file directory folder is not valid: %1").arg(folder.path()));
314-
} else if (!folder.isWritable()) {
315-
emit errorOccurred(tr("Output file directory folder is not writable: %1").arg(folder.path()));
316-
} else if (!file.absoluteFilePath().endsWith(perfDataExtension)) {
317-
emit errorOccurred(tr("Output file must end with %1").arg(perfDataExtension));
318-
} else {
319-
emit errorOccurred({});
320-
m_outputFileName = filePath;
321-
emit outputFileNameChanged(m_outputFileName);
322-
}
332+
Q_ASSERT(QThread::currentThread() == thread());
323333

324-
return;
334+
if (m_clientApplicationArguments != arguments) {
335+
m_clientApplicationArguments = arguments;
336+
emit clientApplicationArgumentsChanged(m_clientApplicationArguments);
325337
}
338+
}
326339

327-
qWarning() << "is not implemented for remote";
340+
void RecordHost::setOutputFileName(const QString& filePath)
341+
{
342+
const auto perfDataExtension = QStringLiteral(".data");
343+
const QFileInfo file(filePath);
344+
const QFileInfo folder(file.absolutePath());
345+
346+
// the recording data is streamed from the device (currently) so there is no need to use different logic for local
347+
// vs remote
348+
if (!folder.exists()) {
349+
emit errorOccurred(tr("Output file directory folder cannot be found: %1").arg(folder.path()));
350+
} else if (!folder.isDir()) {
351+
emit errorOccurred(tr("Output file directory folder is not valid: %1").arg(folder.path()));
352+
} else if (!folder.isWritable()) {
353+
emit errorOccurred(tr("Output file directory folder is not writable: %1").arg(folder.path()));
354+
} else if (!file.absoluteFilePath().endsWith(perfDataExtension)) {
355+
emit errorOccurred(tr("Output file must end with %1").arg(perfDataExtension));
356+
} else {
357+
emit errorOccurred({});
358+
m_outputFileName = filePath;
359+
emit outputFileNameChanged(m_outputFileName);
360+
}
328361
}
329362

330363
void RecordHost::setRecordType(RecordType type)
@@ -366,3 +399,44 @@ QString RecordHost::perfBinaryPath() const
366399
}
367400
return {};
368401
}
402+
403+
void RecordHost::checkRequirements()
404+
{
405+
const auto perfPath = perfBinaryPath();
406+
m_checkPerfCapabilitiesJob.startJob(
407+
[isLocal = isLocal(), &remoteDevice = m_remoteDevice, perfPath](auto&&) {
408+
if (isLocal) {
409+
return fetchLocalPerfCapabilities(perfPath);
410+
} else {
411+
return fetchRemotePerfCapabilities(remoteDevice);
412+
}
413+
},
414+
[this](RecordHost::PerfCapabilities capabilities) {
415+
Q_ASSERT(QThread::currentThread() == thread());
416+
417+
m_perfCapabilities = capabilities;
418+
emit perfCapabilitiesChanged(m_perfCapabilities);
419+
});
420+
421+
m_checkPerfInstalledJob.startJob(
422+
[isLocal = isLocal(), &remoteDevice = m_remoteDevice, perfPath](auto&&) {
423+
if (isLocal) {
424+
if (perfPath.isEmpty()) {
425+
return !QStandardPaths::findExecutable(QStringLiteral("perf")).isEmpty();
426+
}
427+
428+
return QFileInfo::exists(perfPath);
429+
} else {
430+
return remoteDevice.checkIfProgramExists(QStringLiteral("perf"));
431+
}
432+
433+
return false;
434+
},
435+
[this](bool isInstalled) {
436+
if (!isInstalled) {
437+
emit errorOccurred(tr("perf is not installed"));
438+
}
439+
m_isPerfInstalled = isInstalled;
440+
emit isPerfInstalledChanged(isInstalled);
441+
});
442+
}

src/recordhost.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#pragma once
99

1010
#include "jobtracker.h"
11+
#include "remotedevice.h"
1112

1213
#include <QObject>
1314

@@ -60,6 +61,12 @@ class RecordHost : public QObject
6061
}
6162
void setClientApplication(const QString& clientApplication);
6263

64+
QString clientApplicationArguments() const
65+
{
66+
return m_clientApplicationArguments;
67+
}
68+
void setClientApplicationArguments(const QString& arguments);
69+
6370
QString outputFileName() const
6471
{
6572
return m_outputFileName;
@@ -104,26 +111,30 @@ class RecordHost : public QObject
104111
void hostChanged();
105112
void currentWorkingDirectoryChanged(const QString& cwd); // Maybe QUrl
106113
void clientApplicationChanged(const QString& clientApplication);
114+
void clientApplicationArgumentsChanged(const QString& arguments);
107115
void perfCapabilitiesChanged(RecordHost::PerfCapabilities perfCapabilities);
108116
void isPerfInstalledChanged(bool isInstalled);
109117
void outputFileNameChanged(const QString& outputFileName);
110118
void recordTypeChanged(RecordType type);
111119
void pidsChanged();
112120

113121
private:
122+
void checkRequirements();
114123
bool isLocal() const;
115124

116125
QString m_host;
117126
QString m_error;
118127
QString m_cwd;
119128
QString m_clientApplication;
129+
QString m_clientApplicationArguments;
120130
QString m_outputFileName;
121131
PerfCapabilities m_perfCapabilities;
122132
JobTracker m_checkPerfCapabilitiesJob;
123133
JobTracker m_checkPerfInstalledJob;
124134
RecordType m_recordType = RecordType::LaunchApplication;
125135
bool m_isPerfInstalled = false;
126136
QStringList m_pids;
137+
RemoteDevice m_remoteDevice;
127138
};
128139

129140
Q_DECLARE_METATYPE(RecordHost::PerfCapabilities)

src/recordpage.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ RecordPage::RecordPage(QWidget* parent)
203203
});
204204

205205
connect(ui->applicationParametersBox, &QLineEdit::editingFinished, this, [this] {
206-
ui->multiConfig->saveCurrentConfig(); // TODO: set app params
206+
ui->multiConfig->saveCurrentConfig();
207+
m_recordHost->setClientApplicationArguments(ui->applicationParametersBox->text());
207208
});
208209

209210
ui->compressionComboBox->addItem(tr("Disabled"), -1);

0 commit comments

Comments
 (0)