Skip to content

Commit 8cadf77

Browse files
lievenheymilianw
authored andcommitted
add perf query interface and untangle it from recordpage
moving the query part of the recordpage into its own class will make implementing remote recording much easier
1 parent 22b6ff2 commit 8cadf77

18 files changed

+857
-363
lines changed

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ set(HOTSPOT_SRCS
5353
initiallystoppedprocess.cpp
5454
perfcontrolfifowrapper.cpp
5555
errnoutil.cpp
56+
recordhost.cpp
5657
# ui files:
5758
mainwindow.ui
5859
aboutdialog.ui
@@ -73,6 +74,7 @@ set(HOTSPOT_SRCS
7374
callgraphsettingspage.ui
7475
frequencypage.ui
7576
sourcepathsettings.ui
77+
perfsettingspage.ui
7678
# resources:
7779
resources.qrc
7880
)

src/jobtracker.h

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
SPDX-FileCopyrightText: Milian Wolff <[email protected]>
3+
SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected]
4+
5+
SPDX-License-Identifier: GPL-2.0-or-later
6+
*/
7+
8+
#pragma once
9+
10+
#include <ThreadWeaver/ThreadWeaver>
11+
#include <QObject>
12+
#include <QPointer>
13+
14+
class JobTracker
15+
{
16+
public:
17+
explicit JobTracker(QObject* context)
18+
: m_context(context)
19+
{
20+
}
21+
22+
bool isJobRunning() const
23+
{
24+
return m_context && m_isRunning;
25+
}
26+
27+
template<typename Job, typename SetData>
28+
void startJob(Job&& job, SetData&& setData)
29+
{
30+
using namespace ThreadWeaver;
31+
const auto jobId = ++m_currentJobId;
32+
auto jobCancelled = [context = m_context, jobId, currentJobId = &m_currentJobId]() {
33+
return !context || jobId != (*currentJobId);
34+
};
35+
auto maybeSetData = [jobCancelled, setData = std::forward<SetData>(setData),
36+
isRunning = &m_isRunning](auto&& results) {
37+
if (!jobCancelled()) {
38+
setData(std::forward<decltype(results)>(results));
39+
*isRunning = false;
40+
}
41+
};
42+
43+
m_isRunning = true;
44+
stream() << make_job([context = m_context, job = std::forward<Job>(job), maybeSetData = std::move(maybeSetData),
45+
jobCancelled = std::move(jobCancelled)]() mutable {
46+
auto results = job(jobCancelled);
47+
if (jobCancelled())
48+
return;
49+
50+
QMetaObject::invokeMethod(
51+
context.data(),
52+
[results = std::move(results), maybeSetData = std::move(maybeSetData)]() mutable {
53+
maybeSetData(std::move(results));
54+
},
55+
Qt::QueuedConnection);
56+
});
57+
}
58+
59+
private:
60+
QPointer<QObject> m_context;
61+
std::atomic<uint> m_currentJobId;
62+
bool m_isRunning = false;
63+
};

src/multiconfigwidget.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ void MultiConfigWidget::setConfig(const KConfigGroup& group)
7676
m_comboBox->clear();
7777
m_config = group;
7878

79+
if (!m_config.isValid())
80+
return;
81+
7982
const auto groups = m_config.groupList();
8083
for (const auto& config : groups) {
8184
if (m_config.hasGroup(config)) {

src/perfrecord.cpp

Lines changed: 10 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
#include "perfrecord.h"
1010

11+
#include "recordhost.h"
12+
1113
#include <QDebug>
1214
#include <QDir>
1315
#include <QFileInfo>
@@ -19,20 +21,13 @@
1921
#include <csignal>
2022
#include <unistd.h>
2123

22-
#include <KUser>
23-
2424
#include <kwindowsystem_version.h>
2525
#if KWINDOWSYSTEM_VERSION >= QT_VERSION_CHECK(5, 101, 0)
2626
#include <KX11Extras>
2727
#else
2828
#include <KWindowSystem>
2929
#endif
3030

31-
#include <hotspot-config.h>
32-
33-
#include <fstream>
34-
#include <sys/stat.h>
35-
3631
namespace {
3732
void createOutputFile(const QString& outputPath)
3833
{
@@ -44,17 +39,11 @@ void createOutputFile(const QString& outputPath)
4439
QFile::rename(outputPath, bakPath);
4540
QFile(outputPath).open(QIODevice::WriteOnly);
4641
}
47-
48-
QString findPkexec()
49-
{
50-
return QStandardPaths::findExecutable(QStringLiteral("pkexec"));
51-
}
5242
}
5343

54-
PerfRecord::PerfRecord(QObject* parent)
44+
PerfRecord::PerfRecord(const RecordHost* host, QObject* parent)
5545
: QObject(parent)
56-
, m_perfRecordProcess(nullptr)
57-
, m_userTerminated(false)
46+
, m_host(host)
5847
{
5948
connect(&m_perfControlFifo, &PerfControlFifoWrapper::started, this,
6049
[this]() { m_targetProcessForPrivilegedPerf.continueStoppedProcess(); });
@@ -72,38 +61,6 @@ PerfRecord::~PerfRecord()
7261
}
7362
}
7463

75-
static bool privsAlreadyElevated()
76-
{
77-
auto readSysctl = [](const char* path) {
78-
std::ifstream ifs {path};
79-
int i = std::numeric_limits<int>::min();
80-
if (ifs) {
81-
ifs >> i;
82-
}
83-
return i;
84-
};
85-
86-
bool isElevated = readSysctl("/proc/sys/kernel/kptr_restrict") == 0;
87-
if (!isElevated) {
88-
return false;
89-
}
90-
91-
isElevated = readSysctl("/proc/sys/kernel/perf_event_paranoid") == -1;
92-
if (!isElevated) {
93-
return false;
94-
}
95-
96-
auto checkPerms = [](const char* path) {
97-
const mode_t required = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; // 755
98-
struct stat buf;
99-
return stat(path, &buf) == 0 && ((buf.st_mode & 07777) & required) == required;
100-
};
101-
static const auto paths = {"/sys/kernel/debug", "/sys/kernel/debug/tracing"};
102-
isElevated = std::all_of(paths.begin(), paths.end(), checkPerms);
103-
104-
return isElevated;
105-
}
106-
10764
bool PerfRecord::runPerf(bool elevatePrivileges, const QStringList& perfOptions, const QString& outputPath,
10865
const QString& workingDirectory)
10966
{
@@ -176,14 +133,14 @@ bool PerfRecord::runPerf(bool elevatePrivileges, const QStringList& perfOptions,
176133
perfCommand += perfOptions;
177134

178135
if (elevatePrivileges) {
179-
const auto pkexec = findPkexec();
136+
const auto pkexec = m_host->pkexecBinaryPath();
180137
if (pkexec.isEmpty()) {
181138
emit recordingFailed(tr("The pkexec utility was not found, cannot elevate privileges."));
182139
return false;
183140
}
184141

185142
auto options = QStringList();
186-
options.append(perfBinaryPath());
143+
options.append(m_host->perfBinaryPath());
187144
options += perfCommand;
188145

189146
if (!m_perfControlFifo.open()) {
@@ -198,7 +155,7 @@ bool PerfRecord::runPerf(bool elevatePrivileges, const QStringList& perfOptions,
198155

199156
m_perfRecordProcess->start(pkexec, options);
200157
} else {
201-
m_perfRecordProcess->start(perfBinaryPath(), perfCommand);
158+
m_perfRecordProcess->start(m_host->perfBinaryPath(), perfCommand);
202159
}
203160

204161
return true;
@@ -295,106 +252,13 @@ void PerfRecord::sendInput(const QByteArray& input)
295252
m_perfRecordProcess->write(input);
296253
}
297254

298-
QString PerfRecord::currentUsername()
299-
{
300-
return KUser().loginName();
301-
}
302-
303-
bool PerfRecord::canTrace(const QString& path)
304-
{
305-
const auto info = QFileInfo(QLatin1String("/sys/kernel/debug/tracing/") + path);
306-
if (!info.isDir() || !info.isReadable()) {
307-
return false;
308-
}
309-
QFile paranoid(QStringLiteral("/proc/sys/kernel/perf_event_paranoid"));
310-
return paranoid.open(QIODevice::ReadOnly) && paranoid.readAll().trimmed() == "-1";
311-
}
312-
313-
static QByteArray perfOutput(const QStringList& arguments)
314-
{
315-
QProcess process;
316-
317-
auto reportError = [&]() {
318-
qWarning() << "Failed to run perf" << process.arguments() << process.error() << process.errorString()
319-
<< process.readAllStandardError();
320-
};
321-
322-
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
323-
env.insert(QStringLiteral("LANG"), QStringLiteral("C"));
324-
process.setProcessEnvironment(env);
325-
326-
QObject::connect(&process, &QProcess::errorOccurred, &process, reportError);
327-
process.start(PerfRecord::perfBinaryPath(), arguments);
328-
if (!process.waitForFinished(1000) || process.exitCode() != 0)
329-
reportError();
330-
return process.readAllStandardOutput();
331-
}
332-
333-
static QByteArray perfRecordHelp()
334-
{
335-
static const QByteArray recordHelp = []() {
336-
static QByteArray help = perfOutput({QStringLiteral("record"), QStringLiteral("--help")});
337-
if (help.isEmpty()) {
338-
// no man page installed, assume the best
339-
help = "--sample-cpu --switch-events";
340-
}
341-
return help;
342-
}();
343-
return recordHelp;
344-
}
345-
346-
static QByteArray perfBuildOptions()
347-
{
348-
static const QByteArray buildOptions = perfOutput({QStringLiteral("version"), QStringLiteral("--build-options")});
349-
return buildOptions;
350-
}
351-
352-
bool PerfRecord::canProfileOffCpu()
353-
{
354-
return canTrace(QStringLiteral("events/sched/sched_switch"));
355-
}
356-
357255
QStringList PerfRecord::offCpuProfilingOptions()
358256
{
359257
return {QStringLiteral("--switch-events"), QStringLiteral("--event"), QStringLiteral("sched:sched_switch")};
360258
}
361259

362-
bool PerfRecord::canSampleCpu()
363-
{
364-
return perfRecordHelp().contains("--sample-cpu");
365-
}
366-
367-
bool PerfRecord::canSwitchEvents()
368-
{
369-
return perfRecordHelp().contains("--switch-events");
370-
}
371-
372-
bool PerfRecord::canUseAio()
373-
{
374-
return perfBuildOptions().contains("aio: [ on ]");
375-
}
376-
377-
bool PerfRecord::canCompress()
378-
{
379-
return Zstd_FOUND && perfBuildOptions().contains("zstd: [ on ]");
380-
}
381-
382-
bool PerfRecord::canElevatePrivileges()
383-
{
384-
return !findPkexec().isEmpty();
385-
}
386-
387-
QString PerfRecord::perfBinaryPath()
388-
{
389-
return QStandardPaths::findExecutable(QStringLiteral("perf"));
390-
}
391-
392-
bool PerfRecord::isPerfInstalled()
393-
{
394-
return !perfBinaryPath().isEmpty();
395-
}
396-
397-
bool PerfRecord::actuallyElevatePrivileges(bool elevatePrivileges)
260+
bool PerfRecord::actuallyElevatePrivileges(bool elevatePrivileges) const
398261
{
399-
return elevatePrivileges && canElevatePrivileges() && geteuid() != 0 && !privsAlreadyElevated();
262+
const auto capabilities = m_host->perfCapabilities();
263+
return elevatePrivileges && capabilities.canElevatePrivileges && !capabilities.privilegesAlreadyElevated;
400264
}

src/perfrecord.h

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
#include <QPointer>
1616

1717
class QProcess;
18+
class RecordHost;
1819

1920
class PerfRecord : public QObject
2021
{
2122
Q_OBJECT
2223
public:
23-
explicit PerfRecord(QObject* parent = nullptr);
24+
explicit PerfRecord(const RecordHost* host, QObject* parent = nullptr);
2425
~PerfRecord();
2526

2627
void record(const QStringList& perfOptions, const QString& outputPath, bool elevatePrivileges,
@@ -33,21 +34,8 @@ class PerfRecord : public QObject
3334
void stopRecording();
3435
void sendInput(const QByteArray& input);
3536

36-
static QString currentUsername();
37-
38-
static bool canTrace(const QString& path);
39-
static bool canProfileOffCpu();
40-
static bool canSampleCpu();
41-
static bool canSwitchEvents();
42-
static bool canUseAio();
43-
static bool canCompress();
44-
static bool canElevatePrivileges();
45-
4637
static QStringList offCpuProfilingOptions();
4738

48-
static QString perfBinaryPath();
49-
static bool isPerfInstalled();
50-
5139
signals:
5240
void recordingStarted(const QString& perfBinary, const QStringList& arguments);
5341
void recordingFinished(const QString& fileLocation);
@@ -56,13 +44,14 @@ class PerfRecord : public QObject
5644
void debuggeeCrashed();
5745

5846
private:
47+
const RecordHost* m_host = nullptr;
5948
QPointer<QProcess> m_perfRecordProcess;
6049
InitiallyStoppedProcess m_targetProcessForPrivilegedPerf;
6150
PerfControlFifoWrapper m_perfControlFifo;
6251
QString m_outputPath;
63-
bool m_userTerminated;
52+
bool m_userTerminated = false;
6453

65-
static bool actuallyElevatePrivileges(bool elevatePrivileges);
54+
bool actuallyElevatePrivileges(bool elevatePrivileges) const;
6655

6756
bool runPerf(bool elevatePrivileges, const QStringList& perfOptions, const QString& outputPath,
6857
const QString& workingDirectory = QString());

0 commit comments

Comments
 (0)