Utils: Improve speed of ProcessList

Using "find" is considerably slower than "ps" so we try
ps first before using find as a fallback.

We also add error output if all three methods fail.

Amends: 2d15be91bf

Fixes: QTCREATORBUG-32738
Change-Id: I19151c8d9f74e6fb1850319f60f41bfab64386e8
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2025-04-10 08:47:05 +02:00
parent e29d440a47
commit 2d0fc6a3ba

View File

@@ -5,6 +5,7 @@
#include "algorithm.h"
#include "qtcprocess.h"
#include "utilstr.h"
#include <QDir>
#include <QRegularExpression>
@@ -31,11 +32,16 @@ bool ProcessInfo::operator<(const ProcessInfo &other) const
return commandLine < other.commandLine;
}
// Determine UNIX processes by reading "/proc". Default to ps if
// it does not exist
static QList<ProcessInfo> getLocalProcessesUsingProc(const FilePath &procDir)
static expected_str<QList<ProcessInfo>> getLocalProcessesUsingProc(const FilePath &devicePath)
{
const FilePath procDir = devicePath.withNewPath("/proc");
if (!procDir.exists())
return make_unexpected(Tr::tr("%1 does not exist").arg(procDir.toUserOutput()));
const FilePath find = devicePath.withNewPath("find").searchInPath();
if (!find.isExecutableFile())
return make_unexpected(Tr::tr("find is not an existing executable"));
static const QString execs = "-exec test -f {}/exe \\; "
"-exec test -f {}/cmdline \\; "
"-exec echo -en 'p{}\\ne' \\; "
@@ -45,8 +51,7 @@ static QList<ProcessInfo> getLocalProcessesUsingProc(const FilePath &procDir)
"-exec echo \\; "
"-exec echo __SKIP_ME__ \\;";
CommandLine cmd{procDir.withNewPath("find"),
{procDir.nativePath(), "-maxdepth", "1", "-type", "d", "-name", "[0-9]*"}};
CommandLine cmd{find, {procDir.nativePath(), "-maxdepth", "1", "-type", "d", "-name", "[0-9]*"}};
cmd.addArgs(execs, CommandLine::Raw);
@@ -54,6 +59,13 @@ static QList<ProcessInfo> getLocalProcessesUsingProc(const FilePath &procDir)
procProcess.setCommand(cmd);
procProcess.runBlocking();
// We can only check the errorString here. The exit code maybe != 0 if one of the "test"s failed.
if (!procProcess.errorString().isEmpty()) {
return make_unexpected(Tr::tr("Failed to run %1: %2")
.arg(cmd.executable().toUserOutput())
.arg(procProcess.errorString()));
}
QList<ProcessInfo> processes;
const auto lines = procProcess.readAllStandardOutput().split('\n');
@@ -95,12 +107,22 @@ static QList<ProcessInfo> getLocalProcessesUsingProc(const FilePath &procDir)
}
// Determine UNIX processes by running ps
static QMap<qint64, QString> getLocalProcessDataUsingPs(const FilePath &deviceRoot,
const QString &column)
static expected_str<QMap<qint64, QString>> getLocalProcessDataUsingPs(
const FilePath &ps, const QString &column)
{
Process process;
process.setCommand({deviceRoot.withNewPath("ps"), {"-e", "-o", "pid," + column}});
process.setCommand({ps, {"-e", "-o", "pid," + column}});
process.runBlocking();
if (!process.errorString().isEmpty()) {
return make_unexpected(
Tr::tr("Failed to run %1: %2").arg(ps.toUserOutput()).arg(process.errorString()));
}
if (process.exitCode() != 0) {
return make_unexpected(Tr::tr("Failed to run %1: %2")
.arg(ps.toUserOutput())
.arg(process.readAllStandardError()));
}
// Split "457 /Users/foo.app arg1 arg2"
const QStringList lines = process.readAllStandardOutput().split(QLatin1Char('\n'));
@@ -114,21 +136,30 @@ static QMap<qint64, QString> getLocalProcessDataUsingPs(const FilePath &deviceRo
return result;
}
static QList<ProcessInfo> getLocalProcessesUsingPs(const FilePath &deviceRoot)
static expected_str<QList<ProcessInfo>> getLocalProcessesUsingPs(const FilePath &deviceRoot)
{
QList<ProcessInfo> processes;
const FilePath ps = deviceRoot.withNewPath("ps").searchInPath();
if (!ps.isExecutableFile())
return make_unexpected(Tr::tr("ps is not an existing executable"));
// cmdLines are full command lines, usually with absolute path,
// exeNames only the file part of the executable's path.
const QMap<qint64, QString> exeNames = getLocalProcessDataUsingPs(deviceRoot, "comm");
const QMap<qint64, QString> cmdLines = getLocalProcessDataUsingPs(deviceRoot, "args");
const auto exeNames = getLocalProcessDataUsingPs(ps, "comm");
if (!exeNames)
return make_unexpected(exeNames.error());
for (auto it = exeNames.begin(), end = exeNames.end(); it != end; ++it) {
const auto cmdLines = getLocalProcessDataUsingPs(ps, "args");
if (!cmdLines)
return make_unexpected(cmdLines.error());
for (auto it = exeNames->begin(), end = exeNames->end(); it != end; ++it) {
const qint64 pid = it.key();
if (pid <= 0)
continue;
const QString cmdLine = cmdLines.value(pid);
if (cmdLines.isEmpty())
const QString cmdLine = cmdLines->value(pid);
if (cmdLines->isEmpty())
continue;
const QString exeName = it.value();
if (exeName.isEmpty())
@@ -142,11 +173,21 @@ static QList<ProcessInfo> getLocalProcessesUsingPs(const FilePath &deviceRoot)
return processes;
}
static QList<ProcessInfo> getProcessesUsingPidin(const FilePath &pidin)
static expected_str<QList<ProcessInfo>> getProcessesUsingPidin(const FilePath &deviceRoot)
{
const FilePath pidin = deviceRoot.withNewPath("pidin").searchInPath();
if (!pidin.isExecutableFile())
return make_unexpected(Tr::tr("pidin is not an existing executable"));
Process process;
process.setCommand({pidin, {"-F", "%a %A {/%n}"}});
process.runBlocking();
if (process.errorString().isEmpty()) {
return make_unexpected(
Tr::tr("Failed to run %1: %2").arg(pidin.toUserOutput()).arg(process.errorString()));
}
if (process.exitCode() != 0)
return make_unexpected(process.readAllStandardError());
QList<ProcessInfo> processes;
QStringList lines = process.readAllStandardOutput().split(QLatin1Char('\n'));
@@ -176,25 +217,37 @@ static QList<ProcessInfo> getProcessesUsingPidin(const FilePath &pidin)
return Utils::sorted(std::move(processes));
}
static QList<ProcessInfo> processInfoListUnix(const FilePath &deviceRoot)
static expected_str<QList<ProcessInfo>> processInfoListUnix(const FilePath &deviceRoot)
{
const FilePath procDir = deviceRoot.withNewPath("/proc");
const FilePath pidin = deviceRoot.withNewPath("pidin").searchInPath();
if (pidin.isExecutableFile())
return getProcessesUsingPidin(pidin);
if (procDir.isReadableDir())
return getLocalProcessesUsingProc(procDir);
return getLocalProcessesUsingPs(deviceRoot);
return getLocalProcessesUsingPs(deviceRoot)
.or_else([&deviceRoot](const QString &error) {
return getProcessesUsingPidin(deviceRoot)
.transform_error([error](const QString &pidinError) {
return Tr::tr("Failed to run pidin: %1\n%2").arg(pidinError).arg(error);
});
})
.or_else([&deviceRoot](const QString &error) {
return getLocalProcessesUsingProc(deviceRoot)
.transform_error([error](const QString &procError) {
return Tr::tr("Failed to run /proc: %1\n%2").arg(procError).arg(error);
});
})
.transform_error([](const QString &error) {
return Tr::tr("Failed to retrieve process information: %1").arg(error);
});
}
#if defined(Q_OS_UNIX)
QList<ProcessInfo> ProcessInfo::processInfoList(const FilePath &deviceRoot)
{
return processInfoListUnix(deviceRoot);
auto result = processInfoListUnix(deviceRoot);
if (!result) {
qWarning().noquote() << result.error();
return {};
}
return *result;
}
#elif defined(Q_OS_WIN)