Skip to content

Commit

Permalink
add perf query interface and untangle it from recordpage
Browse files Browse the repository at this point in the history
moving the query part of the recordpage into its own class will make
implementing remote recording much easier
  • Loading branch information
lievenhey authored and milianw committed Aug 30, 2023
1 parent f4a310c commit dcc02ad
Show file tree
Hide file tree
Showing 16 changed files with 854 additions and 362 deletions.
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ set(HOTSPOT_SRCS
initiallystoppedprocess.cpp
perfcontrolfifowrapper.cpp
errnoutil.cpp
recordhost.cpp
# ui files:
mainwindow.ui
aboutdialog.ui
Expand All @@ -73,6 +74,7 @@ set(HOTSPOT_SRCS
callgraphsettingspage.ui
frequencypage.ui
sourcepathsettings.ui
perfsettingspage.ui
# resources:
resources.qrc
)
Expand Down
63 changes: 63 additions & 0 deletions src/jobtracker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
SPDX-FileCopyrightText: Milian Wolff <[email protected]>
SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected]
SPDX-License-Identifier: GPL-2.0-or-later
*/

#pragma once

#include <ThreadWeaver/ThreadWeaver>
#include <QObject>
#include <QPointer>

class JobTracker
{
public:
explicit JobTracker(QObject* context)
: m_context(context)
{
}

bool isJobRunning() const
{
return m_context && m_isRunning;
}

template<typename Job, typename SetData>
void startJob(Job&& job, SetData&& setData)
{
using namespace ThreadWeaver;
const auto jobId = ++m_currentJobId;
auto jobCancelled = [context = m_context, jobId, currentJobId = &m_currentJobId]() {
return !context || jobId != (*currentJobId);
};
auto maybeSetData = [jobCancelled, setData = std::forward<SetData>(setData),
isRunning = &m_isRunning](auto&& results) {
if (!jobCancelled()) {
setData(std::forward<decltype(results)>(results));
*isRunning = false;
}
};

m_isRunning = true;
stream() << make_job([context = m_context, job = std::forward<Job>(job), maybeSetData = std::move(maybeSetData),
jobCancelled = std::move(jobCancelled)]() mutable {
auto results = job(jobCancelled);
if (jobCancelled())
return;

QMetaObject::invokeMethod(
context.data(),
[results = std::move(results), maybeSetData = std::move(maybeSetData)]() mutable {
maybeSetData(std::move(results));
},
Qt::QueuedConnection);
});
}

private:
QPointer<QObject> m_context;
std::atomic<uint> m_currentJobId;
bool m_isRunning = false;
};
3 changes: 3 additions & 0 deletions src/multiconfigwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ void MultiConfigWidget::setConfig(const KConfigGroup& group)
m_comboBox->clear();
m_config = group;

if (!m_config.isValid())
return;

const auto groups = m_config.groupList();
for (const auto& config : groups) {
if (m_config.hasGroup(config)) {
Expand Down
156 changes: 10 additions & 146 deletions src/perfrecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#include "perfrecord.h"

#include "recordhost.h"

#include <QDebug>
#include <QDir>
#include <QFileInfo>
Expand All @@ -19,20 +21,13 @@
#include <csignal>
#include <unistd.h>

#include <KUser>

#include <kwindowsystem_version.h>
#if KWINDOWSYSTEM_VERSION >= QT_VERSION_CHECK(5, 101, 0)
#include <KX11Extras>
#else
#include <KWindowSystem>
#endif

#include <hotspot-config.h>

#include <fstream>
#include <sys/stat.h>

namespace {
void createOutputFile(const QString& outputPath)
{
Expand All @@ -44,17 +39,11 @@ void createOutputFile(const QString& outputPath)
QFile::rename(outputPath, bakPath);
QFile(outputPath).open(QIODevice::WriteOnly);
}

QString findPkexec()
{
return QStandardPaths::findExecutable(QStringLiteral("pkexec"));
}
}

PerfRecord::PerfRecord(QObject* parent)
PerfRecord::PerfRecord(const RecordHost *host, QObject* parent)
: QObject(parent)
, m_perfRecordProcess(nullptr)
, m_userTerminated(false)
, m_host(host)
{
connect(&m_perfControlFifo, &PerfControlFifoWrapper::started, this,
[this]() { m_targetProcessForPrivilegedPerf.continueStoppedProcess(); });
Expand All @@ -72,38 +61,6 @@ PerfRecord::~PerfRecord()
}
}

static bool privsAlreadyElevated()
{
auto readSysctl = [](const char* path) {
std::ifstream ifs {path};
int i = std::numeric_limits<int>::min();
if (ifs) {
ifs >> i;
}
return i;
};

bool isElevated = readSysctl("/proc/sys/kernel/kptr_restrict") == 0;
if (!isElevated) {
return false;
}

isElevated = readSysctl("/proc/sys/kernel/perf_event_paranoid") == -1;
if (!isElevated) {
return false;
}

auto checkPerms = [](const char* path) {
const mode_t required = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; // 755
struct stat buf;
return stat(path, &buf) == 0 && ((buf.st_mode & 07777) & required) == required;
};
static const auto paths = {"/sys/kernel/debug", "/sys/kernel/debug/tracing"};
isElevated = std::all_of(paths.begin(), paths.end(), checkPerms);

return isElevated;
}

bool PerfRecord::runPerf(bool elevatePrivileges, const QStringList& perfOptions, const QString& outputPath,
const QString& workingDirectory)
{
Expand Down Expand Up @@ -176,14 +133,14 @@ bool PerfRecord::runPerf(bool elevatePrivileges, const QStringList& perfOptions,
perfCommand += perfOptions;

if (elevatePrivileges) {
const auto pkexec = findPkexec();
const auto pkexec = m_host->pkexecBinaryPath();
if (pkexec.isEmpty()) {
emit recordingFailed(tr("The pkexec utility was not found, cannot elevate privileges."));
return false;
}

auto options = QStringList();
options.append(perfBinaryPath());
options.append(m_host->perfBinaryPath());
options += perfCommand;

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

m_perfRecordProcess->start(pkexec, options);
} else {
m_perfRecordProcess->start(perfBinaryPath(), perfCommand);
m_perfRecordProcess->start(m_host->perfBinaryPath(), perfCommand);
}

return true;
Expand Down Expand Up @@ -295,106 +252,13 @@ void PerfRecord::sendInput(const QByteArray& input)
m_perfRecordProcess->write(input);
}

QString PerfRecord::currentUsername()
{
return KUser().loginName();
}

bool PerfRecord::canTrace(const QString& path)
{
const auto info = QFileInfo(QLatin1String("/sys/kernel/debug/tracing/") + path);
if (!info.isDir() || !info.isReadable()) {
return false;
}
QFile paranoid(QStringLiteral("/proc/sys/kernel/perf_event_paranoid"));
return paranoid.open(QIODevice::ReadOnly) && paranoid.readAll().trimmed() == "-1";
}

static QByteArray perfOutput(const QStringList& arguments)
{
QProcess process;

auto reportError = [&]() {
qWarning() << "Failed to run perf" << process.arguments() << process.error() << process.errorString()
<< process.readAllStandardError();
};

QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert(QStringLiteral("LANG"), QStringLiteral("C"));
process.setProcessEnvironment(env);

QObject::connect(&process, &QProcess::errorOccurred, &process, reportError);
process.start(PerfRecord::perfBinaryPath(), arguments);
if (!process.waitForFinished(1000) || process.exitCode() != 0)
reportError();
return process.readAllStandardOutput();
}

static QByteArray perfRecordHelp()
{
static const QByteArray recordHelp = []() {
static QByteArray help = perfOutput({QStringLiteral("record"), QStringLiteral("--help")});
if (help.isEmpty()) {
// no man page installed, assume the best
help = "--sample-cpu --switch-events";
}
return help;
}();
return recordHelp;
}

static QByteArray perfBuildOptions()
{
static const QByteArray buildOptions = perfOutput({QStringLiteral("version"), QStringLiteral("--build-options")});
return buildOptions;
}

bool PerfRecord::canProfileOffCpu()
{
return canTrace(QStringLiteral("events/sched/sched_switch"));
}

QStringList PerfRecord::offCpuProfilingOptions()
{
return {QStringLiteral("--switch-events"), QStringLiteral("--event"), QStringLiteral("sched:sched_switch")};
}

bool PerfRecord::canSampleCpu()
{
return perfRecordHelp().contains("--sample-cpu");
}

bool PerfRecord::canSwitchEvents()
{
return perfRecordHelp().contains("--switch-events");
}

bool PerfRecord::canUseAio()
{
return perfBuildOptions().contains("aio: [ on ]");
}

bool PerfRecord::canCompress()
{
return Zstd_FOUND && perfBuildOptions().contains("zstd: [ on ]");
}

bool PerfRecord::canElevatePrivileges()
{
return !findPkexec().isEmpty();
}

QString PerfRecord::perfBinaryPath()
{
return QStandardPaths::findExecutable(QStringLiteral("perf"));
}

bool PerfRecord::isPerfInstalled()
{
return !perfBinaryPath().isEmpty();
}

bool PerfRecord::actuallyElevatePrivileges(bool elevatePrivileges)
bool PerfRecord::actuallyElevatePrivileges(bool elevatePrivileges) const
{
return elevatePrivileges && canElevatePrivileges() && geteuid() != 0 && !privsAlreadyElevated();
const auto capabilities = m_host->perfCapabilities();
return elevatePrivileges && capabilities.canElevatePrivileges && !capabilities.privilegesAlreadyElevated;
}
21 changes: 5 additions & 16 deletions src/perfrecord.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
#include <QPointer>

class QProcess;
class RecordHost;

class PerfRecord : public QObject
{
Q_OBJECT
public:
explicit PerfRecord(QObject* parent = nullptr);
explicit PerfRecord(const RecordHost *host, QObject* parent = nullptr);
~PerfRecord();

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

static QString currentUsername();

static bool canTrace(const QString& path);
static bool canProfileOffCpu();
static bool canSampleCpu();
static bool canSwitchEvents();
static bool canUseAio();
static bool canCompress();
static bool canElevatePrivileges();

static QStringList offCpuProfilingOptions();

static QString perfBinaryPath();
static bool isPerfInstalled();

signals:
void recordingStarted(const QString& perfBinary, const QStringList& arguments);
void recordingFinished(const QString& fileLocation);
Expand All @@ -56,13 +44,14 @@ class PerfRecord : public QObject
void debuggeeCrashed();

private:
const RecordHost *m_host = nullptr;
QPointer<QProcess> m_perfRecordProcess;
InitiallyStoppedProcess m_targetProcessForPrivilegedPerf;
PerfControlFifoWrapper m_perfControlFifo;
QString m_outputPath;
bool m_userTerminated;
bool m_userTerminated = false;

static bool actuallyElevatePrivileges(bool elevatePrivileges);
bool actuallyElevatePrivileges(bool elevatePrivileges) const;

bool runPerf(bool elevatePrivileges, const QStringList& perfOptions, const QString& outputPath,
const QString& workingDirectory = QString());
Expand Down
Loading

0 comments on commit dcc02ad

Please sign in to comment.