Skip to content

Commit 427a893

Browse files
committed
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 df6cc5e commit 427a893

17 files changed

+807
-314
lines changed

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ set(HOTSPOT_SRCS
5252
perfoutputwidgettext.cpp
5353
perfoutputwidgetkonsole.cpp
5454
costcontextmenu.cpp
55+
recordhost.cpp
5556
# ui files:
5657
mainwindow.ui
5758
aboutdialog.ui
@@ -71,6 +72,7 @@ set(HOTSPOT_SRCS
7172
callgraphwidget.ui
7273
callgraphsettingspage.ui
7374
frequencypage.ui
75+
perfsettingspage.ui
7476
# resources:
7577
resources.qrc
7678
)

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: Lieven Hey <[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/perfrecord.cpp

Lines changed: 6 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ static bool privsAlreadyElevated()
106106
return isElevated;
107107
}
108108

109+
QStringList PerfRecord::offCpuProfilingOptions()
110+
{
111+
return {QStringLiteral("--switch-events"), QStringLiteral("--event"), QStringLiteral("sched:sched_switch")};
112+
}
113+
109114
void PerfRecord::startRecording(bool elevatePrivileges, const QStringList& perfOptions, const QString& outputPath,
110115
const QStringList& recordOptions, const QString& workingDirectory)
111116
{
@@ -115,7 +120,7 @@ void PerfRecord::startRecording(bool elevatePrivileges, const QStringList& perfO
115120
// then parse its output and once we get the "waiting..." line the privileges got elevated
116121
// in that case, we can continue to start perf and quit the elevate_perf_privileges.sh script
117122
// once perf has started
118-
const auto sudoBinary = sudoUtil();
123+
const auto sudoBinary = Util::sudoUtil();
119124
if (sudoBinary.isEmpty()) {
120125
emit recordingFailed(tr("No sudo utility found. Please install pkexec, kdesudo or kdesu."));
121126
return;
@@ -395,107 +400,3 @@ void PerfRecord::sendInput(const QByteArray& input)
395400
Q_ASSERT(m_perfRecordProcess);
396401
m_perfRecordProcess->write(input);
397402
}
398-
399-
QString PerfRecord::sudoUtil()
400-
{
401-
const auto commands = {
402-
QStringLiteral("pkexec"), QStringLiteral("kdesudo"), QStringLiteral("kdesu"),
403-
// gksudo / gksu seem to close stdin and thus the elevate script doesn't wait on read
404-
};
405-
for (const auto& cmd : commands) {
406-
QString util = QStandardPaths::findExecutable(cmd);
407-
if (!util.isEmpty()) {
408-
return util;
409-
}
410-
}
411-
return {};
412-
}
413-
414-
QString PerfRecord::currentUsername()
415-
{
416-
return KUser().loginName();
417-
}
418-
419-
bool PerfRecord::canTrace(const QString& path)
420-
{
421-
QFileInfo info(QLatin1String("/sys/kernel/debug/tracing/") + path);
422-
if (!info.isDir() || !info.isReadable()) {
423-
return false;
424-
}
425-
QFile paranoid(QStringLiteral("/proc/sys/kernel/perf_event_paranoid"));
426-
return paranoid.open(QIODevice::ReadOnly) && paranoid.readAll().trimmed() == "-1";
427-
}
428-
429-
static QByteArray perfOutput(const QStringList& arguments)
430-
{
431-
QProcess process;
432-
433-
auto reportError = [&]() {
434-
qWarning() << "Failed to run perf" << process.arguments() << process.error() << process.errorString()
435-
<< process.readAllStandardError();
436-
};
437-
438-
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
439-
env.insert(QStringLiteral("LANG"), QStringLiteral("C"));
440-
process.setProcessEnvironment(env);
441-
442-
QObject::connect(&process, &QProcess::errorOccurred, &process, reportError);
443-
process.start(QStringLiteral("perf"), arguments);
444-
if (!process.waitForFinished(1000) || process.exitCode() != 0)
445-
reportError();
446-
return process.readAllStandardOutput();
447-
}
448-
449-
static QByteArray perfRecordHelp()
450-
{
451-
static const QByteArray recordHelp = []() {
452-
static QByteArray help = perfOutput({QStringLiteral("record"), QStringLiteral("--help")});
453-
if (help.isEmpty()) {
454-
// no man page installed, assume the best
455-
help = "--sample-cpu --switch-events";
456-
}
457-
return help;
458-
}();
459-
return recordHelp;
460-
}
461-
462-
static QByteArray perfBuildOptions()
463-
{
464-
static const QByteArray buildOptions = perfOutput({QStringLiteral("version"), QStringLiteral("--build-options")});
465-
return buildOptions;
466-
}
467-
468-
bool PerfRecord::canProfileOffCpu()
469-
{
470-
return canTrace(QStringLiteral("events/sched/sched_switch"));
471-
}
472-
473-
QStringList PerfRecord::offCpuProfilingOptions()
474-
{
475-
return {QStringLiteral("--switch-events"), QStringLiteral("--event"), QStringLiteral("sched:sched_switch")};
476-
}
477-
478-
bool PerfRecord::canSampleCpu()
479-
{
480-
return perfRecordHelp().contains("--sample-cpu");
481-
}
482-
483-
bool PerfRecord::canSwitchEvents()
484-
{
485-
return perfRecordHelp().contains("--switch-events");
486-
}
487-
488-
bool PerfRecord::canUseAio()
489-
{
490-
return perfBuildOptions().contains("aio: [ on ]");
491-
}
492-
493-
bool PerfRecord::canCompress()
494-
{
495-
return Zstd_FOUND && perfBuildOptions().contains("zstd: [ on ]");
496-
}
497-
498-
bool PerfRecord::isPerfInstalled()
499-
{
500-
return !QStandardPaths::findExecutable(QStringLiteral("perf")).isEmpty();
501-
}

src/perfrecord.h

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,8 @@ class PerfRecord : public QObject
3030
void stopRecording();
3131
void sendInput(const QByteArray& input);
3232

33-
static QString sudoUtil();
34-
static QString currentUsername();
35-
36-
static bool canTrace(const QString& path);
37-
static bool canProfileOffCpu();
38-
static bool canSampleCpu();
39-
static bool canSwitchEvents();
40-
static bool canUseAio();
41-
static bool canCompress();
42-
4333
static QStringList offCpuProfilingOptions();
4434

45-
static bool isPerfInstalled();
46-
4735
signals:
4836
void recordingStarted(const QString& perfBinary, const QStringList& arguments);
4937
void recordingFinished(const QString& fileLocation);

src/perfsettingspage.ui

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>PerfSettingsPage</class>
4+
<widget class="QWidget" name="PerfSettingsPage">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>400</width>
10+
<height>300</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Form</string>
15+
</property>
16+
<layout class="QFormLayout" name="formLayout">
17+
<item row="1" column="0">
18+
<widget class="QLabel" name="label">
19+
<property name="text">
20+
<string>Perf Binary:</string>
21+
</property>
22+
</widget>
23+
</item>
24+
<item row="1" column="1">
25+
<widget class="KUrlRequester" name="perfPathEdit"/>
26+
</item>
27+
</layout>
28+
</widget>
29+
<customwidgets>
30+
<customwidget>
31+
<class>KUrlRequester</class>
32+
<extends>QWidget</extends>
33+
<header>kurlrequester.h</header>
34+
</customwidget>
35+
</customwidgets>
36+
<resources/>
37+
<connections/>
38+
</ui>

0 commit comments

Comments
 (0)