File indexing completed on 2024-04-28 16:49:55

0001 /*
0002     SPDX-FileCopyrightText: 2007 John Tapsell <tapsell@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "processes.h"
0008 #include "processes_atop_p.h"
0009 #include "processes_base_p.h"
0010 #include "processes_local_p.h"
0011 #include "processes_remote_p.h"
0012 
0013 #include <QByteArray>
0014 #include <QElapsedTimer>
0015 #include <QMutableSetIterator>
0016 #include <QSet>
0017 #include <QVariantMap>
0018 
0019 // for sysconf
0020 #include <unistd.h>
0021 
0022 /* if porting to an OS without signal.h  please #define SIGTERM to something */
0023 #include <signal.h>
0024 
0025 namespace KSysGuard
0026 {
0027 class Q_DECL_HIDDEN Processes::Private
0028 {
0029 public:
0030     Private(Processes *q_ptr)
0031     {
0032         mFakeProcess.setParent(&mFakeProcess);
0033         mAbstractProcesses = nullptr;
0034         mHistoricProcesses = nullptr;
0035         mIsLocalHost = true;
0036         mProcesses.insert(-1, &mFakeProcess);
0037         mElapsedTimeMilliSeconds = 0;
0038         mUpdateFlags = {};
0039         mUsingHistoricalData = false;
0040         mLastError = Error::NoError;
0041         q = q_ptr;
0042     }
0043     ~Private();
0044     void markProcessesAsEnded(long pid);
0045 
0046     QSet<long> mToBeProcessed;
0047     QSet<long> mEndedProcesses; ///< Processes that have finished
0048 
0049     QHash<long, Process *> mProcesses; ///< This must include mFakeProcess at pid -1
0050     QList<Process *> mListProcesses; ///< A list of the processes.  Does not include mFakeProcesses
0051     Process mFakeProcess; ///< A fake process with pid -1 just so that even init points to a parent
0052 
0053     AbstractProcesses *mAbstractProcesses; ///< The OS specific code to get the process information
0054     ProcessesATop *mHistoricProcesses; ///< A way to get historic information about processes
0055     bool mIsLocalHost; ///< Whether this is localhost or not
0056 
0057     QElapsedTimer mLastUpdated; ///< This is the time we last updated.  Used to calculate cpu usage.
0058     long mElapsedTimeMilliSeconds; ///< The number of milliseconds  (1000ths of a second) that passed since the last update
0059 
0060     Processes::UpdateFlags mUpdateFlags;
0061     bool mUsingHistoricalData; ///< Whether to return historical data for updateProcess() etc
0062     Processes *q;
0063 
0064     Error mLastError;
0065 };
0066 
0067 Processes::Private::~Private()
0068 {
0069     Q_FOREACH (Process *process, mProcesses) {
0070         if (process != &mFakeProcess)
0071             delete process;
0072     }
0073     mProcesses.clear();
0074     mListProcesses.clear();
0075     delete mAbstractProcesses;
0076     mAbstractProcesses = nullptr;
0077     delete mHistoricProcesses;
0078     mHistoricProcesses = nullptr;
0079 }
0080 
0081 Processes::Processes(const QString &host, QObject *parent)
0082     : QObject(parent)
0083     , d(new Private(this))
0084 {
0085     qRegisterMetaType<KSysGuard::Process::Updates>();
0086 
0087     if (host.isEmpty()) {
0088         d->mAbstractProcesses = new ProcessesLocal();
0089     } else {
0090         ProcessesRemote *remote = new ProcessesRemote(host);
0091         d->mAbstractProcesses = remote;
0092         connect(remote, &ProcessesRemote::runCommand, this, &Processes::runCommand);
0093     }
0094     d->mIsLocalHost = host.isEmpty();
0095     connect(d->mAbstractProcesses, &AbstractProcesses::processesUpdated, this, &Processes::processesUpdated);
0096     connect(d->mAbstractProcesses, &AbstractProcesses::processUpdated, this, &Processes::processUpdated);
0097 }
0098 Processes::~Processes()
0099 {
0100     delete d;
0101 }
0102 
0103 Processes::Error Processes::lastError() const
0104 {
0105     return d->mLastError;
0106 }
0107 Process *Processes::getProcess(long pid) const
0108 {
0109     return d->mProcesses.value(pid);
0110 }
0111 
0112 const QList<Process *> &Processes::getAllProcesses() const
0113 {
0114     return d->mListProcesses;
0115 }
0116 
0117 int Processes::processCount() const
0118 {
0119     return d->mListProcesses.count();
0120 }
0121 
0122 bool Processes::updateProcess(Process *ps, long ppid)
0123 {
0124     Process *parent = d->mProcesses.value(ppid, &d->mFakeProcess);
0125     Q_ASSERT(parent); // even init has a non-null parent - the mFakeProcess
0126 
0127     if (ps->parent() != parent) {
0128         Q_EMIT beginMoveProcess(ps, parent /*new parent*/);
0129         // Processes has been reparented
0130         Process *p = ps;
0131         do {
0132             p = p->parent();
0133             p->numChildren() -= (ps->numChildren() + 1);
0134         } while (p->pid() != -1);
0135         Q_ASSERT(ps != parent);
0136         ps->parent()->children().removeAll(ps);
0137         ps->setParent(parent); // the parent has changed
0138         parent->children().append(ps);
0139         p = ps;
0140         do {
0141             p = p->parent();
0142             p->numChildren() += (ps->numChildren() + 1);
0143         } while (p->pid() != -1);
0144         Q_EMIT endMoveProcess();
0145         Q_ASSERT(ps != parent);
0146         ps->setParent(parent);
0147     }
0148 
0149     ps->setParentPid(ppid);
0150 
0151     bool success = updateProcessInfo(ps);
0152     Q_EMIT processChanged(ps, false);
0153 
0154     return success;
0155 }
0156 
0157 bool Processes::updateProcessInfo(Process *ps)
0158 {
0159     // Now we can actually get the process info
0160     qlonglong oldUserTime = ps->userTime();
0161     qlonglong oldSysTime = ps->sysTime();
0162 
0163     qlonglong oldIoCharactersRead = 0;
0164     qlonglong oldIoCharactersWritten = 0;
0165     qlonglong oldIoReadSyscalls = 0;
0166     qlonglong oldIoWriteSyscalls = 0;
0167     qlonglong oldIoCharactersActuallyRead = 0;
0168     qlonglong oldIoCharactersActuallyWritten = 0;
0169 
0170     if (d->mUpdateFlags.testFlag(Processes::IOStatistics)) {
0171         oldIoCharactersRead = ps->ioCharactersRead();
0172         oldIoCharactersWritten = ps->ioCharactersWritten();
0173         oldIoReadSyscalls = ps->ioReadSyscalls();
0174         oldIoWriteSyscalls = ps->ioWriteSyscalls();
0175         oldIoCharactersActuallyRead = ps->ioCharactersActuallyRead();
0176         oldIoCharactersActuallyWritten = ps->ioCharactersActuallyWritten();
0177     }
0178 
0179     ps->setChanges(Process::Nothing);
0180     bool success;
0181     if (d->mUsingHistoricalData)
0182         success = d->mHistoricProcesses->updateProcessInfo(ps->pid(), ps);
0183     else
0184         success = d->mAbstractProcesses->updateProcessInfo(ps->pid(), ps);
0185 
0186     // Now we have the process info.  Calculate the cpu usage and total cpu usage for itself and all its parents
0187     if (!d->mUsingHistoricalData && d->mElapsedTimeMilliSeconds != 0) { // Update the user usage and sys usage
0188 #ifndef Q_OS_NETBSD
0189         /* The elapsed time is the d->mElapsedTimeMilliSeconds
0190          * (which is of the order 2 seconds or so) plus a small
0191          * correction where we get the amount of time elapsed since
0192          * we start processing. This is because the processing itself
0193          * can take a non-trivial amount of time.  */
0194         int elapsedTime = ps->elapsedTimeMilliSeconds();
0195         ps->setElapsedTimeMilliSeconds(d->mLastUpdated.elapsed());
0196         elapsedTime = ps->elapsedTimeMilliSeconds() - elapsedTime + d->mElapsedTimeMilliSeconds;
0197         if (elapsedTime > 0) {
0198             ps->setUserUsage((int)(((ps->userTime() - oldUserTime) * 1000.0) / elapsedTime));
0199             ps->setSysUsage((int)(((ps->sysTime() - oldSysTime) * 1000.0) / elapsedTime));
0200         }
0201 #endif
0202 
0203         static auto calculateRate = [](qlonglong current, qlonglong previous, int elapsedTime) {
0204             if (elapsedTime <= 0 || previous <= 0) {
0205                 return 0.0;
0206             }
0207             return (current - previous) * 1000.0 / elapsedTime;
0208         };
0209 
0210         if (d->mUpdateFlags.testFlag(Processes::IOStatistics)) {
0211             ps->setIoCharactersReadRate(calculateRate(ps->ioCharactersRead(), oldIoCharactersRead, elapsedTime));
0212             ps->setIoCharactersWrittenRate(calculateRate(ps->ioCharactersWritten(), oldIoCharactersWritten, elapsedTime));
0213             ps->setIoReadSyscallsRate(calculateRate(ps->ioReadSyscalls(), oldIoReadSyscalls, elapsedTime));
0214             ps->setIoWriteSyscallsRate(calculateRate(ps->ioWriteSyscalls(), oldIoWriteSyscalls, elapsedTime));
0215             ps->setIoCharactersActuallyReadRate(calculateRate(ps->ioCharactersActuallyRead(), oldIoCharactersActuallyRead, elapsedTime));
0216             ps->setIoCharactersActuallyWrittenRate(calculateRate(ps->ioCharactersActuallyWritten(), oldIoCharactersActuallyWritten, elapsedTime));
0217         } else {
0218             ps->setIoCharactersReadRate(0);
0219             ps->setIoCharactersWrittenRate(0);
0220             ps->setIoReadSyscallsRate(0);
0221             ps->setIoWriteSyscallsRate(0);
0222             ps->setIoCharactersActuallyReadRate(0);
0223             ps->setIoCharactersActuallyWrittenRate(0);
0224         }
0225     }
0226     if (d->mUsingHistoricalData || d->mElapsedTimeMilliSeconds != 0) {
0227         ps->setTotalUserUsage(ps->userUsage());
0228         ps->setTotalSysUsage(ps->sysUsage());
0229         if (ps->userUsage() != 0 || ps->sysUsage() != 0) {
0230             Process *p = ps->parent();
0231             while (p->pid() != -1) {
0232                 p->totalUserUsage() += ps->userUsage();
0233                 p->totalSysUsage() += ps->sysUsage();
0234                 Q_EMIT processChanged(p, true);
0235                 p = p->parent();
0236             }
0237         }
0238     }
0239 
0240     return success;
0241 }
0242 
0243 bool Processes::addProcess(long pid, long ppid)
0244 {
0245     Process *parent = d->mProcesses.value(ppid);
0246     if (!parent) {
0247         // Under race conditions, the parent could have already quit
0248         // In this case, attach to top leaf
0249         parent = &d->mFakeProcess;
0250         Q_ASSERT(parent); // even init has a non-null parent - the mFakeProcess
0251     }
0252     // it's a new process - we need to set it up
0253     Process *ps = new Process(pid, ppid, parent);
0254 
0255     Q_EMIT beginAddProcess(ps);
0256 
0257     d->mProcesses.insert(pid, ps);
0258 
0259     ps->setIndex(d->mListProcesses.count());
0260     d->mListProcesses.append(ps);
0261 
0262     ps->parent()->children().append(ps);
0263     Process *p = ps;
0264     do {
0265         Q_ASSERT(p);
0266         p = p->parent();
0267         p->numChildren()++;
0268     } while (p->pid() != -1);
0269     ps->setParentPid(ppid);
0270 
0271     // Now we can actually get the process info
0272     bool success = updateProcessInfo(ps);
0273     Q_EMIT endAddProcess();
0274     return success;
0275 }
0276 bool Processes::updateOrAddProcess(long pid)
0277 {
0278     long ppid;
0279     if (d->mUsingHistoricalData)
0280         ppid = d->mHistoricProcesses->getParentPid(pid);
0281     else
0282         ppid = d->mAbstractProcesses->getParentPid(pid);
0283 
0284     if (ppid == pid) // Shouldn't ever happen
0285         ppid = -1;
0286 
0287     if (d->mToBeProcessed.contains(ppid)) {
0288         // Make sure that we update the parent before we update this one.  Just makes things a bit easier.
0289         d->mToBeProcessed.remove(ppid);
0290         updateOrAddProcess(ppid);
0291     }
0292 
0293     Process *ps = d->mProcesses.value(pid);
0294     if (!ps)
0295         return addProcess(pid, ppid);
0296     else
0297         return updateProcess(ps, ppid);
0298 }
0299 
0300 void Processes::updateAllProcesses(long updateDurationMS, Processes::UpdateFlags updateFlags)
0301 {
0302     d->mUpdateFlags = updateFlags;
0303 
0304     if (d->mUsingHistoricalData || d->mLastUpdated.elapsed() >= updateDurationMS || !d->mLastUpdated.isValid()) {
0305         d->mElapsedTimeMilliSeconds = d->mLastUpdated.restart();
0306         if (d->mUsingHistoricalData)
0307             d->mHistoricProcesses->updateAllProcesses(d->mUpdateFlags);
0308         else
0309             d->mAbstractProcesses->updateAllProcesses(d->mUpdateFlags); // For a local machine, this will directly call Processes::processesUpdated()
0310     }
0311 }
0312 
0313 void Processes::processesUpdated()
0314 {
0315     // First really delete any processes that ended last time
0316     long pid;
0317     {
0318         QSetIterator<long> i(d->mEndedProcesses);
0319         while (i.hasNext()) {
0320             pid = i.next();
0321             deleteProcess(pid);
0322         }
0323     }
0324 
0325     if (d->mUsingHistoricalData)
0326         d->mToBeProcessed = d->mHistoricProcesses->getAllPids();
0327     else
0328         d->mToBeProcessed = d->mAbstractProcesses->getAllPids();
0329 
0330     QSet<long> endedProcesses;
0331     for (Process *p : d->mListProcesses) {
0332         if (!d->mToBeProcessed.contains(p->pid())) {
0333             endedProcesses += p->pid();
0334         }
0335     }
0336 
0337     {
0338         QMutableSetIterator<long> i(d->mToBeProcessed);
0339         while (i.hasNext()) {
0340             pid = i.next();
0341             i.remove();
0342             updateOrAddProcess(pid); // This adds the process or changes an existing one
0343             i.toFront(); // we can remove entries from this set elsewhere, so our iterator might be invalid.  Reset it back to the start of the set
0344         }
0345     }
0346     {
0347         QSetIterator<long> i(endedProcesses);
0348         while (i.hasNext()) {
0349             // We saw these pids last time, but not this time.  That means we have to mark them for deletion now
0350             pid = i.next();
0351             d->markProcessesAsEnded(pid);
0352         }
0353         d->mEndedProcesses = endedProcesses;
0354     }
0355 
0356     Q_EMIT updated();
0357 }
0358 
0359 void Processes::processUpdated(long pid, const Process::Updates &changes)
0360 {
0361     auto process = d->mProcesses.value(pid);
0362     if (!process) {
0363         return;
0364     }
0365 
0366     for (auto entry : changes) {
0367         switch (entry.first) {
0368         case Process::VmPSS:
0369             process->setVmPSS(entry.second.toLongLong());
0370             break;
0371         default:
0372             break;
0373         }
0374     }
0375 
0376     Q_EMIT processChanged(process, false);
0377 }
0378 
0379 void Processes::Private::markProcessesAsEnded(long pid)
0380 {
0381     Q_ASSERT(pid >= 0);
0382 
0383     Process *process = mProcesses.value(pid);
0384     if (!process)
0385         return;
0386     process->setStatus(Process::Ended);
0387     Q_EMIT q->processChanged(process, false);
0388 }
0389 void Processes::deleteProcess(long pid)
0390 {
0391     Q_ASSERT(pid >= 0);
0392 
0393     Process *process = d->mProcesses.value(pid);
0394     if (!process)
0395         return;
0396     Q_FOREACH (Process *it, process->children()) {
0397         deleteProcess(it->pid());
0398     }
0399 
0400     Q_EMIT beginRemoveProcess(process);
0401 
0402     d->mProcesses.remove(pid);
0403     d->mListProcesses.removeAll(process);
0404     process->parent()->children().removeAll(process);
0405     Process *p = process;
0406     do {
0407         Q_ASSERT(p);
0408         p = p->parent();
0409         p->numChildren()--;
0410     } while (p->pid() != -1);
0411 #ifndef QT_NO_DEBUG
0412     int i = 0;
0413 #endif
0414     Q_FOREACH (Process *it, d->mListProcesses) {
0415         if (it->index() > process->index())
0416             it->setIndex(it->index() - 1);
0417 #ifndef QT_NO_DEBUG
0418         Q_ASSERT(it->index() == i++);
0419 #endif
0420     }
0421 
0422     delete process;
0423     Q_EMIT endRemoveProcess();
0424 }
0425 
0426 bool Processes::killProcess(long pid)
0427 {
0428     return sendSignal(pid, SIGTERM);
0429 }
0430 
0431 bool Processes::sendSignal(long pid, int sig)
0432 {
0433     auto processes = d->mUsingHistoricalData ? d->mHistoricProcesses : d->mAbstractProcesses;
0434     auto error = processes->sendSignal(pid, sig);
0435     if (error != NoError) {
0436         d->mLastError = error;
0437         return false;
0438     }
0439     return true;
0440 }
0441 
0442 bool Processes::setNiceness(long pid, int priority)
0443 {
0444     auto processes = d->mUsingHistoricalData ? d->mHistoricProcesses : d->mAbstractProcesses;
0445     auto error = processes->setNiceness(pid, priority);
0446     if (error != NoError) {
0447         d->mLastError = error;
0448         return false;
0449     }
0450     return true;
0451 }
0452 
0453 bool Processes::setScheduler(long pid, KSysGuard::Process::Scheduler priorityClass, int priority)
0454 {
0455     auto processes = d->mUsingHistoricalData ? d->mHistoricProcesses : d->mAbstractProcesses;
0456     auto error = processes->setScheduler(pid, priorityClass, priority);
0457     if (error != NoError) {
0458         d->mLastError = error;
0459         return false;
0460     }
0461     return true;
0462 }
0463 
0464 bool Processes::setIoNiceness(long pid, KSysGuard::Process::IoPriorityClass priorityClass, int priority)
0465 {
0466     auto processes = d->mUsingHistoricalData ? d->mHistoricProcesses : d->mAbstractProcesses;
0467     auto error = processes->setIoNiceness(pid, priorityClass, priority);
0468     if (error != NoError) {
0469         d->mLastError = error;
0470         return false;
0471     }
0472     return true;
0473 }
0474 
0475 bool Processes::supportsIoNiceness()
0476 {
0477     if (d->mUsingHistoricalData)
0478         return d->mHistoricProcesses->supportsIoNiceness();
0479     return d->mAbstractProcesses->supportsIoNiceness();
0480 }
0481 
0482 long long Processes::totalPhysicalMemory()
0483 {
0484     return d->mAbstractProcesses->totalPhysicalMemory();
0485 }
0486 
0487 long Processes::numberProcessorCores()
0488 {
0489     return d->mAbstractProcesses->numberProcessorCores();
0490 }
0491 
0492 void Processes::answerReceived(int id, const QList<QByteArray> &answer)
0493 {
0494     KSysGuard::ProcessesRemote *processes = qobject_cast<KSysGuard::ProcessesRemote *>(d->mAbstractProcesses);
0495     if (processes)
0496         processes->answerReceived(id, answer);
0497 }
0498 
0499 QList<QPair<QDateTime, uint>> Processes::historiesAvailable() const
0500 {
0501     if (!d->mIsLocalHost)
0502         return QList<QPair<QDateTime, uint>>();
0503     if (!d->mHistoricProcesses)
0504         d->mHistoricProcesses = new ProcessesATop();
0505 
0506     return d->mHistoricProcesses->historiesAvailable();
0507 }
0508 
0509 void Processes::useCurrentData()
0510 {
0511     if (d->mUsingHistoricalData) {
0512         delete d->mHistoricProcesses;
0513         d->mHistoricProcesses = nullptr;
0514         connect(d->mAbstractProcesses, &AbstractProcesses::processesUpdated, this, &Processes::processesUpdated);
0515         d->mUsingHistoricalData = false;
0516     }
0517 }
0518 
0519 bool Processes::setViewingTime(const QDateTime &when)
0520 {
0521     if (!d->mIsLocalHost) {
0522         d->mLastError = NotSupported;
0523         return false;
0524     }
0525     if (!d->mUsingHistoricalData) {
0526         if (!d->mHistoricProcesses)
0527             d->mHistoricProcesses = new ProcessesATop();
0528         disconnect(d->mAbstractProcesses, &AbstractProcesses::processesUpdated, this, &Processes::processesUpdated);
0529         connect(d->mHistoricProcesses, &AbstractProcesses::processesUpdated, this, &Processes::processesUpdated);
0530         d->mUsingHistoricalData = true;
0531     }
0532     return d->mHistoricProcesses->setViewingTime(when);
0533 }
0534 
0535 bool Processes::loadHistoryFile(const QString &filename)
0536 {
0537     if (!d->mIsLocalHost) {
0538         d->mLastError = NotSupported;
0539         return false;
0540     }
0541     if (!d->mHistoricProcesses)
0542         d->mHistoricProcesses = new ProcessesATop(false);
0543 
0544     return d->mHistoricProcesses->loadHistoryFile(filename);
0545 }
0546 
0547 QString Processes::historyFileName() const
0548 {
0549     if (!d->mIsLocalHost || !d->mHistoricProcesses)
0550         return QString();
0551     return d->mHistoricProcesses->historyFileName();
0552 }
0553 QDateTime Processes::viewingTime() const
0554 {
0555     if (!d->mIsLocalHost || !d->mHistoricProcesses)
0556         return QDateTime();
0557     return d->mHistoricProcesses->viewingTime();
0558 }
0559 
0560 bool Processes::isHistoryAvailable() const
0561 {
0562     if (!d->mIsLocalHost)
0563         return false;
0564     if (!d->mHistoricProcesses)
0565         d->mHistoricProcesses = new ProcessesATop();
0566 
0567     return d->mHistoricProcesses->isHistoryAvailable();
0568 }
0569 
0570 }