diff --git a/processcore/process.cpp b/processcore/process.cpp index 9d3f368..1c7e6cc 100644 --- a/processcore/process.cpp +++ b/processcore/process.cpp @@ -1,768 +1,780 @@ /* This file is part of the KDE project Copyright (C) 2007 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "process.h" #include namespace KSysGuard { class ProcessPrivate { public: long pid; long parent_pid; Process *parent; QString login; qlonglong uid; qlonglong euid; qlonglong suid; qlonglong fsuid; qlonglong gid; qlonglong egid; qlonglong sgid; qlonglong fsgid; qlonglong tracerpid; QByteArray tty; qlonglong userTime; qlonglong sysTime; qlonglong startTime; int userUsage; int sysUsage; int totalUserUsage; int totalSysUsage; unsigned long numChildren; int niceLevel; Process::Scheduler scheduler; Process::IoPriorityClass ioPriorityClass; int ioniceLevel; qlonglong vmSize; qlonglong vmRSS; qlonglong vmURSS; qlonglong vmSizeChange; qlonglong vmRSSChange; qlonglong vmURSSChange; unsigned long pixmapBytes; bool hasManagedGuiWindow; QString name; QString command; Process::ProcessStatus status; qlonglong ioCharactersRead; qlonglong ioCharactersWritten; qlonglong ioReadSyscalls; qlonglong ioWriteSyscalls; qlonglong ioCharactersActuallyRead; qlonglong ioCharactersActuallyWritten; long ioCharactersReadRate; long ioCharactersWrittenRate; long ioReadSyscallsRate; long ioWriteSyscallsRate; long ioCharactersActuallyReadRate; long ioCharactersActuallyWrittenRate; int numThreads; QList children; QTime timeKillWasSent; int index; Process::Changes changes; int elapsedTimeMilliSeconds; + int noNewPrivileges; }; Process::Process() : d(new ProcessPrivate()) { clear(); } Process::Process(qlonglong _pid, qlonglong _ppid, Process *_parent) : d(new ProcessPrivate()) { clear(); d->pid = _pid; d->parent_pid = _ppid; d->parent = _parent; } Process::~Process() { delete d; } QString Process::niceLevelAsString() const { // Just some rough heuristic to map a number to how nice it is if (d->niceLevel == 0) return i18nc("Process Niceness", "Normal"); if (d->niceLevel >= 10) return i18nc("Process Niceness", "Very low priority"); if (d->niceLevel > 0) return i18nc("Process Niceness", "Low priority"); if (d->niceLevel <= -10) return i18nc("Process Niceness", "Very high priority"); if (d->niceLevel < 0) return i18nc("Process Niceness", "High priority"); return QString(); //impossible; } QString Process::ioniceLevelAsString() const { // Just some rough heuristic to map a number to how nice it is if (d->ioniceLevel == 4) return i18nc("Process Niceness", "Normal"); if (d->ioniceLevel >= 6) return i18nc("Process Niceness", "Very low priority"); if (d->ioniceLevel > 4) return i18nc("Process Niceness", "Low priority"); if (d->ioniceLevel <= 2) return i18nc("Process Niceness", "Very high priority"); if (d->ioniceLevel < 4) return i18nc("Process Niceness", "High priority"); return QString(); //impossible; } QString Process::ioPriorityClassAsString() const { switch (d->ioPriorityClass) { case None: return i18nc("Priority Class", "None"); case RealTime: return i18nc("Priority Class", "Real Time"); case BestEffort: return i18nc("Priority Class", "Best Effort"); case Idle: return i18nc("Priority Class", "Idle"); default: return i18nc("Priority Class", "Unknown"); } } QString Process::translatedStatus() const { switch (d->status) { case Running: return i18nc("process status", "running"); case Sleeping: return i18nc("process status", "sleeping"); case DiskSleep: return i18nc("process status", "disk sleep"); case Zombie: return i18nc("process status", "zombie"); case Stopped: return i18nc("process status", "stopped"); case Paging: return i18nc("process status", "paging"); case Ended: return i18nc("process status", "finished"); default: return i18nc("process status", "unknown"); } } QString Process::schedulerAsString() const { switch (d->scheduler) { case Fifo: return i18nc("Scheduler", "FIFO"); case RoundRobin: return i18nc("Scheduler", "Round Robin"); case Interactive: return i18nc("Scheduler", "Interactive"); case Batch: return i18nc("Scheduler", "Batch"); case SchedulerIdle: return i18nc("Scheduler", "Idle"); default: return QString(); } } void Process::clear() { d->pid = -1; d->parent_pid = -1; d->parent = nullptr; d->uid = 0; d->gid = -1; d->suid = d->euid = d->fsuid = -1; d->sgid = d->egid = d->fsgid = -1; d->tracerpid = -1; d->userTime = 0; d->sysTime = 0; d->startTime = 0; d->userUsage=0; d->sysUsage=0; d->totalUserUsage=0; d->totalSysUsage=0; d->numChildren=0; d->niceLevel=0; d->vmSize=0; d->vmRSS = 0; d->vmURSS = 0; d->vmSizeChange = 0; d->vmRSSChange = 0; d->vmURSSChange = 0; d->pixmapBytes = 0; d->hasManagedGuiWindow = false; d->status=OtherStatus; d->ioPriorityClass = None; d->ioniceLevel = -1; d->scheduler = Other; d->ioCharactersRead = 0; d->ioCharactersWritten = 0; d->ioReadSyscalls = 0; d->ioWriteSyscalls = 0; d->ioCharactersActuallyRead = 0; d->ioCharactersActuallyWritten = 0; d->ioCharactersReadRate = 0; d->ioCharactersWrittenRate = 0; d->ioReadSyscallsRate = 0; d->ioWriteSyscallsRate = 0; d->ioCharactersActuallyReadRate = 0; d->ioCharactersActuallyWrittenRate = 0; d->elapsedTimeMilliSeconds = 0; d->numThreads = 0; d->changes = Process::Nothing; } long int Process::pid() const { return d->pid; } long int Process::parentPid() const { return d->parent_pid; } Process* Process::parent() const { return d->parent; } QString Process::login() const { return d->login; } qlonglong Process::uid() const { return d->uid; } qlonglong Process::euid() const { return d->euid; } qlonglong Process::suid() const { return d->suid; } qlonglong Process::fsuid() const { return d->fsuid; } qlonglong Process::gid() const { return d->gid; } qlonglong Process::egid() const { return d->egid; } qlonglong Process::sgid() const { return d->sgid; } qlonglong Process::fsgid() const { return d->fsgid; } qlonglong Process::tracerpid() const { return d->tracerpid; } QByteArray Process::tty() const { return d->tty; } qlonglong Process::userTime() const { return d->userTime; } qlonglong Process::sysTime() const { return d->sysTime; } qlonglong Process::startTime() const { return d->startTime; } +int Process::noNewPrivileges() const +{ + return d->noNewPrivileges; +} + int Process::userUsage() const { return d->userUsage; } int Process::sysUsage() const { return d->sysUsage; } int & Process::totalUserUsage() const { return d->totalUserUsage; } int & Process::totalSysUsage() const { return d->totalSysUsage; } long unsigned & Process::numChildren() const { return d->numChildren; } int Process::niceLevel() const { return d->niceLevel; } Process::Scheduler Process::scheduler() const { return d->scheduler; } Process::IoPriorityClass Process::ioPriorityClass() const { return d->ioPriorityClass; } int Process::ioniceLevel() const { return d->ioniceLevel; } qlonglong Process::vmSize() const { return d->vmSize; } qlonglong Process::vmRSS() const { return d->vmRSS; } qlonglong Process::vmURSS() const { return d->vmURSS; } qlonglong& Process::vmSizeChange() const { return d->vmSizeChange; } qlonglong& Process::vmRSSChange() const { return d->vmRSSChange; } qlonglong& Process::vmURSSChange() const { return d->vmURSSChange; } unsigned long& Process::pixmapBytes() const { return d->pixmapBytes; } bool& Process::hasManagedGuiWindow() const { return d->hasManagedGuiWindow; } QString Process::name() const { return d->name; } QString& Process::command() const { return d->command; } Process::ProcessStatus Process::status() const { return d->status; } qlonglong Process::ioCharactersRead() const { return d->ioCharactersRead; } qlonglong Process::ioCharactersWritten() const { return d->ioCharactersWritten; } qlonglong Process::ioReadSyscalls() const { return d->ioReadSyscalls; } qlonglong Process::ioWriteSyscalls() const { return d->ioWriteSyscalls; } qlonglong Process::ioCharactersActuallyRead() const { return d->ioCharactersActuallyRead; } qlonglong Process::ioCharactersActuallyWritten() const { return d->ioCharactersActuallyWritten; } long int Process::ioCharactersReadRate() const { return d->ioCharactersReadRate; } long int Process::ioCharactersWrittenRate() const { return d->ioCharactersWrittenRate; } long int Process::ioReadSyscallsRate() const { return d->ioReadSyscallsRate; } long int Process::ioWriteSyscallsRate() const { return d->ioWriteSyscallsRate; } long int Process::ioCharactersActuallyReadRate() const { return d->ioCharactersActuallyReadRate; } long int Process::ioCharactersActuallyWrittenRate() const { return d->ioCharactersActuallyWrittenRate; } int Process::numThreads() const { return d->numThreads; } QList< Process* > & Process::children() const { return d->children; } QTime Process::timeKillWasSent() const { return d->timeKillWasSent; } int Process::index() const { return d->index; } Process::Changes Process::changes() const { return d->changes; } int Process::elapsedTimeMilliSeconds() const { return d->elapsedTimeMilliSeconds; } void Process::setParentPid(long int parent_pid) { d->parent_pid = parent_pid; } void Process::setParent(Process* parent) { d->parent = parent; } void Process::setLogin(const QString &login) { if(d->login == login) return; d->login = login; d->changes |= Process::Login; } void Process::setUid(qlonglong uid) { if(d->uid == uid) return; d->uid = uid; d->changes |= Process::Uids; } void Process::setEuid(qlonglong euid) { if(d->euid == euid) return; d->euid = euid; d->changes |= Process::Uids; } void Process::setSuid(qlonglong suid) { if(d->suid == suid) return; d->suid = suid; d->changes |= Process::Uids; } void Process::setFsuid(qlonglong fsuid) { if(d->fsuid == fsuid) return; d->fsuid = fsuid; d->changes |= Process::Uids; } void Process::setGid(qlonglong gid) { if(d->gid == gid) return; d->gid = gid; d->changes |= Process::Gids; } void Process::setEgid(qlonglong egid) { if(d->egid == egid) return; d->egid = egid; d->changes |= Process::Gids; } void Process::setSgid(qlonglong sgid) { if(d->sgid == sgid) return; d->sgid = sgid; d->changes |= Process::Gids; } void Process::setFsgid(qlonglong fsgid) { if(d->fsgid == fsgid) return; d->fsgid = fsgid; d->changes |= Process::Gids; } void Process::setTracerpid(qlonglong tracerpid) { if(d->tracerpid == tracerpid) return; d->tracerpid = tracerpid; d->changes |= Process::Tracerpid; } void Process::setTty(const QByteArray &tty) { if(d->tty == tty) return; d->tty = tty; d->changes |= Process::Tty; } void Process::setUserTime(qlonglong userTime) { d->userTime = userTime; } void Process::setSysTime(qlonglong sysTime) { d->sysTime = sysTime; } void Process::setStartTime(qlonglong startTime) { d->startTime = startTime; } +void Process::setNoNewPrivileges(int number) { + if(d->noNewPrivileges == number) return; + d->noNewPrivileges = number; + d->changes |= Process::Status; +} + void Process::setUserUsage(int _userUsage) { if(d->userUsage == _userUsage) return; d->userUsage = _userUsage; d->changes |= Process::Usage; } void Process::setSysUsage(int _sysUsage) { if(d->sysUsage == _sysUsage) return; d->sysUsage = _sysUsage; d->changes |= Process::Usage; } void Process::setTotalUserUsage(int _totalUserUsage) { if(d->totalUserUsage == _totalUserUsage) return; d->totalUserUsage = _totalUserUsage; d->changes |= Process::TotalUsage; } void Process::setTotalSysUsage(int _totalSysUsage) { if(d->totalSysUsage == _totalSysUsage) return; d->totalSysUsage = _totalSysUsage; d->changes |= Process::TotalUsage; } void Process::setNiceLevel(int _niceLevel) { if(d->niceLevel == _niceLevel) return; d->niceLevel = _niceLevel; d->changes |= Process::NiceLevels; } void Process::setScheduler(Scheduler _scheduler) { if(d->scheduler == _scheduler) return; d->scheduler = _scheduler; d->changes |= Process::NiceLevels; } void Process::setIoPriorityClass(IoPriorityClass _ioPriorityClass) { if(d->ioPriorityClass == _ioPriorityClass) return; d->ioPriorityClass = _ioPriorityClass; d->changes |= Process::NiceLevels; } void Process::setIoniceLevel(int _ioniceLevel) { if(d->ioniceLevel == _ioniceLevel) return; d->ioniceLevel = _ioniceLevel; d->changes |= Process::NiceLevels; } void Process::setVmSize(qlonglong _vmSize) { if(d->vmSizeChange != 0 || d->vmSize != 0) d->vmSizeChange = _vmSize - d->vmSize; if(d->vmSize == _vmSize) return; d->vmSize = _vmSize; d->changes |= Process::VmSize; } void Process::setVmRSS(qlonglong _vmRSS) { if(d->vmRSSChange != 0 || d->vmRSS != 0) d->vmRSSChange = _vmRSS - d->vmRSS; if(d->vmRSS == _vmRSS) return; d->vmRSS = _vmRSS; d->changes |= Process::VmRSS; } void Process::setVmURSS(qlonglong _vmURSS) { if(d->vmURSSChange != 0 || d->vmURSS != 0) d->vmURSSChange = _vmURSS - d->vmURSS; if(d->vmURSS == _vmURSS) return; d->vmURSS = _vmURSS; d->changes |= Process::VmURSS; } void Process::setName(const QString &_name) { if(d->name == _name) return; d->name = _name; d->changes |= Process::Name; } void Process::setCommand(const QString &_command) { if(d->command == _command) return; d->command = _command; d->changes |= Process::Command; } void Process::setStatus(ProcessStatus _status) { if(d->status == _status) return; d->status = _status; d->changes |= Process::Status; } void Process::setIoCharactersRead(qlonglong number) { if(d->ioCharactersRead == number) return; d->ioCharactersRead = number; d->changes |= Process::IO; } void Process::setIoCharactersWritten(qlonglong number) { if(d->ioCharactersWritten == number) return; d->ioCharactersWritten = number; d->changes |= Process::IO; } void Process::setIoReadSyscalls(qlonglong number) { if(d->ioReadSyscalls == number) return; d->ioReadSyscalls = number; d->changes |= Process::IO; } void Process::setIoWriteSyscalls(qlonglong number) { if(d->ioWriteSyscalls == number) return; d->ioWriteSyscalls = number; d->changes |= Process::IO; } void Process::setIoCharactersActuallyRead(qlonglong number) { if(d->ioCharactersActuallyRead == number) return; d->ioCharactersActuallyRead = number; d->changes |= Process::IO; } void Process::setIoCharactersActuallyWritten(qlonglong number) { if(d->ioCharactersActuallyWritten == number) return; d->ioCharactersActuallyWritten = number; d->changes |= Process::IO; } void Process::setIoCharactersReadRate(long number) { if(d->ioCharactersReadRate == number) return; d->ioCharactersReadRate = number; d->changes |= Process::IO; } void Process::setIoCharactersWrittenRate(long number) { if(d->ioCharactersWrittenRate == number) return; d->ioCharactersWrittenRate = number; d->changes |= Process::IO; } void Process::setIoReadSyscallsRate(long number) { if(d->ioReadSyscallsRate == number) return; d->ioReadSyscallsRate = number; d->changes |= Process::IO; } void Process::setIoWriteSyscallsRate(long number) { if(d->ioWriteSyscallsRate == number) return; d->ioWriteSyscallsRate = number; d->changes |= Process::IO; } void Process::setIoCharactersActuallyReadRate(long number) { if(d->ioCharactersActuallyReadRate == number) return; d->ioCharactersActuallyReadRate = number; d->changes |= Process::IO; } void Process::setIoCharactersActuallyWrittenRate(long number) { if(d->ioCharactersActuallyWrittenRate == number) return; d->ioCharactersActuallyWrittenRate = number; d->changes |= Process::IO; } void Process::setNumThreads(int number) { if(d->numThreads == number) return; d->numThreads = number; d->changes |= Process::NumThreads; } void Process::setIndex(int index) { d->index = index; } void Process::setElapsedTimeMilliSeconds(int value) { d->elapsedTimeMilliSeconds = value; } void Process::setChanges(KSysGuard::Process::Change changes) { d->changes = changes; } } diff --git a/processcore/process.h b/processcore/process.h index a4b0ef3..183f1ef 100644 --- a/processcore/process.h +++ b/processcore/process.h @@ -1,260 +1,263 @@ /* This file is part of the KDE project Copyright (C) 2007 John Tapsell Copyright (C) 2015 Gregor Mi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PROCESS_H #define PROCESS_H #include #include #include namespace KSysGuard { class ProcessPrivate; // forward decl d-ptr class Q_DECL_EXPORT Process { public: enum ProcessStatus { Running, Sleeping, DiskSleep, Zombie, Stopped, Paging, Ended, OtherStatus = 99 }; enum IoPriorityClass { None, RealTime, BestEffort, Idle }; enum Scheduler { Other = 0, Fifo, RoundRobin, Batch, SchedulerIdle, Interactive }; ///< Interactive is Solaris only Process(); Process(qlonglong _pid, qlonglong _ppid, Process *_parent); virtual ~Process(); long pid() const; ///< The system's ID for this process. 1 for init. -1 for our virtual 'parent of init' process used just for convenience. long parentPid() const; ///< The system's ID for the parent of this process. Set to -1 if it has no parent (e.g. 'init' on Linux). void setParentPid(long parent_pid); /** A guaranteed NON-NULL pointer for all real processes to the parent process except for the fake process with pid -1. * The Parent's pid is the same value as the parent_pid. The parent process will be also pointed * to by ProcessModel::mPidToProcess to there is no need to worry about mem management in using parent. * For process without a parent (such as 'init' on Linux, parent will point to a (fake) process with pid -1 to simplify things. * For the fake process, this will point to NULL */ Process *parent() const; void setParent(Process *parent); QList & children() const; // REF, make non-ref later! ///< A list of all the direct children that the process has. Children of children are not listed here, so note that children_pids <= numChildren unsigned long& numChildren() const; // REF, make non-ref later! QString login() const; void setLogin(const QString &login); ///< The user login name. Only used for processes on remote machines. Otherwise use uid to get the name qlonglong uid() const; void setUid(qlonglong uid); ///< The user id that the process is running as qlonglong euid() const; void setEuid(qlonglong euid); ///< The effective user id that the process is running as qlonglong suid() const; void setSuid(qlonglong suid); ///< The set user id that the process is running as qlonglong fsuid() const; void setFsuid(qlonglong fsuid); ///< The file system user id that the process is running as. qlonglong gid() const; void setGid(qlonglong gid); ///< The process group id that the process is running as qlonglong egid() const; void setEgid(qlonglong egid); ///< The effective group id that the process is running as qlonglong sgid() const; void setSgid(qlonglong sgid); ///< The set group id that the process is running as qlonglong fsgid() const; void setFsgid(qlonglong fsgid); ///< The file system group id that the process is running as qlonglong tracerpid() const; void setTracerpid(qlonglong tracerpid); ///< If this is being debugged, this is the process that is debugging it, or 0 otherwise QByteArray tty() const; void setTty(const QByteArray &tty); ///< The name of the tty the process owns qlonglong userTime() const; void setUserTime(qlonglong userTime); ///< The time, in 100ths of a second, spent in total on user calls. -1 if not known qlonglong sysTime() const; void setSysTime(qlonglong sysTime); ///< The time, in 100ths of a second, spent in total on system calls. -1 if not known /** * the value is expressed in clock ticks (since Linux 2.6; we only handle this case) since system boot */ qlonglong startTime() const; void setStartTime(qlonglong startTime); /// The time the process started after system boot. Since Linux 2.6, the value is expressed in clock ticks. See man proc. int userUsage() const; void setUserUsage(int userUsage); ///< Percentage (0 to 100). It might be more than 100% on multiple cpu core systems int sysUsage() const; void setSysUsage(int sysUsage); ///< Percentage (0 to 100). It might be more than 100% on multiple cpu core systems int& totalUserUsage() const; // REF, make non-ref later! void setTotalUserUsage(int totalUserUsage); ///< Percentage (0 to 100) from the sum of itself and all its children recursively. If there's no children, it's equal to userUsage. It might be more than 100% on multiple cpu core systems int& totalSysUsage() const; // REF, make non-ref later! void setTotalSysUsage(int totalSysUsage); ///< Percentage (0 to 100) from the sum of itself and all its children recursively. If there's no children, it's equal to sysUsage. It might be more than 100% on multiple cpu core systems int niceLevel() const; void setNiceLevel(int niceLevel); ///< If Scheduler = Other, niceLevel is the niceness (-20 to 20) of this process. A lower number means a higher priority. Otherwise sched priority (1 to 99) Scheduler scheduler() const; void setScheduler(Scheduler scheduler); ///< The scheduler this process is running in. See man sched_getscheduler for more info IoPriorityClass ioPriorityClass() const; void setIoPriorityClass(IoPriorityClass ioPriorityClass); ///< The IO priority class. See man ionice for detailed information. int ioniceLevel() const; void setIoniceLevel(int ioniceLevel); ///< IO Niceness (0 to 7) of this process. A lower number means a higher io priority. -1 if not known or not applicable because ioPriorityClass is Idle or None qlonglong vmSize() const; void setVmSize(qlonglong vmSize); ///< Virtual memory size in KiloBytes, including memory used, mmap'ed files, graphics memory etc, qlonglong vmRSS() const; void setVmRSS(qlonglong vmRSS); ///< Physical memory used by the process and its shared libraries. If the process and libraries are swapped to disk, this could be as low as 0 qlonglong vmURSS() const; void setVmURSS(qlonglong vmURSS); ///< Physical memory used only by the process, and not counting the code for shared libraries. Set to -1 if unknown QString name() const; void setName(const QString &name); ///< The name (e.g. "ksysguard", "konversation", "init") QString& command() const; // REF, make non-ref later! void setCommand(const QString &command); ///< The command the process was launched with ProcessStatus status() const; void setStatus( ProcessStatus status); ///< Whether the process is running/sleeping/etc qlonglong ioCharactersRead() const; void setIoCharactersRead(qlonglong number); ///< The number of bytes which this task has caused to be read from storage qlonglong ioCharactersWritten() const; void setIoCharactersWritten(qlonglong number); ///< The number of bytes which this task has caused, or shall cause to be written to disk. qlonglong ioReadSyscalls() const; void setIoReadSyscalls(qlonglong number); ///< Number of read I/O operations, i.e. syscalls like read() and pread(). qlonglong ioWriteSyscalls() const; void setIoWriteSyscalls(qlonglong number); ///< Number of write I/O operations, i.e. syscalls like write() and pwrite(). qlonglong ioCharactersActuallyRead() const; void setIoCharactersActuallyRead(qlonglong number); ///< Number of bytes which this process really did cause to be fetched from the storage layer. qlonglong ioCharactersActuallyWritten() const; void setIoCharactersActuallyWritten(qlonglong number); ///< Attempt to count the number of bytes which this process caused to be sent to the storage layer. long ioCharactersReadRate() const; void setIoCharactersReadRate(long number); ///< The rate, in bytes per second, which this task has caused to be read from storage long ioCharactersWrittenRate() const; void setIoCharactersWrittenRate(long number); ///< The rate, in bytes per second, which this task has caused, or shall cause to be written to disk. long ioReadSyscallsRate() const; void setIoReadSyscallsRate(long number); ///< Number of read I/O operations per second, i.e. syscalls like read() and pread(). long ioWriteSyscallsRate() const; void setIoWriteSyscallsRate(long number); ///< Number of write I/O operations per second, i.e. syscalls like write() and pwrite(). long ioCharactersActuallyReadRate() const; void setIoCharactersActuallyReadRate(long number); ///< Number of bytes per second which this process really did cause to be fetched from the storage layer. long ioCharactersActuallyWrittenRate() const; void setIoCharactersActuallyWrittenRate(long number); ///< Attempt to count the number of bytes per second which this process caused to be sent to the storage layer. int numThreads() const; ///< Number of threads that this process has, including the main one. 0 if not known void setNumThreads(int number); ///< The number of threads that this process has, including this process. + int noNewPrivileges() const; + void setNoNewPrivileges(int number); ///< Linux process flag NoNewPrivileges + int index() const; ///< Each process has a parent process. Each sibling has a unique number to identify it under that parent. This is that number. void setIndex(int index); qlonglong& vmSizeChange() const; // REF, make non-ref later! ///< The change in vmSize since last update, in KiB qlonglong& vmRSSChange() const; // REF, make non-ref later! ///< The change in vmRSS since last update, in KiB qlonglong& vmURSSChange() const; // REF, make non-ref later! ///< The change in vmURSS since last update, in KiB unsigned long& pixmapBytes() const; // REF, make non-ref later! ///< The number of bytes used for pixmaps/images and not counted by vmRSS or vmURSS bool& hasManagedGuiWindow() const; // REF, make non-ref later! QTime timeKillWasSent() const; ///< This is usually a NULL time. When trying to kill a process, this is the time that the kill signal was sent to the process. QString translatedStatus() const; ///< Returns a translated string of the status. e.g. "Running" etc QString niceLevelAsString() const; ///< Returns a simple translated string of the nice priority. e.g. "Normal", "High", etc QString ioniceLevelAsString() const; ///< Returns a simple translated string of the io nice priority. e.g. "Normal", "High", etc QString ioPriorityClassAsString() const; ///< Returns a translated string of the io nice class. i.e. "None", "Real Time", "Best Effort", "Idle" QString schedulerAsString() const; ///< Returns a translated string of the scheduler class. e.g. "FIFO", "Round Robin", "Batch" /** This is the number of 1/1000ths of a second since this * particular process was last updated compared to when all the processes * were updated. The purpose is to allow a more fine tracking of the time * a process has been running for. * * This is updated in processes.cpp and so shouldn't be touched by the * OS dependant classes. */ int elapsedTimeMilliSeconds() const; void setElapsedTimeMilliSeconds(int value); /** An enum to keep track of what changed since the last update. Note that we * the maximum we can use is 0x4000, so some of the enums represent multiple variables */ enum Change { Nothing = 0x0, Uids = 0x1, Gids = 0x2, Tracerpid = 0x4, Tty = 0x8, Usage = 0x10, TotalUsage = 0x20, NiceLevels = 0x40, VmSize = 0x80, VmRSS = 0x100, VmURSS = 0x200, Name = 0x400, Command = 0x800, Status = 0x1000, Login = 0x2000, IO = 0x4000, NumThreads = 0x8000 }; Q_DECLARE_FLAGS(Changes, Change) Changes changes() const; /**< A QFlags representing what has changed */ void setChanges(Change changes); private: void clear(); private: ProcessPrivate* const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Process::Changes) } #endif diff --git a/processcore/processes_linux_p.cpp b/processcore/processes_linux_p.cpp index 64a99a4..8278493 100644 --- a/processcore/processes_linux_p.cpp +++ b/processcore/processes_linux_p.cpp @@ -1,719 +1,723 @@ /* This file is part of the KDE project Copyright (C) 2007 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "processes_local_p.h" #include "process.h" #include #include #include #include #include #include //for sysconf #include //for kill and setNice #include #include #include #include #include #include //for ionice #include #include //for getsched #include #define PROCESS_BUFFER_SIZE 1000 /* For ionice */ extern int sys_ioprio_set(int, int, int); extern int sys_ioprio_get(int, int); #define HAVE_IONICE /* Check if this system has ionice */ #if !defined(SYS_ioprio_get) || !defined(SYS_ioprio_set) /* All new kernels have SYS_ioprio_get and _set defined, but for the few that do not, here are the definitions */ #if defined(__i386__) #define __NR_ioprio_set 289 #define __NR_ioprio_get 290 #elif defined(__ppc__) || defined(__powerpc__) #define __NR_ioprio_set 273 #define __NR_ioprio_get 274 #elif defined(__x86_64__) #define __NR_ioprio_set 251 #define __NR_ioprio_get 252 #elif defined(__ia64__) #define __NR_ioprio_set 1274 #define __NR_ioprio_get 1275 #else #ifdef __GNUC__ #warning "This architecture does not support IONICE. Disabling ionice feature." #endif #undef HAVE_IONICE #endif /* Map these to SYS_ioprio_get */ #define SYS_ioprio_get __NR_ioprio_get #define SYS_ioprio_set __NR_ioprio_set #endif /* !SYS_ioprio_get */ /* Set up ionice functions */ #ifdef HAVE_IONICE #define IOPRIO_WHO_PROCESS 1 #define IOPRIO_CLASS_SHIFT 13 /* Expose the kernel calls to userspace via syscall * See man ioprio_set and man ioprio_get for information on these functions */ static int ioprio_set(int which, int who, int ioprio) { return syscall(SYS_ioprio_set, which, who, ioprio); } static int ioprio_get(int which, int who) { return syscall(SYS_ioprio_get, which, who); } #endif namespace KSysGuard { class ProcessesLocal::Private { public: Private() { mProcDir = opendir( "/proc" );} ~Private(); inline bool readProcStatus(const QString &dir, Process *process); inline bool readProcStat(const QString &dir, Process *process); inline bool readProcStatm(const QString &dir, Process *process); inline bool readProcCmdline(const QString &dir, Process *process); inline bool getNiceness(long pid, Process *process); inline bool getIOStatistics(const QString &dir, Process *process); QFile mFile; char mBuffer[PROCESS_BUFFER_SIZE+1]; //used as a buffer to read data into DIR* mProcDir; }; ProcessesLocal::Private::~Private() { closedir(mProcDir); } ProcessesLocal::ProcessesLocal() : d(new Private()) { } bool ProcessesLocal::Private::readProcStatus(const QString &dir, Process *process) { mFile.setFileName(dir + QStringLiteral("status")); if(!mFile.open(QIODevice::ReadOnly)) return false; /* process has terminated in the meantime */ process->setUid(0); process->setGid(0); process->setTracerpid(-1); process->setNumThreads(0); + process->setNoNewPrivileges(0); int size; int found = 0; //count how many fields we found while( (size = mFile.readLine( mBuffer, sizeof(mBuffer))) > 0) { //-1 indicates an error switch( mBuffer[0]) { case 'N': if((unsigned int)size > sizeof("Name:") && qstrncmp(mBuffer, "Name:", sizeof("Name:")-1) == 0) { if(process->command().isEmpty()) process->setName(QString::fromLocal8Bit(mBuffer + sizeof("Name:")-1, size-sizeof("Name:")+1).trimmed()); - if(++found == 5) goto finish; + if(++found == 6) goto finish; + } else if((unsigned int)size > sizeof("NoNewPrivs:") && qstrncmp(mBuffer, "NoNewPrivs:", sizeof("NoNewPrivs:")-1) == 0) { + process->setNoNewPrivileges(atol(mBuffer + sizeof("NoNewPrivs:")-1)); + if(++found == 6) goto finish; } break; case 'U': if((unsigned int)size > sizeof("Uid:") && qstrncmp(mBuffer, "Uid:", sizeof("Uid:")-1) == 0) { qlonglong uid; qlonglong euid; qlonglong suid; qlonglong fsuid; sscanf(mBuffer + sizeof("Uid:") -1, "%Ld %Ld %Ld %Ld", &uid, &euid, &suid, &fsuid); process->setUid(uid); process->setEuid(euid); process->setSuid(suid); process->setFsuid(fsuid); - if(++found == 5) goto finish; + if(++found == 6) goto finish; } break; case 'G': if((unsigned int)size > sizeof("Gid:") && qstrncmp(mBuffer, "Gid:", sizeof("Gid:")-1) == 0) { qlonglong gid, egid, sgid, fsgid; sscanf(mBuffer + sizeof("Gid:")-1, "%Ld %Ld %Ld %Ld", &gid, &egid, &sgid, &fsgid); process->setGid(gid); process->setEgid(egid); process->setSgid(sgid); process->setFsgid(fsgid); - if(++found == 5) goto finish; + if(++found == 6) goto finish; } break; case 'T': if((unsigned int)size > sizeof("TracerPid:") && qstrncmp(mBuffer, "TracerPid:", sizeof("TracerPid:")-1) == 0) { process->setTracerpid(atol(mBuffer + sizeof("TracerPid:")-1)); if (process->tracerpid() == 0) process->setTracerpid(-1); - if(++found == 5) goto finish; + if(++found == 6) goto finish; } else if((unsigned int)size > sizeof("Threads:") && qstrncmp(mBuffer, "Threads:", sizeof("Threads:")-1) == 0) { process->setNumThreads(atol(mBuffer + sizeof("Threads:")-1)); - if(++found == 5) goto finish; + if(++found == 6) goto finish; } break; default: break; } } finish: mFile.close(); return true; } long ProcessesLocal::getParentPid(long pid) { if (pid <= 0) return -1; d->mFile.setFileName(QStringLiteral("/proc/") + QString::number(pid) + QStringLiteral("/stat")); if(!d->mFile.open(QIODevice::ReadOnly)) return -1; /* process has terminated in the meantime */ int size; //amount of data read in if( (size = d->mFile.readLine( d->mBuffer, sizeof(d->mBuffer))) <= 0) { //-1 indicates nothing read d->mFile.close(); return -1; } d->mFile.close(); char *word = d->mBuffer; //The command name is the second parameter, and this ends with a closing bracket. So find the last //closing bracket and start from there word = strrchr(word, ')'); if (!word) return -1; word++; //Nove to the space after the last ")" int current_word = 1; while(true) { if(word[0] == ' ' ) { if(++current_word == 3) break; } else if(word[0] == 0) { return -1; //end of data - serious problem } word++; } long ppid = atol(++word); if (ppid == 0) return -1; return ppid; } bool ProcessesLocal::Private::readProcStat(const QString &dir, Process *ps) { QString filename = dir + QStringLiteral("stat"); // As an optimization, if the last file read in was stat, then we already have this info in memory if(mFile.fileName() != filename) { mFile.setFileName(filename); if(!mFile.open(QIODevice::ReadOnly)) return false; /* process has terminated in the meantime */ if( mFile.readLine( mBuffer, sizeof(mBuffer)) <= 0) { //-1 indicates nothing read mFile.close(); return false; } mFile.close(); } char *word = mBuffer; //The command name is the second parameter, and this ends with a closing bracket. So find the last //closing bracket and start from there word = strrchr(word, ')'); if (!word) return false; word++; //Nove to the space after the last ")" int current_word = 1; //We've skipped the process ID and now at the end of the command name char status='\0'; unsigned long long vmSize = 0; unsigned long long vmRSS = 0; while(current_word < 23) { if(word[0] == ' ' ) { ++current_word; switch(current_word) { case 2: //status status=word[1]; // Look at the first letter of the status. // We analyze this after the while loop break; case 6: //ttyNo { int ttyNo = atoi(word+1); int major = ttyNo >> 8; int minor = ttyNo & 0xff; switch(major) { case 136: ps->setTty(QByteArray("pts/") + QByteArray::number(minor)); break; case 5: ps->setTty(QByteArray("tty")); break; case 4: if(minor < 64) ps->setTty(QByteArray("tty") + QByteArray::number(minor)); else ps->setTty(QByteArray("ttyS") + QByteArray::number(minor-64)); break; default: ps->setTty(QByteArray()); } } break; case 13: //userTime ps->setUserTime(atoll(word+1)); break; case 14: //sysTime ps->setSysTime(atoll(word+1)); break; case 18: //niceLevel ps->setNiceLevel(atoi(word+1)); /*Or should we use getPriority instead? */ break; case 21: // startTime ps->setStartTime(atoll(word+1)); break; case 22: //vmSize vmSize = atoll(word+1); break; case 23: //vmRSS vmRSS = atoll(word+1); break; default: break; } } else if(word[0] == 0) { return false; //end of data - serious problem } word++; } /* There was a "(ps->vmRss+3) * sysconf(_SC_PAGESIZE)" here in the original ksysguard code. I have no idea why! After comparing it to * meminfo and other tools, this means we report the RSS by 12 bytes differently compared to them. So I'm removing the +3 * to be consistent. NEXT TIME COMMENT STRANGE THINGS LIKE THAT! :-) * * Update: I think I now know why - the kernel allocates 3 pages for * tracking information about each the process. This memory isn't * included in vmRSS..*/ ps->setVmRSS(vmRSS * (sysconf(_SC_PAGESIZE) / 1024)); /*convert to KiB*/ ps->setVmSize(vmSize / 1024); /* convert to KiB */ switch( status) { case 'R': ps->setStatus(Process::Running); break; case 'S': ps->setStatus(Process::Sleeping); break; case 'D': ps->setStatus(Process::DiskSleep); break; case 'Z': ps->setStatus(Process::Zombie); break; case 'T': ps->setStatus(Process::Stopped); break; case 'W': ps->setStatus(Process::Paging); break; default: ps->setStatus(Process::OtherStatus); break; } return true; } bool ProcessesLocal::Private::readProcStatm(const QString &dir, Process *process) { #ifdef _SC_PAGESIZE mFile.setFileName(dir + QStringLiteral("statm")); if(!mFile.open(QIODevice::ReadOnly)) return false; /* process has terminated in the meantime */ if( mFile.readLine( mBuffer, sizeof(mBuffer)) <= 0) { //-1 indicates nothing read mFile.close(); return 0; } mFile.close(); int current_word = 0; char *word = mBuffer; while(true) { if(word[0] == ' ' ) { if(++current_word == 2) //number of pages that are shared break; } else if(word[0] == 0) { return false; //end of data - serious problem } word++; } long shared = atol(word+1); /* we use the rss - shared to find the amount of memory just this app uses */ process->setVmURSS(process->vmRSS() - (shared * sysconf(_SC_PAGESIZE) / 1024)); #else process->setVmURSS(0); #endif return true; } bool ProcessesLocal::Private::readProcCmdline(const QString &dir, Process *process) { if(!process->command().isNull()) return true; //only parse the cmdline once. This function takes up 25% of the CPU time :-/ mFile.setFileName(dir + QStringLiteral("cmdline")); if(!mFile.open(QIODevice::ReadOnly)) return false; /* process has terminated in the meantime */ QTextStream in(&mFile); process->setCommand(in.readAll()); //cmdline separates parameters with the NULL character if(!process->command().isEmpty()) { //extract non-truncated name from cmdline int zeroIndex = process->command().indexOf(QLatin1Char('\0')); int processNameStart = process->command().lastIndexOf(QLatin1Char('/'), zeroIndex); if(processNameStart == -1) processNameStart = 0; else processNameStart++; QString nameFromCmdLine = process->command().mid(processNameStart, zeroIndex - processNameStart); if(nameFromCmdLine.startsWith(process->name())) process->setName(nameFromCmdLine); process->command().replace(QLatin1Char('\0'), QLatin1Char(' ')); } mFile.close(); return true; } bool ProcessesLocal::Private::getNiceness(long pid, Process *process) { int sched = sched_getscheduler(pid); switch(sched) { case (SCHED_OTHER): process->setScheduler(KSysGuard::Process::Other); break; case (SCHED_RR): process->setScheduler(KSysGuard::Process::RoundRobin); break; case (SCHED_FIFO): process->setScheduler(KSysGuard::Process::Fifo); break; #ifdef SCHED_IDLE case (SCHED_IDLE): process->setScheduler(KSysGuard::Process::SchedulerIdle); break; #endif #ifdef SCHED_BATCH case (SCHED_BATCH): process->setScheduler(KSysGuard::Process::Batch); break; #endif default: process->setScheduler(KSysGuard::Process::Other); } if(sched == SCHED_FIFO || sched == SCHED_RR) { struct sched_param param; if(sched_getparam(pid, ¶m) == 0) process->setNiceLevel(param.sched_priority); else process->setNiceLevel(0); //Error getting scheduler parameters. } #ifdef HAVE_IONICE int ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid); /* Returns from 0 to 7 for the iopriority, and -1 if there's an error */ if(ioprio == -1) { process->setIoniceLevel(-1); process->setIoPriorityClass(KSysGuard::Process::None); return false; /* Error. Just give up. */ } process->setIoniceLevel(ioprio & 0xff); /* Bottom few bits are the priority */ process->setIoPriorityClass((KSysGuard::Process::IoPriorityClass)(ioprio >> IOPRIO_CLASS_SHIFT)); /* Top few bits are the class */ return true; #else return false; /* Do nothing, if we do not support this architecture */ #endif } bool ProcessesLocal::Private::getIOStatistics(const QString &dir, Process *process) { QString filename = dir + QStringLiteral("io"); // As an optimization, if the last file read in was io, then we already have this info in memory mFile.setFileName(filename); if(!mFile.open(QIODevice::ReadOnly)) return false; /* process has terminated in the meantime */ if( mFile.read( mBuffer, sizeof(mBuffer)) <= 0) { //-1 indicates nothing read mFile.close(); return false; } mFile.close(); int current_word = 0; //count from 0 char *word = mBuffer; while(current_word < 6 && word[0] != 0) { if(word[0] == ' ' ) { qlonglong number = atoll(word+1); switch(current_word++) { case 0: //rchar - characters read process->setIoCharactersRead(number); break; case 1: //wchar - characters written process->setIoCharactersWritten(number); break; case 2: //syscr - read syscall process->setIoReadSyscalls(number); break; case 3: //syscw - write syscall process->setIoWriteSyscalls(number); break; case 4: //read_bytes - bytes actually read from I/O process->setIoCharactersActuallyRead(number); break; case 5: //write_bytes - bytes actually written to I/O process->setIoCharactersActuallyWritten(number); default: break; } } word++; } return true; } bool ProcessesLocal::updateProcessInfo( long pid, Process *process) { bool success = true; QString dir = QStringLiteral("/proc/") + QString::number(pid) + QLatin1Char('/'); if(!d->readProcStat(dir, process)) success = false; if(!d->readProcStatus(dir, process)) success = false; if(!d->readProcStatm(dir, process)) success = false; if(!d->readProcCmdline(dir, process)) success = false; if(!d->getNiceness(pid, process)) success = false; if(mUpdateFlags.testFlag(Processes::IOStatistics) && !d->getIOStatistics(dir, process)) success = false; return success; } QSet ProcessesLocal::getAllPids( ) { QSet pids; if(d->mProcDir==nullptr) return pids; //There's not much we can do without /proc struct dirent* entry; rewinddir(d->mProcDir); while ( ( entry = readdir( d->mProcDir ) ) ) if ( entry->d_name[ 0 ] >= '0' && entry->d_name[ 0 ] <= '9' ) pids.insert(atol( entry->d_name )); return pids; } bool ProcessesLocal::sendSignal(long pid, int sig) { errno = 0; if (pid <= 0) { errorCode = Processes::InvalidPid; return false; } if (kill( (pid_t)pid, sig )) { switch (errno) { case ESRCH: errorCode = Processes::ProcessDoesNotExistOrZombie; break; case EINVAL: errorCode = Processes::InvalidParameter; break; case EPERM: errorCode = Processes::InsufficientPermissions; break; default: break; } //Kill failed return false; } return true; } bool ProcessesLocal::setNiceness(long pid, int priority) { errno = 0; if (pid <= 0) { errorCode = Processes::InvalidPid; return false; } if (setpriority( PRIO_PROCESS, pid, priority )) { switch (errno) { case ESRCH: errorCode = Processes::ProcessDoesNotExistOrZombie; break; case EINVAL: errorCode = Processes::InvalidParameter; break; case EACCES: case EPERM: errorCode = Processes::InsufficientPermissions; break; default: break; } //set niceness failed return false; } return true; } bool ProcessesLocal::setScheduler(long pid, int priorityClass, int priority) { errno = 0; if(priorityClass == KSysGuard::Process::Other || priorityClass == KSysGuard::Process::Batch || priorityClass == KSysGuard::Process::SchedulerIdle) priority = 0; if (pid <= 0) { errorCode = Processes::InvalidPid; return false; } struct sched_param params; params.sched_priority = priority; int policy; switch(priorityClass) { case (KSysGuard::Process::Other): policy = SCHED_OTHER; break; case (KSysGuard::Process::RoundRobin): policy = SCHED_RR; break; case (KSysGuard::Process::Fifo): policy = SCHED_FIFO; break; #ifdef SCHED_IDLE case (KSysGuard::Process::SchedulerIdle): policy = SCHED_IDLE; break; #endif #ifdef SCHED_BATCH case (KSysGuard::Process::Batch): policy = SCHED_BATCH; break; #endif default: errorCode = Processes::NotSupported; return false; } if (sched_setscheduler( pid, policy, ¶ms) != 0) { switch (errno) { case ESRCH: errorCode = Processes::ProcessDoesNotExistOrZombie; break; case EINVAL: errorCode = Processes::InvalidParameter; break; case EPERM: errorCode = Processes::InsufficientPermissions; break; default: break; } return false; } return true; } bool ProcessesLocal::setIoNiceness(long pid, int priorityClass, int priority) { errno = 0; if (pid <= 0) { errorCode = Processes::InvalidPid; return false; } #ifdef HAVE_IONICE if (ioprio_set(IOPRIO_WHO_PROCESS, pid, priority | priorityClass << IOPRIO_CLASS_SHIFT) == -1) { //set io niceness failed switch (errno) { case ESRCH: errorCode = Processes::ProcessDoesNotExistOrZombie; break; case EINVAL: errorCode = Processes::InvalidParameter; break; case EPERM: errorCode = Processes::InsufficientPermissions; break; default: break; } return false; } return true; #else errorCode = Processes::NotSupported; return false; #endif } bool ProcessesLocal::supportsIoNiceness() { #ifdef HAVE_IONICE return true; #else return false; #endif } long long ProcessesLocal::totalPhysicalMemory() { //Try to get the memory via sysconf. Note the cast to long long to try to avoid a long overflow //Should we use sysconf(_SC_PAGESIZE) or getpagesize() ? #ifdef _SC_PHYS_PAGES return ((long long)sysconf(_SC_PHYS_PAGES)) * (sysconf(_SC_PAGESIZE)/1024); #else //This is backup code in case this is not defined. It should never fail on a linux system. d->mFile.setFileName("/proc/meminfo"); if(!d->mFile.open(QIODevice::ReadOnly)) return 0; int size; while( (size = d->mFile.readLine( d->mBuffer, sizeof(d->mBuffer))) > 0) { //-1 indicates an error switch( d->mBuffer[0]) { case 'M': if((unsigned int)size > sizeof("MemTotal:") && qstrncmp(d->mBuffer, "MemTotal:", sizeof("MemTotal:")-1) == 0) { d->mFile.close(); return atoll(d->mBuffer + sizeof("MemTotal:")-1); } } } return 0; // Not found. Probably will never happen #endif } ProcessesLocal::~ProcessesLocal() { delete d; } } diff --git a/processcore/processes_remote_p.cpp b/processcore/processes_remote_p.cpp index a49e8dd..7e38553 100644 --- a/processcore/processes_remote_p.cpp +++ b/processcore/processes_remote_p.cpp @@ -1,273 +1,277 @@ /* This file is part of the KDE project Copyright (C) 2007 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "processes_remote_p.h" #include "process.h" #include "processcore_debug.h" #include #include #include #include namespace KSysGuard { class ProcessesRemote::Private { public: Private() {havePsInfo = false; pidColumn = 1; ppidColumn = nameColumn = uidColumn = gidColumn = statusColumn = userColumn = systemColumn = niceColumn = vmSizeColumn = vmRSSColumn = loginColumn = commandColumn = tracerPidColumn = ttyColumn = ioprioClassColumn = ioprioColumn = - vmURSSColumn = -1; + vmURSSColumn = noNewPrivilegesColumn = -1; usedMemory = freeMemory;} ~Private() {} QString host; QList lastAnswer; QSet pids; QHash > processByPid; bool havePsInfo; int pidColumn; int ppidColumn; int tracerPidColumn; int nameColumn; int uidColumn; int gidColumn; int statusColumn; int userColumn; int systemColumn; int niceColumn; int vmSizeColumn; int vmRSSColumn; int vmURSSColumn; int loginColumn; int commandColumn; int ioprioClassColumn; int ioprioColumn; int ttyColumn; + int noNewPrivilegesColumn; int numColumns; long freeMemory; long usedMemory; Processes::UpdateFlags updateFlags; }; ProcessesRemote::ProcessesRemote(const QString &hostname) : d(new Private()) { d->host = hostname; QTimer::singleShot(0, this, &ProcessesRemote::setup); } void ProcessesRemote::setup() { emit runCommand(QStringLiteral("mem/physical/used"), (int)UsedMemory); emit runCommand(QStringLiteral("mem/physical/free"), (int)FreeMemory); emit runCommand(QStringLiteral("ps?"), (int)PsInfo); emit runCommand(QStringLiteral("ps"), (int)Ps); } long ProcessesRemote::getParentPid(long pid) { if(!d->processByPid.contains(pid)) { qCDebug(LIBKSYSGUARD_PROCESSCORE) << "Parent pid requested for pid that we do not have info on " << pid; return 0; } if(d->ppidColumn == -1) { qCDebug(LIBKSYSGUARD_PROCESSCORE) << "ppid column not known "; return 0; } return d->processByPid[pid].at(d->ppidColumn).toLong(); } bool ProcessesRemote::updateProcessInfo( long pid, Process *process) { Q_CHECK_PTR(process); if(!d->processByPid.contains(pid)) { qCDebug(LIBKSYSGUARD_PROCESSCORE) << "update request for pid that we do not have info on " << pid; return false; } QList p = d->processByPid[pid]; if(d->nameColumn!= -1) process->setName(QString::fromUtf8(p.at(d->nameColumn))); if(d->uidColumn!= -1) process->setUid(p.at(d->uidColumn).toLong()); if(d->gidColumn!= -1) process->setGid(p.at(d->gidColumn).toLong()); if(d->statusColumn!= -1) { switch( p.at(d->statusColumn)[0] ) { case 's': process->setStatus(Process::Sleeping); break; case 'r': process->setStatus(Process::Running); break; } } if(d->userColumn!= -1) process->setUserTime(p.at(d->userColumn).toLong()); if(d->systemColumn!= -1) process->setSysTime(p.at(d->systemColumn).toLong()); if(d->niceColumn!= -1) process->setNiceLevel(p.at(d->niceColumn).toLong()); if(d->vmSizeColumn!= -1) process->setVmSize(p.at(d->vmSizeColumn).toLong()); if(d->vmRSSColumn!= -1) process->setVmRSS(p.at(d->vmRSSColumn).toLong()); if(d->vmURSSColumn!= -1) process->setVmURSS(p.at(d->vmURSSColumn).toLong()); if(d->loginColumn!= -1) process->setLogin(QString::fromUtf8(p.at(d->loginColumn).data())); if(d->commandColumn!= -1) process->setCommand(QString::fromUtf8(p.at(d->commandColumn).data())); if(d->tracerPidColumn!= -1) process->setTracerpid(p.at(d->tracerPidColumn).toLong()); if(d->vmURSSColumn!= -1) process->setVmURSS(p.at(d->vmURSSColumn).toLong()); if(d->ttyColumn!= -1) process->setTty(p.at(d->ttyColumn)); if(d->ioprioColumn!= -1) process->setIoniceLevel(p.at(d->ioprioColumn).toInt()); if(d->ioprioClassColumn!= -1) process->setIoPriorityClass((KSysGuard::Process::IoPriorityClass)(p.at(d->ioprioClassColumn).toInt())); + if(d->noNewPrivilegesColumn!= -1) process->setNoNewPrivileges(p.at(d->noNewPrivilegesColumn).toLong()); return true; } void ProcessesRemote::updateAllProcesses( Processes::UpdateFlags updateFlags ) { d->updateFlags = updateFlags; if(!d->havePsInfo) emit runCommand(QStringLiteral("ps?"), (int)PsInfo); emit runCommand(QStringLiteral("ps"), (int)Ps); } QSet ProcessesRemote::getAllPids( ) { d->pids.clear(); d->processByPid.clear(); Q_FOREACH(const QByteArray &process, d->lastAnswer) { QList info = process.split('\t'); if(info.size() == d->numColumns) { int pid = info.at(d->pidColumn).toLong(); Q_ASSERT(! d->pids.contains(pid)); d->pids << pid; d->processByPid[pid] = info; } } return d->pids; } bool ProcessesRemote::sendSignal(long pid, int sig) { //TODO run the proper command for all these functions below emit runCommand(QStringLiteral("kill ") + QString::number(pid) + QStringLiteral(" ") + QString::number(sig), (int)Kill); return true; } bool ProcessesRemote::setNiceness(long pid, int priority) { emit runCommand(QStringLiteral("setpriority ") + QString::number(pid) + QStringLiteral(" ") + QString::number(priority), (int)Renice); return true; } bool ProcessesRemote::setIoNiceness(long pid, int priorityClass, int priority) { emit runCommand(QStringLiteral("ionice ") + QString::number(pid) + QStringLiteral(" ") + QString::number(priorityClass) + QStringLiteral(" ") + QString::number(priority), (int)Ionice); return true; } bool ProcessesRemote::setScheduler(long pid, int priorityClass, int priority) { Q_UNUSED(pid); Q_UNUSED(priorityClass); Q_UNUSED(priority); errorCode = Processes::NotSupported; return false; } bool ProcessesRemote::supportsIoNiceness() { return true; } long long ProcessesRemote::totalPhysicalMemory() { return d->usedMemory + d->freeMemory; } long ProcessesRemote::numberProcessorCores() { return 0; } void ProcessesRemote::answerReceived( int id, const QList& answer ) { switch (id) { case PsInfo: { if(answer.isEmpty()) return; //Invalid data QList info = answer.at(0).split('\t'); d->numColumns = info.size(); for(int i =0; i < d->numColumns; i++) { if(info[i] == "Name") d->nameColumn = i; else if(info[i] == "PID") d->pidColumn = i; else if(info[i] == "PPID") d->ppidColumn = i; else if(info[i] == "UID") d->uidColumn = i; else if(info[i] == "GID") d->gidColumn = i; else if(info[i] == "TracerPID") d->tracerPidColumn = i; else if(info[i] == "Status") d->statusColumn = i; else if(info[i] == "User Time") d->userColumn = i; else if(info[i] == "System Time") d->systemColumn = i; else if(info[i] == "Nice") d->niceColumn = i; else if(info[i] == "VmSize") d->vmSizeColumn = i; else if(info[i] == "VmRss") d->vmRSSColumn = i; else if(info[i] == "VmURss") d->vmURSSColumn = i; else if(info[i] == "Login") d->loginColumn = i; else if(info[i] == "TTY") d->ttyColumn = i; else if(info[i] == "Command") d->commandColumn = i; else if(info[i] == "IO Priority Class") d->ioprioClassColumn = i; else if(info[i] == "IO Priority") d->ioprioColumn = i; + else if(info[i] == "NNP") + d->noNewPrivilegesColumn = i; } d->havePsInfo = true; break; } case Ps: d->lastAnswer = answer; if(!d->havePsInfo) return; //Not setup yet. Should never happen emit processesUpdated(); case FreeMemory: if(answer.isEmpty()) return; //Invalid data d->freeMemory = answer[0].toLong(); break; case UsedMemory: if(answer.isEmpty()) return; //Invalid data d->usedMemory = answer[0].toLong(); break; } } ProcessesRemote::~ProcessesRemote() { delete d; } } diff --git a/processui/ProcessModel.cpp b/processui/ProcessModel.cpp index a150f51..8a903aa 100644 --- a/processui/ProcessModel.cpp +++ b/processui/ProcessModel.cpp @@ -1,2227 +1,2245 @@ /* KSysGuard, the KDE System Guard Copyright (c) 1999, 2000 Chris Schlaeger Copyright (c) 2006-2007 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ProcessModel.h" #include "ProcessModel_p.h" #include "timeutil.h" #include "processcore/processes.h" #include "processcore/process.h" #include "processui_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HEADING_X_ICON_SIZE 16 #define MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS 2000 #define GET_OWN_ID #ifdef GET_OWN_ID /* For getuid*/ #include #include #endif #ifdef HAVE_XRES #include #endif extern QApplication* Qapp; static QString formatByteSize(qlonglong amountInKB, int units) { enum { UnitsAuto, UnitsKB, UnitsMB, UnitsGB, UnitsTB, UnitsPB }; static QString kString = i18n("%1 K", QString::fromLatin1("%1")); static QString mString = i18n("%1 M", QString::fromLatin1("%1")); static QString gString = i18n("%1 G", QString::fromLatin1("%1")); static QString tString = i18n("%1 T", QString::fromLatin1("%1")); static QString pString = i18n("%1 P", QString::fromLatin1("%1")); double amount; if (units == UnitsAuto) { if (amountInKB < 1024.0*0.9) units = UnitsKB; // amount < 0.9 MiB == KiB else if (amountInKB < 1024.0*1024.0*0.9) units = UnitsMB; // amount < 0.9 GiB == MiB else if (amountInKB < 1024.0*1024.0*1024.0*0.9) units = UnitsGB; // amount < 0.9 TiB == GiB else if (amountInKB < 1024.0*1024.0*1024.0*1024.0*0.9) units = UnitsTB; // amount < 0.9 PiB == TiB else units = UnitsPB; } switch(units) { case UnitsKB: return kString.arg(QLocale().toString(amountInKB)); case UnitsMB: amount = amountInKB/1024.0; return mString.arg(QLocale().toString(amount, 'f', 1)); case UnitsGB: amount = amountInKB/(1024.0*1024.0); if(amount < 0.1 && amount > 0.05) amount = 0.1; return gString.arg(QLocale().toString(amount, 'f', 1)); case UnitsTB: amount = amountInKB/(1024.0*1024.0*1024.0); if(amount < 0.1 && amount > 0.05) amount = 0.1; return tString.arg(QLocale().toString(amount, 'f', 1)); case UnitsPB: amount = amountInKB/(1024.0*1024.0*1024.0*1024.0); if(amount < 0.1 && amount > 0.05) amount = 0.1; return pString.arg(QLocale().toString(amount, 'f', 1)); default: return QLatin1String(""); // error } } ProcessModelPrivate::ProcessModelPrivate() : mBlankPixmap(HEADING_X_ICON_SIZE,1) { mBlankPixmap.fill(QColor(0,0,0,0)); mSimple = true; mIsLocalhost = true; mMemTotal = -1; mNumProcessorCores = 1; mProcesses = nullptr; mShowChildTotals = true; mShowCommandLineOptions = false; mShowingTooltips = true; mNormalizeCPUUsage = true; mIoInformation = ProcessModel::ActualBytes; #ifdef HAVE_XRES mHaveXRes = false; #endif mHaveTimer = false, mTimerId = -1, mMovingRow = false; mRemovingRow = false; mInsertingRow = false; #if HAVE_X11 mIsX11 = QX11Info::isPlatformX11(); #else mIsX11 = false; #endif } ProcessModelPrivate::~ProcessModelPrivate() { #if HAVE_X11 qDeleteAll(mPidToWindowInfo); #endif delete mProcesses; mProcesses = nullptr; } ProcessModel::ProcessModel(QObject* parent, const QString &host) : QAbstractItemModel(parent), d(new ProcessModelPrivate) { d->q=this; #ifdef HAVE_XRES if (d->mIsX11) { int event, error, major, minor; d->mHaveXRes = XResQueryExtension(QX11Info::display(), &event, &error) && XResQueryVersion(QX11Info::display(), &major, &minor); } #endif if(host.isEmpty() || host == QLatin1String("localhost")) { d->mHostName = QString(); d->mIsLocalhost = true; } else { d->mHostName = host; d->mIsLocalhost = false; } setupHeader(); d->setupProcesses(); #if HAVE_X11 d->setupWindows(); #endif d->mUnits = UnitsKB; d->mIoUnits = UnitsKB; } bool ProcessModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { //Because we need to sort Descendingly by default for most of the headings, we often return left > right KSysGuard::Process *processLeft = reinterpret_cast< KSysGuard::Process * > (left.internalPointer()); KSysGuard::Process *processRight = reinterpret_cast< KSysGuard::Process * > (right.internalPointer()); Q_ASSERT(processLeft); Q_ASSERT(processRight); Q_ASSERT(left.column() == right.column()); switch(left.column()) { case HeadingUser: { /* Sorting by user will be the default and the most common. We want to sort in the most useful way that we can. We need to return a number though. This code is based on that sorting ascendingly should put the current user at the top First the user we are running as should be at the top. Then any other users in the system. Then at the bottom the 'system' processes. We then sort by cpu usage to sort by that, then finally sort by memory usage */ /* First, place traced processes at the very top, ignoring any other sorting criteria */ if(processLeft->tracerpid() >= 0) return true; if(processRight->tracerpid() >= 0) return false; /* Sort by username. First group into own user, normal users, system users */ if(processLeft->uid() != processRight->uid()) { //We primarily sort by username if(d->mIsLocalhost) { int ownUid = getuid(); if(processLeft->uid() == ownUid) return true; //Left is our user, right is not. So left is above right if(processRight->uid() == ownUid) return false; //Left is not our user, right is. So right is above left } bool isLeftSystemUser = processLeft->uid() < 100 || !canUserLogin(processLeft->uid()); bool isRightSystemUser = processRight->uid() < 100 || !canUserLogin(processRight->uid()); if(isLeftSystemUser && !isRightSystemUser) return false; //System users are less than non-system users if(!isLeftSystemUser && isRightSystemUser) return true; //They are either both system users, or both non-system users. //So now sort by username return d->getUsernameForUser(processLeft->uid(), false) < d->getUsernameForUser(processRight->uid(), false); } /* 2nd sort order - Graphics Windows */ //Both columns have the same user. Place processes with windows at the top if(processLeft->hasManagedGuiWindow() && !processRight->hasManagedGuiWindow()) return true; if(!processLeft->hasManagedGuiWindow() && processRight->hasManagedGuiWindow()) return false; /* 3rd sort order - CPU Usage */ int leftCpu, rightCpu; if(d->mSimple || !d->mShowChildTotals) { leftCpu = processLeft->userUsage() + processLeft->sysUsage(); rightCpu = processRight->userUsage() + processRight->sysUsage(); } else { leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage(); rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage(); } if(leftCpu != rightCpu) return leftCpu > rightCpu; /* 4th sort order - Memory Usage */ qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS(); qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS(); return memoryLeft > memoryRight; } case HeadingCPUUsage: { int leftCpu, rightCpu; if(d->mSimple || !d->mShowChildTotals) { leftCpu = processLeft->userUsage() + processLeft->sysUsage(); rightCpu = processRight->userUsage() + processRight->sysUsage(); } else { leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage(); rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage(); } return leftCpu > rightCpu; } case HeadingCPUTime: { return (processLeft->userTime() + processLeft->sysTime()) > (processRight->userTime() + processRight->sysTime()); } case HeadingMemory: { qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS(); qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS(); return memoryLeft > memoryRight; } case HeadingStartTime: { return processLeft->startTime() > processRight->startTime(); } + case HeadingNoNewPrivileges: + return processLeft->noNewPrivileges() > processRight->noNewPrivileges(); case HeadingXMemory: return processLeft->pixmapBytes() > processRight->pixmapBytes(); case HeadingVmSize: return processLeft->vmSize() > processRight->vmSize(); case HeadingSharedMemory: { qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmRSS() - processLeft->vmURSS() : 0; qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmRSS() - processRight->vmURSS() : 0; return memoryLeft > memoryRight; } case HeadingPid: return processLeft->pid() > processRight->pid(); case HeadingNiceness: //Sort by scheduler first if(processLeft->scheduler() != processRight->scheduler()) { if(processLeft->scheduler() == KSysGuard::Process::RoundRobin || processLeft->scheduler() == KSysGuard::Process::Fifo) return true; if(processRight->scheduler() == KSysGuard::Process::RoundRobin || processRight->scheduler() == KSysGuard::Process::Fifo) return false; if(processLeft->scheduler() == KSysGuard::Process::Other) return true; if(processRight->scheduler() == KSysGuard::Process::Other) return false; if(processLeft->scheduler() == KSysGuard::Process::Batch) return true; } if(processLeft->niceLevel() == processRight->niceLevel()) return processLeft->pid() < processRight->pid(); //Subsort by pid if the niceLevel is the same return processLeft->niceLevel() < processRight->niceLevel(); case HeadingTty: { if(processLeft->tty() == processRight->tty()) return processLeft->pid() < processRight->pid(); //Both ttys are the same. Sort by pid if(processLeft->tty().isEmpty()) return false; //Only left is empty (since they aren't the same) else if(processRight->tty().isEmpty()) return true; //Only right is empty //Neither left or right is empty. The tty string is like "tty10" so split this into "tty" and "10" //and sort by the string first, then sort by the number QRegExp regexpLeft(QStringLiteral("^(\\D*)(\\d*)$")); QRegExp regexpRight(regexpLeft); if(regexpLeft.indexIn(QString::fromUtf8(processLeft->tty())) == -1 || regexpRight.indexIn(QString::fromUtf8(processRight->tty())) == -1) return processLeft->tty() < processRight->tty(); int nameMatch = regexpLeft.cap(1).compare(regexpRight.cap(1)); if(nameMatch < 0) return true; if(nameMatch > 0) return false; return regexpLeft.cap(2).toInt() < regexpRight.cap(2).toInt(); } case HeadingIoRead: switch(d->mIoInformation) { case ProcessModel::Bytes: return processLeft->ioCharactersRead() > processRight->ioCharactersRead(); case ProcessModel::Syscalls: return processLeft->ioReadSyscalls() > processRight->ioReadSyscalls(); case ProcessModel::ActualBytes: return processLeft->ioCharactersActuallyRead() > processRight->ioCharactersActuallyRead(); case ProcessModel::BytesRate: return processLeft->ioCharactersReadRate() > processRight->ioCharactersReadRate(); case ProcessModel::SyscallsRate: return processLeft->ioReadSyscallsRate() > processRight->ioReadSyscallsRate(); case ProcessModel::ActualBytesRate: return processLeft->ioCharactersActuallyReadRate() > processRight->ioCharactersActuallyReadRate(); } return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through case HeadingIoWrite: switch(d->mIoInformation) { case ProcessModel::Bytes: return processLeft->ioCharactersWritten() > processRight->ioCharactersWritten(); case ProcessModel::Syscalls: return processLeft->ioWriteSyscalls() > processRight->ioWriteSyscalls(); case ProcessModel::ActualBytes: return processLeft->ioCharactersActuallyWritten() > processRight->ioCharactersActuallyWritten(); case ProcessModel::BytesRate: return processLeft->ioCharactersWrittenRate() > processRight->ioCharactersWrittenRate(); case ProcessModel::SyscallsRate: return processLeft->ioWriteSyscallsRate() > processRight->ioWriteSyscallsRate(); case ProcessModel::ActualBytesRate: return processLeft->ioCharactersActuallyWrittenRate() > processRight->ioCharactersActuallyWrittenRate(); } } //Sort by the display string if we do not have an explicit sorting here return data(left, Qt::DisplayRole).toString() < data(right, Qt::DisplayRole).toString(); } ProcessModel::~ProcessModel() { delete d; } KSysGuard::Processes *ProcessModel::processController() const { return d->mProcesses; } #if HAVE_X11 void ProcessModelPrivate::windowRemoved(WId wid) { WindowInfo *window = mWIdToWindowInfo.take(wid); if(!window) return; qlonglong pid = window->pid; QMultiHash::iterator i = mPidToWindowInfo.find(pid); while (i != mPidToWindowInfo.end() && i.key() == pid) { if(i.value()->wid == wid) { i = mPidToWindowInfo.erase(i); break; } else i++; } delete window; //Update the model so that it redraws and resorts KSysGuard::Process *process = mProcesses->getProcess(pid); if(!process) return; int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process); emit q->dataChanged(index2, index2); } #endif #if HAVE_X11 void ProcessModelPrivate::setupWindows() { if (!mIsX11) { return; } connect( KWindowSystem::self(), SIGNAL(windowChanged(WId,uint)), this, SLOT(windowChanged(WId,uint))); connect( KWindowSystem::self(), &KWindowSystem::windowAdded, this, &ProcessModelPrivate::windowAdded); connect( KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &ProcessModelPrivate::windowRemoved); //Add all the windows that KWin is managing - i.e. windows that the user can see const QList windows = KWindowSystem::windows(); for (auto it = windows.begin(); it != windows.end(); ++it ) { updateWindowInfo(*it, ~0u, true); } } #endif #ifdef HAVE_XRES bool ProcessModelPrivate::updateXResClientData() { if (!mIsX11) { return false; } XResClient *clients; int count; XResQueryClients(QX11Info::display(), &count, &clients); mXResClientResources.clear(); for (int i=0; i < count; i++) mXResClientResources.insert(-(qlonglong)(clients[i].resource_base), clients[i].resource_mask); if(clients) XFree(clients); return true; } void ProcessModelPrivate::queryForAndUpdateAllXWindows() { if (!mIsX11) { return; } updateXResClientData(); Window *children, dummy; unsigned int count; Status result = XQueryTree(QX11Info::display(), QX11Info::appRootWindow(), &dummy, &dummy, &children, &count); if(!result) return; if(!updateXResClientData()) return; for (uint i=0; i < count; ++i) { WId wid = children[i]; QMap::iterator iter = mXResClientResources.lowerBound(-(qlonglong)(wid)); if(iter == mXResClientResources.end()) continue; //We couldn't find it this time :-/ if(-iter.key() != (qlonglong)(wid & ~iter.value())) continue; //Already added this window //Get the PID for this window if we do not know it NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::WMPid, NET::Properties2()); qlonglong pid = info.pid(); if(!pid) continue; //We found a window with this client mXResClientResources.erase(iter); KSysGuard::Process *process = mProcesses->getProcess(pid); if(!process) return; //shouldn't really happen.. maybe race condition etc unsigned long previousPixmapBytes = process->pixmapBytes(); //Now update the pixmap bytes for this window bool success = XResQueryClientPixmapBytes(QX11Info::display(), wid, &process->pixmapBytes()); if(!success) process->pixmapBytes() = 0; if(previousPixmapBytes != process->pixmapBytes()) { int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); QModelIndex index = q->createIndex(row, ProcessModel::HeadingXMemory, process); emit q->dataChanged(index, index); } } if(children) XFree((char*)children); } #endif void ProcessModelPrivate::setupProcesses() { if(mProcesses) { #ifdef Q_WS_X11_DISABLE mWIdToWindowInfo.clear(); mPidToWindowInfo.clear(); #endif delete mProcesses; mProcesses = nullptr; q->beginResetModel(); q->endResetModel(); } mProcesses = new KSysGuard::Processes(mHostName); connect( mProcesses, &KSysGuard::Processes::processChanged, this, &ProcessModelPrivate::processChanged); connect( mProcesses, &KSysGuard::Processes::beginAddProcess, this, &ProcessModelPrivate::beginInsertRow); connect( mProcesses, &KSysGuard::Processes::endAddProcess, this, &ProcessModelPrivate::endInsertRow); connect( mProcesses, &KSysGuard::Processes::beginRemoveProcess, this, &ProcessModelPrivate::beginRemoveRow); connect( mProcesses, &KSysGuard::Processes::endRemoveProcess, this, &ProcessModelPrivate::endRemoveRow); connect( mProcesses, &KSysGuard::Processes::beginMoveProcess, this, &ProcessModelPrivate::beginMoveProcess); connect( mProcesses, &KSysGuard::Processes::endMoveProcess, this, &ProcessModelPrivate::endMoveRow); mNumProcessorCores = mProcesses->numberProcessorCores(); if(mNumProcessorCores < 1) mNumProcessorCores=1; //Default to 1 if there was an error getting the number } #if HAVE_X11 void ProcessModelPrivate::windowChanged(WId wid, unsigned int properties) { updateWindowInfo(wid, properties, false); } void ProcessModelPrivate::windowAdded(WId wid) { updateWindowInfo(wid, ~0u, true); } void ProcessModelPrivate::updateWindowInfo(WId wid, unsigned int properties, bool newWindow) { if (!mIsX11) { return; } properties &= (NET::WMPid | NET::WMVisibleName | NET::WMName | NET::WMIcon); if(!properties) return; //Nothing interesting changed WindowInfo *w = mWIdToWindowInfo.value(wid); if(!w && !newWindow) return; //We do not have a record of this window and this is not a new window if(properties == NET::WMIcon) { if(w) w->icon = KWindowSystem::icon(wid, HEADING_X_ICON_SIZE, HEADING_X_ICON_SIZE, true); return; } /* Get PID for window */ NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::Properties(properties) & ~NET::WMIcon, NET::Properties2()); if(!w) { //We know that this must be a newWindow qlonglong pid = info.pid(); if(!(properties & NET::WMPid && pid)) return; //No PID for the window - this happens if the process did not set _NET_WM_PID //If we are to get the PID only, we are only interested in the XRes info for this, //so don't bother if we already have this info if(properties == NET::WMPid && mPidToWindowInfo.contains(pid)) return; w = new WindowInfo(wid, pid); mPidToWindowInfo.insertMulti(pid, w); mWIdToWindowInfo.insert(wid, w); } if(w && (properties & NET::WMIcon)) w->icon = KWindowSystem::icon(wid, HEADING_X_ICON_SIZE, HEADING_X_ICON_SIZE, true); if(properties & NET::WMVisibleName && info.visibleName()) w->name = QString::fromUtf8(info.visibleName()); else if(properties & NET::WMName) w->name = QString::fromUtf8(info.name()); else if(properties & (NET::WMName | NET::WMVisibleName)) w->name.clear(); KSysGuard::Process *process = mProcesses->getProcess(w->pid); if(!process) { return; //This happens when a new window is detected before we've read in the process } int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); if(!process->hasManagedGuiWindow()) { process->hasManagedGuiWindow() = true; //Since this is the first window for a process, invalidate HeadingName so that //if we are sorting by name this gets taken into account QModelIndex index1 = q->createIndex(row, ProcessModel::HeadingName, process); emit q->dataChanged(index1, index1); } QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process); emit q->dataChanged(index2, index2); } #endif void ProcessModel::update(long updateDurationMSecs, KSysGuard::Processes::UpdateFlags updateFlags) { if(updateFlags != KSysGuard::Processes::XMemory) { d->mProcesses->updateAllProcesses(updateDurationMSecs, updateFlags); if(d->mMemTotal <= 0) d->mMemTotal = d->mProcesses->totalPhysicalMemory(); } #ifdef HAVE_XRES //Add all the rest of the windows if(d->mHaveXRes && updateFlags.testFlag(KSysGuard::Processes::XMemory)) d->queryForAndUpdateAllXWindows(); #endif } QString ProcessModelPrivate::getStatusDescription(KSysGuard::Process::ProcessStatus status) const { switch( status) { case KSysGuard::Process::Running: return i18n("- Process is doing some work."); case KSysGuard::Process::Sleeping: return i18n("- Process is waiting for something to happen."); case KSysGuard::Process::Stopped: return i18n("- Process has been stopped. It will not respond to user input at the moment."); case KSysGuard::Process::Zombie: return i18n("- Process has finished and is now dead, but the parent process has not cleaned up."); case KSysGuard::Process::Ended: // return i18n("- Process has finished and no longer exists."); default: return QString(); } } KSysGuard::Process *ProcessModel::getProcessAtIndex(int index) const { Q_ASSERT(d->mSimple); return d->mProcesses->getAllProcesses().at(index); } int ProcessModel::rowCount(const QModelIndex &parent) const { if(d->mSimple) { if(parent.isValid()) return 0; //In flat mode, none of the processes have children return d->mProcesses->processCount(); } //Deal with the case that we are showing it as a tree KSysGuard::Process *process; if(parent.isValid()) { if(parent.column() != 0) return 0; //For a treeview we say that only the first column has children process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); //when parent is invalid, it must be the root level which we set as 0 } else { process = d->mProcesses->getProcess(-1); } Q_ASSERT(process); int num_rows = process->children().count(); return num_rows; } int ProcessModel::columnCount ( const QModelIndex & ) const { return d->mHeadings.count(); } bool ProcessModel::hasChildren ( const QModelIndex & parent = QModelIndex() ) const { if(d->mSimple) { if(parent.isValid()) return 0; //In flat mode, none of the processes have children return !d->mProcesses->getAllProcesses().isEmpty(); } //Deal with the case that we are showing it as a tree KSysGuard::Process *process; if(parent.isValid()) { if(parent.column() != 0) return false; //For a treeview we say that only the first column has children process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); //when parent is invalid, it must be the root level which we set as 0 } else { process = d->mProcesses->getProcess(-1); } Q_ASSERT(process); bool has_children = !process->children().isEmpty(); Q_ASSERT((rowCount(parent) > 0) == has_children); return has_children; } QModelIndex ProcessModel::index ( int row, int column, const QModelIndex & parent ) const { if(row<0) return QModelIndex(); if(column<0 || column >= d->mHeadings.count() ) return QModelIndex(); if(d->mSimple) { if( parent.isValid()) return QModelIndex(); if( d->mProcesses->processCount() <= row) return QModelIndex(); return createIndex( row, column, d->mProcesses->getAllProcesses().at(row)); } //Deal with the case that we are showing it as a tree KSysGuard::Process *parent_process = nullptr; if(parent.isValid()) //not valid for init or children without parents, so use our special item with pid of 0 parent_process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); else parent_process = d->mProcesses->getProcess(-1); Q_ASSERT(parent_process); if(parent_process->children().count() > row) return createIndex(row,column, parent_process->children()[row]); else { return QModelIndex(); } } bool ProcessModel::isSimpleMode() const { return d->mSimple; } void ProcessModelPrivate::processChanged(KSysGuard::Process *process, bool onlyTotalCpu) { int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); if (!process->timeKillWasSent().isNull()) { int elapsed = process->timeKillWasSent().elapsed(); if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) { if (!mPidsToUpdate.contains(process->pid())) mPidsToUpdate.append(process->pid()); QModelIndex index1 = q->createIndex(row, 0, process); QModelIndex index2 = q->createIndex(row, mHeadings.count()-1, process); emit q->dataChanged(index1, index2); if (!mHaveTimer) { mHaveTimer = true; mTimerId = startTimer(100); } } } int totalUpdated = 0; Q_ASSERT(row != -1); //Something has gone very wrong if(onlyTotalCpu) { if(mShowChildTotals) { //Only the total cpu usage changed, so only update that QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process); emit q->dataChanged(index, index); } return; } else { if(process->changes() == KSysGuard::Process::Nothing) { return; //Nothing changed } if(process->changes() & KSysGuard::Process::Uids) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Tty) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingTty, process); emit q->dataChanged(index, index); } if(process->changes() & (KSysGuard::Process::Usage | KSysGuard::Process::Status) || (process->changes() & KSysGuard::Process::TotalUsage && mShowChildTotals)) { totalUpdated+=2; QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process); emit q->dataChanged(index, index); index = q->createIndex(row, ProcessModel::HeadingCPUTime, process); emit q->dataChanged(index, index); //Because of our sorting, changing usage needs to also invalidate the User column index = q->createIndex(row, ProcessModel::HeadingUser, process); emit q->dataChanged(index, index); } + if(process->changes() & KSysGuard::Process::Status) { + totalUpdated++; + QModelIndex index = q->createIndex(row, ProcessModel::HeadingNoNewPrivileges, process); + emit q->dataChanged(index, index); + } if(process->changes() & KSysGuard::Process::NiceLevels) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingNiceness, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::VmSize) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingVmSize, process); emit q->dataChanged(index, index); } if(process->changes() & (KSysGuard::Process::VmSize | KSysGuard::Process::VmRSS | KSysGuard::Process::VmURSS)) { totalUpdated+=2; QModelIndex index = q->createIndex(row, ProcessModel::HeadingMemory, process); emit q->dataChanged(index, index); QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingSharedMemory, process); emit q->dataChanged(index2, index2); //Because of our sorting, changing usage needs to also invalidate the User column index = q->createIndex(row, ProcessModel::HeadingUser, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Name) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingName, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Command) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingCommand, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::Login) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process); emit q->dataChanged(index, index); } if(process->changes() & KSysGuard::Process::IO) { totalUpdated++; QModelIndex index = q->createIndex(row, ProcessModel::HeadingIoRead, process); emit q->dataChanged(index, index); index = q->createIndex(row, ProcessModel::HeadingIoWrite, process); emit q->dataChanged(index, index); } /* Normally this would only be called if changes() tells * us to. We need to update the timestamp even if the value * didn't change though. */ auto historyMapEntry = mMapProcessCPUHistory.find(process); if(historyMapEntry != mMapProcessCPUHistory.end()) { auto &history = *historyMapEntry; unsigned long timestamp = QDateTime::currentMSecsSinceEpoch(); // Only add an entry if the latest one is older than MIN_HIST_AGE if(history.isEmpty() || timestamp - history.constLast().timestamp > MIN_HIST_AGE) { if(history.size() == MAX_HIST_ENTRIES) { history.removeFirst(); } float usage = (process->totalUserUsage() + process->totalSysUsage()) / (100.0f * mNumProcessorCores); history.push_back({static_cast(QDateTime::currentMSecsSinceEpoch()), usage}); } } } } void ProcessModelPrivate::beginInsertRow( KSysGuard::Process *process) { Q_ASSERT(process); Q_ASSERT(!mRemovingRow); Q_ASSERT(!mInsertingRow); Q_ASSERT(!mMovingRow); mInsertingRow = true; #if HAVE_X11 process->hasManagedGuiWindow() = mPidToWindowInfo.contains(process->pid()); #endif if(mSimple) { int row = mProcesses->processCount(); q->beginInsertRows( QModelIndex(), row, row ); return; } //Deal with the case that we are showing it as a tree int row = process->parent()->children().count(); QModelIndex parentModelIndex = q->getQModelIndex(process->parent(), 0); //Only here can we actually change the model. First notify the view/proxy models then modify q->beginInsertRows(parentModelIndex, row, row); } void ProcessModelPrivate::endInsertRow() { Q_ASSERT(!mRemovingRow); Q_ASSERT(mInsertingRow); Q_ASSERT(!mMovingRow); mInsertingRow = false; q->endInsertRows(); } void ProcessModelPrivate::beginRemoveRow( KSysGuard::Process *process ) { Q_ASSERT(process); Q_ASSERT(process->pid() >= 0); Q_ASSERT(!mRemovingRow); Q_ASSERT(!mInsertingRow); Q_ASSERT(!mMovingRow); mRemovingRow = true; mMapProcessCPUHistory.remove(process); if(mSimple) { return q->beginRemoveRows(QModelIndex(), process->index(), process->index()); } else { int row = process->parent()->children().indexOf(process); if(row == -1) { qCDebug(LIBKSYSGUARD_PROCESSUI) << "A serious problem occurred in remove row."; mRemovingRow = false; return; } return q->beginRemoveRows(q->getQModelIndex(process->parent(), 0), row, row); } } void ProcessModelPrivate::endRemoveRow() { Q_ASSERT(!mInsertingRow); Q_ASSERT(!mMovingRow); if(!mRemovingRow) return; mRemovingRow = false; q->endRemoveRows(); } void ProcessModelPrivate::beginMoveProcess(KSysGuard::Process *process, KSysGuard::Process *new_parent) { Q_ASSERT(!mRemovingRow); Q_ASSERT(!mInsertingRow); Q_ASSERT(!mMovingRow); if(mSimple) return; //We don't need to move processes when in simple mode mMovingRow = true; int current_row = process->parent()->children().indexOf(process); Q_ASSERT(current_row != -1); int new_row = new_parent->children().count(); QModelIndex sourceParent = q->getQModelIndex( process->parent(), 0); QModelIndex destinationParent = q->getQModelIndex( new_parent, 0 ); mMovingRow = q->beginMoveRows(sourceParent, current_row, current_row, destinationParent, new_row); Q_ASSERT(mMovingRow); } void ProcessModelPrivate::endMoveRow() { Q_ASSERT(!mInsertingRow); Q_ASSERT(!mRemovingRow); if(!mMovingRow) return; mMovingRow = false; q->endMoveRows(); } QModelIndex ProcessModel::getQModelIndex( KSysGuard::Process *process, int column) const { Q_ASSERT(process); int pid = process->pid(); if (pid == -1) return QModelIndex(); //pid -1 is our fake process meaning the very root (never drawn). To represent that, we return QModelIndex() which also means the top element int row = 0; if(d->mSimple) { row = process->index(); } else { row = process->parent()->children().indexOf(process); } Q_ASSERT(row != -1); return createIndex(row, column, process); } QModelIndex ProcessModel::parent ( const QModelIndex & index ) const { if(!index.isValid()) return QModelIndex(); KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); Q_ASSERT(process); if(d->mSimple) return QModelIndex(); else return getQModelIndex(process->parent(), 0); } QVariant ProcessModel::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation != Qt::Horizontal) return QVariant(); if(section < 0 || section >= d->mHeadings.count()) return QVariant(); //is this needed? switch( role ) { case Qt::TextAlignmentRole: { switch(section) { case HeadingPid: case HeadingTty: case HeadingMemory: case HeadingXMemory: case HeadingSharedMemory: case HeadingStartTime: + case HeadingNoNewPrivileges: case HeadingIoRead: case HeadingIoWrite: case HeadingVmSize: case HeadingNiceness: // return QVariant(Qt::AlignRight); case HeadingUser: case HeadingCPUUsage: case HeadingCPUTime: return QVariant(Qt::AlignCenter); } return QVariant(); } case Qt::ToolTipRole: { if(!d->mShowingTooltips) return QVariant(); switch(section) { case HeadingName: return i18n("The process name."); case HeadingUser: return i18n("The user who owns this process."); case HeadingTty: return i18n("The controlling terminal on which this process is running."); case HeadingNiceness: return i18n("The priority with which this process is being run. For the normal scheduler, this ranges from 19 (very nice, least priority) to -19 (top priority)."); case HeadingCPUUsage: if(d->mNumProcessorCores == 1) return i18n("The current CPU usage of the process."); else // i18n: %1 is always greater than 1, so do not worry about // nonsensical verbosity of the singular part. if(d->mNormalizeCPUUsage) return i18np("The current total CPU usage of the process, divided by the %1 processor core in the machine.", "The current total CPU usage of the process, divided by the %1 processor cores in the machine.", d->mNumProcessorCores); else return i18n("The current total CPU usage of the process."); case HeadingCPUTime: return i18n("The total user and system time that this process has been running for, displayed as minutes:seconds."); case HeadingVmSize: return i18n("This is the amount of virtual memory space that the process is using, included shared libraries, graphics memory, files on disk, and so on. This number is almost meaningless."); case HeadingMemory: return i18n("This is the amount of real physical memory that this process is using by itself, and approximates the Private memory usage of the process.
It does not include any swapped out memory, nor the code size of its shared libraries.
This is often the most useful figure to judge the memory use of a program. See What's This for more information.
"); case HeadingSharedMemory: return i18n("This is approximately the amount of real physical memory that this process's shared libraries are using.
This memory is shared among all processes that use this library.
"); case HeadingStartTime: return i18n("The elapsed time since the process was started."); + case HeadingNoNewPrivileges: + return i18n("Linux flag NoNewPrivileges, if set the process can't gain further privileges via setuid etc."); case HeadingCommand: return i18n("The command with which this process was launched."); case HeadingXMemory: return i18n("The amount of pixmap memory that this process is using."); case HeadingXTitle: return i18n("The title of any windows that this process is showing."); case HeadingPid: return i18n("The unique Process ID that identifies this process."); case HeadingIoRead: return i18n("The number of bytes read. See What's This for more information."); case HeadingIoWrite: return i18n("The number of bytes written. See What's This for more information."); default: return QVariant(); } } case Qt::WhatsThisRole: { switch(section) { case HeadingName: return i18n("Technical information: The kernel process name is a maximum of 8 characters long, so the full command is examined. If the first word in the full command line starts with the process name, the first word of the command line is shown, otherwise the process name is used."); case HeadingUser: return i18n("The user who owns this process. If the effective, setuid etc user is different, the user who owns the process will be shown, followed by the effective user. The ToolTip contains the full information.

" "" "" "" "" #ifdef Q_OS_LINUX "" #endif "
Login Name/GroupThe username of the Real User/Group who created this process
Effective User/GroupThe process is running with privileges of the Effective User/Group. This is shown if different from the real user.
Setuid User/GroupThe saved username of the binary. The process can escalate its Effective User/Group to the Setuid User/Group.
File System User/GroupAccesses to the filesystem are checked with the File System User/Group. This is a Linux specific call. See setfsuid(2) for more information.
"); case HeadingVmSize: return i18n("This is the size of allocated address space - not memory, but address space. This value in practice means next to nothing. When a process requests a large memory block from the system but uses only a small part of it, the real usage will be low, VIRT will be high.

Technical information: This is VmSize in proc/*/status and VIRT in top."); case HeadingMemory: return i18n("Technical information: This is an approximation of the Private memory usage, calculated as VmRSS - Shared, from /proc/*/statm. This tends to underestimate the true Private memory usage of a process (by not including i/o backed memory pages), but is the best estimation that is fast to determine. This is sometimes known as URSS (Unique Resident Set Size). For an individual process, see \"Detailed Memory Information\" for a more accurate, but slower, calculation of the true Private memory usage."); case HeadingCPUUsage: return i18n("The CPU usage of a process and all of its threads."); case HeadingCPUTime: return i18n("The total system and user time that a process and all of its threads have been running on the CPU for. This can be greater than the wall clock time if the process has been across multiple CPU cores."); case HeadingSharedMemory: return i18n("Technical information: This is an approximation of the Shared memory, called SHR in top. It is the number of pages that are backed by a file (see kernel Documentation/filesystems/proc.txt). For an individual process, see \"Detailed Memory Information\" for a more accurate, but slower, calculation of the true Shared memory usage."); case HeadingStartTime: return i18n("Technical information: The underlying value (clock ticks since system boot) is retrieved from /proc/[pid]/stat"); + case HeadingNoNewPrivileges: + return i18n("Technical information: The flag is retrieved from /proc/[pid]/status"); case HeadingCommand: return i18n("Technical information: This is from /proc/*/cmdline"); case HeadingXMemory: return i18n("Technical information: This is the amount of memory used by the Xorg process for images for this process. This is memory used in addition to Memory and Shared Memory.
Technical information: This only counts the pixmap memory, and does not include resource memory used by fonts, cursors, glyphsets etc. See the xrestop program for a more detailed breakdown."); case HeadingXTitle: return i18n("Technical information: For each X11 window, the X11 property _NET_WM_PID is used to map the window to a PID. If a process' windows are not shown, then that application incorrectly is not setting _NET_WM_PID."); case HeadingPid: return i18n("Technical information: This is the Process ID. A multi-threaded application is treated a single process, with all threads sharing the same PID. The CPU usage etc will be the total, accumulated, CPU usage of all the threads."); case HeadingIoRead: case HeadingIoWrite: return i18n("This column shows the IO statistics for each process. The tooltip provides the following information:
" "" "" "" "" "" "" "" "
Characters ReadThe number of bytes which this task has caused to be read from storage. This is simply the sum of bytes which this process passed to read() and pread(). It includes things like tty IO and it is unaffected by whether or not actual physical disk IO was required (the read might have been satisfied from pagecache).
Characters WrittenThe number of bytes which this task has caused, or shall cause to be written to disk. Similar caveats apply here as with Characters Read.
Read SyscallsThe number of read I/O operations, i.e. syscalls like read() and pread().
Write SyscallsThe number of write I/O operations, i.e. syscalls like write() and pwrite().
Actual Bytes ReadThe number of bytes which this process really did cause to be fetched from the storage layer. Done at the submit_bio() level, so it is accurate for block-backed filesystems. This may not give sensible values for NFS and CIFS filesystems.
Actual Bytes WrittenAttempt to count the number of bytes which this process caused to be sent to the storage layer. This is done at page-dirtying time.

" "The number in brackets shows the rate at which each value is changing, determined from taking the difference between the previous value and the new value, and dividing by the update interval.

" "Technical information: This data is collected from /proc/*/io and is documented further in Documentation/accounting and Documentation/filesystems/proc.txt in the kernel source."); default: return QVariant(); } } case Qt::DisplayRole: return d->mHeadings[section]; default: return QVariant(); } } void ProcessModel::setSimpleMode(bool simple) { if(d->mSimple == simple) return; emit layoutAboutToBeChanged (); d->mSimple = simple; int flatrow; int treerow; QList flatIndexes; QList treeIndexes; foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { flatrow = process->index(); treerow = process->parent()->children().indexOf(process); flatIndexes.clear(); treeIndexes.clear(); for(int i=0; i < columnCount(); i++) { flatIndexes << createIndex(flatrow, i, process); treeIndexes << createIndex(treerow, i, process); } if(d->mSimple) //change from tree mode to flat mode changePersistentIndexList(treeIndexes, flatIndexes); else // change from flat mode to tree mode changePersistentIndexList(flatIndexes, treeIndexes); } emit layoutChanged(); } bool ProcessModel::canUserLogin(long uid ) const { if(uid == 65534) { //nobody user return false; } if(!d->mIsLocalhost) return true; //We only deal with localhost. Just always return true for non localhost int canLogin = d->mUidCanLogin.value(uid, -1); //Returns 0 if we cannot login, 1 if we can, and the default is -1 meaning we don't know if(canLogin != -1) return canLogin; //We know whether they can log in //We got the default, -1, so we don't know. Look it up KUser user(uid); if(!user.isValid()) { //for some reason the user isn't recognised. This might happen under certain security situations. //Just return true to be safe d->mUidCanLogin[uid] = 1; return true; } QString shell = user.shell(); if(shell == QLatin1String("/bin/false") ) //FIXME - add in any other shells it could be for false { d->mUidCanLogin[uid] = 0; return false; } d->mUidCanLogin[uid] = 1; return true; } QString ProcessModelPrivate::getTooltipForUser(const KSysGuard::Process *ps) const { QString userTooltip; if(!mIsLocalhost) { return xi18nc("@info:tooltip", "Login Name: %1", getUsernameForUser(ps->uid(), true)); } else { KUser user(ps->uid()); if(!user.isValid()) userTooltip += xi18nc("@info:tooltip", "This user is not recognized for some reason."); else { if(!user.property(KUser::FullName).isValid()) userTooltip += xi18nc("@info:tooltip", "%1", user.property(KUser::FullName).toString()); userTooltip += xi18nc("@info:tooltip", "Login Name: %1 (uid: %2)", user.loginName(), QString::number(ps->uid())); if(!user.property(KUser::RoomNumber).isValid()) userTooltip += xi18nc("@info:tooltip", " Room Number: %1", user.property(KUser::RoomNumber).toString()); if(!user.property(KUser::WorkPhone).isValid()) userTooltip += xi18nc("@info:tooltip", " Work Phone: %1", user.property(KUser::WorkPhone).toString()); } } if( (ps->uid() != ps->euid() && ps->euid() != -1) || (ps->uid() != ps->suid() && ps->suid() != -1) || (ps->uid() != ps->fsuid() && ps->fsuid() != -1)) { if(ps->euid() != -1) userTooltip += xi18nc("@info:tooltip", "Effective User: %1", getUsernameForUser(ps->euid(), true)); if(ps->suid() != -1) userTooltip += xi18nc("@info:tooltip", "Setuid User: %1", getUsernameForUser(ps->suid(), true)); if(ps->fsuid() != -1) userTooltip += xi18nc("@info:tooltip", "File System User: %1", getUsernameForUser(ps->fsuid(), true)); userTooltip += QLatin1String("
"); } if(ps->gid() != -1) { userTooltip += xi18nc("@info:tooltip", "Group: %1", getGroupnameForGroup(ps->gid())); if( (ps->gid() != ps->egid() && ps->egid() != -1) || (ps->gid() != ps->sgid() && ps->sgid() != -1) || (ps->gid() != ps->fsgid() && ps->fsgid() != -1)) { if(ps->egid() != -1) userTooltip += xi18nc("@info:tooltip", "Effective Group: %1", getGroupnameForGroup(ps->egid())); if(ps->sgid() != -1) userTooltip += xi18nc("@info:tooltip", "Setuid Group: %1", getGroupnameForGroup(ps->sgid())); if(ps->fsgid() != -1) userTooltip += xi18nc("@info:tooltip", "File System Group: %1", getGroupnameForGroup(ps->fsgid())); } } return userTooltip; } QString ProcessModel::getStringForProcess(KSysGuard::Process *process) const { return i18nc("Short description of a process. PID, name, user", "%1: %2, owned by user %3", (long)(process->pid()), process->name(), d->getUsernameForUser(process->uid(), false)); } QString ProcessModelPrivate::getGroupnameForGroup(long gid) const { if(mIsLocalhost) { QString groupname = KUserGroup(gid).name(); if(!groupname.isEmpty()) return i18nc("Group name and group id", "%1 (gid: %2)", groupname, gid); } return QString::number(gid); } QString ProcessModelPrivate::getUsernameForUser(long uid, bool withuid) const { QString &username = mUserUsername[uid]; if(username.isNull()) { if(!mIsLocalhost) { username = QLatin1String(""); //empty, but not null } else { KUser user(uid); if(!user.isValid()) username = QLatin1String(""); else username = user.loginName(); } } if(username.isEmpty()) return QString::number(uid); if(withuid) return i18nc("User name and user id", "%1 (uid: %2)", username, (long int)uid); return username; } QVariant ProcessModel::data(const QModelIndex &index, int role) const { //This function must be super duper ultra fast because it's called thousands of times every few second :( //I think it should be optomised for role first, hence the switch statement (fastest possible case) if (!index.isValid()) { return QVariant(); } if (index.column() >= d->mHeadings.count()) { return QVariant(); } KFormat format; switch (role){ case Qt::DisplayRole: { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); switch(index.column()) { case HeadingName: if(d->mShowCommandLineOptions) return process->name(); else return process->name().section(QLatin1Char(' '), 0,0); case HeadingPid: return (qlonglong)process->pid(); case HeadingUser: if(!process->login().isEmpty()) return process->login(); if(process->uid() == process->euid()) return d->getUsernameForUser(process->uid(), false); else return QString(d->getUsernameForUser(process->uid(), false) + QStringLiteral(", ") + d->getUsernameForUser(process->euid(), false)); case HeadingNiceness: switch(process->scheduler()) { case KSysGuard::Process::Other: return process->niceLevel(); case KSysGuard::Process::SchedulerIdle: return i18nc("scheduler", "Idle"); //neither static nor dynamic priority matter case KSysGuard::Process::Batch: return i18nc("scheduler", "(Batch) %1", process->niceLevel()); //only dynamic priority matters case KSysGuard::Process::RoundRobin: return i18nc("Round robin scheduler", "RR %1", process->niceLevel()); case KSysGuard::Process::Fifo: if(process->niceLevel() == 99) return i18nc("Real Time scheduler", "RT"); else return i18nc("First in first out scheduler", "FIFO %1", process->niceLevel()); case KSysGuard::Process::Interactive: return i18nc("scheduler", "(IA) %1", process->niceLevel()); } return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through case HeadingTty: return process->tty(); case HeadingCPUUsage: { double total; if(d->mShowChildTotals && !d->mSimple) total = process->totalUserUsage() + process->totalSysUsage(); else total = process->userUsage() + process->sysUsage(); if(d->mNormalizeCPUUsage) total = total / d->mNumProcessorCores; if(total < 1 && process->status() != KSysGuard::Process::Sleeping && process->status() != KSysGuard::Process::Running && process->status() != KSysGuard::Process::Ended) return process->translatedStatus(); //tell the user when the process is a zombie or stopped if(total < 0.5) return QString(); return QString(QString::number((int)(total+0.5)) + QLatin1Char('%')); } case HeadingCPUTime: { qlonglong seconds = (process->userTime() + process->sysTime())/100; return QStringLiteral("%1:%2").arg(seconds/60).arg((int)seconds%60, 2, 10, QLatin1Char('0')); } case HeadingMemory: if(process->vmURSS() == -1) { //If we don't have the URSS (the memory used by only the process, not the shared libraries) //then return the RSS (physical memory used by the process + shared library) as the next best thing return formatMemoryInfo(process->vmRSS(), d->mUnits, true); } else { return formatMemoryInfo(process->vmURSS(), d->mUnits, true); } case HeadingVmSize: return formatMemoryInfo(process->vmSize(), d->mUnits, true); case HeadingSharedMemory: if(process->vmRSS() - process->vmURSS() <= 0 || process->vmURSS() == -1) return QVariant(QVariant::String); return formatMemoryInfo(process->vmRSS() - process->vmURSS(), d->mUnits); case HeadingStartTime: { // NOTE: the next 6 lines are the same as in the next occurrence of 'case HeadingStartTime:' => keep in sync or remove duplicate code const auto clockTicksSinceSystemBoot = process->startTime(); const auto clockTicksPerSecond = sysconf(_SC_CLK_TCK); // see man proc or http://superuser.com/questions/101183/what-is-a-cpu-tick const auto secondsSinceSystemBoot = (double)clockTicksSinceSystemBoot / clockTicksPerSecond; const auto systemBootTime = TimeUtil::systemUptimeAbsolute(); const auto absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot); const auto relativeStartTime = absoluteStartTime.secsTo(QDateTime::currentDateTime()); return TimeUtil::secondsToHumanElapsedString(relativeStartTime); } + case HeadingNoNewPrivileges: + return QString::number(process->noNewPrivileges()); case HeadingCommand: { return process->command().replace(QLatin1Char('\n'),QLatin1Char(' ')); // It would be nice to embolden the process name in command, but this requires that the itemdelegate to support html text // QString command = process->command; // command.replace(process->name, "" + process->name + ""); // return "" + command; } case HeadingIoRead: { switch(d->mIoInformation) { case ProcessModel::Bytes: //divide by 1024 to convert to kB return formatMemoryInfo(process->ioCharactersRead() / 1024, d->mIoUnits, true); case ProcessModel::Syscalls: if( process->ioReadSyscalls()) return QString::number(process->ioReadSyscalls()); break; case ProcessModel::ActualBytes: return formatMemoryInfo(process->ioCharactersActuallyRead() / 1024, d->mIoUnits, true); case ProcessModel::BytesRate: if( process->ioCharactersReadRate() / 1024) return i18n("%1/s", formatMemoryInfo(process->ioCharactersReadRate() / 1024, d->mIoUnits, true)); break; case ProcessModel::SyscallsRate: if( process->ioReadSyscallsRate()) return QString::number(process->ioReadSyscallsRate()); break; case ProcessModel::ActualBytesRate: if( process->ioCharactersActuallyReadRate() / 1024) return i18n("%1/s", formatMemoryInfo(process->ioCharactersActuallyReadRate() / 1024, d->mIoUnits, true)); break; } return QVariant(); } case HeadingIoWrite: { switch(d->mIoInformation) { case ProcessModel::Bytes: return formatMemoryInfo(process->ioCharactersWritten() / 1024, d->mIoUnits, true); case ProcessModel::Syscalls: if( process->ioWriteSyscalls()) return QString::number(process->ioWriteSyscalls()); break; case ProcessModel::ActualBytes: return formatMemoryInfo(process->ioCharactersActuallyWritten() / 1024, d->mIoUnits, true); case ProcessModel::BytesRate: if(process->ioCharactersWrittenRate() / 1024) return i18n("%1/s", formatMemoryInfo(process->ioCharactersWrittenRate() / 1024, d->mIoUnits, true)); break; case ProcessModel::SyscallsRate: if( process->ioWriteSyscallsRate()) return QString::number(process->ioWriteSyscallsRate()); break; case ProcessModel::ActualBytesRate: if(process->ioCharactersActuallyWrittenRate() / 1024) return i18n("%1/s", formatMemoryInfo(process->ioCharactersActuallyWrittenRate() / 1024, d->mIoUnits, true)); break; } return QVariant(); } #if HAVE_X11 case HeadingXMemory: return formatMemoryInfo(process->pixmapBytes() / 1024, d->mUnits, true); case HeadingXTitle: { if(!process->hasManagedGuiWindow()) return QVariant(QVariant::String); WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); if(!w) return QVariant(QVariant::String); else return w->name; } #endif default: return QVariant(); } break; } case Qt::ToolTipRole: { if(!d->mShowingTooltips) return QVariant(); KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); QString tracer; if(process->tracerpid() >= 0) { KSysGuard::Process *process_tracer = d->mProcesses->getProcess(process->tracerpid()); if(process_tracer) //it is possible for this to be not the case in certain race conditions tracer = xi18nc("tooltip. name,pid ","This process is being debugged by %1 (%2)", process_tracer->name(), (long int)process->tracerpid()); } switch(index.column()) { case HeadingName: { /* It would be nice to be able to show the icon in the tooltip, but Qt4 won't let us put * a picture in a tooltip :( QIcon icon; if(mPidToWindowInfo.contains(process->pid())) { WId wid; wid = mPidToWindowInfo[process->pid()].wid; icon = KWindowSystem::icon(wid); } if(icon.isValid()) { tooltip = i18n(""); firstrow = false; } for(int i = 0; i < d->mHeadings.size(); i++) { if(firstrow) { QString heading = d->mHeadings[i]; textHtmlHeaders += QStringLiteral(""); if(i) { textCsvHeaders += QLatin1Char(','); textPlainHeaders += QLatin1String(", "); } textPlainHeaders += heading; heading.replace(QLatin1Char('"'), QLatin1String("\"\"")); textCsvHeaders += QLatin1Char('"') + heading + QLatin1Char('"'); } QModelIndex index2 = createIndex(index.row(), i, reinterpret_cast< KSysGuard::Process * > (index.internalPointer())); QString display = data(index2, PlainValueRole).toString(); if(i) { textCsv += QLatin1Char(','); textPlain += QLatin1String(", "); } textHtml += QStringLiteral(""); textPlain += display; display.replace(QLatin1Char('"'),QLatin1String("\"\"")); textCsv += QLatin1Char('"') + display + QLatin1Char('"'); } } } textHtml = QStringLiteral("
%1", icon); } */ QString tooltip; if(process->parentPid() == -1) { //Give a quick explanation of init and kthreadd if(process->name() == QLatin1String("init") || process->name() == QLatin1String("systemd")) { tooltip = xi18nc("@info:tooltip", "%1The parent of all other processes and cannot be killed.Process ID: %2", process->name(), (long int)process->pid()); } else if(process->name() == QLatin1String("kthreadd")) { tooltip = xi18nc("@info:tooltip", "KThreaddManages kernel threads. The children processes run in the kernel, controlling hard disk access, etc."); } else { tooltip = xi18nc("@info:tooltip","%1Process ID: %2",process->name(), (long int)process->pid()); } } else { KSysGuard::Process *parent_process = d->mProcesses->getProcess(process->parentPid()); if(parent_process) { //In race conditions, it's possible for this process to not exist tooltip = xi18nc("@info:tooltip", "%1" "Process ID: %2" "Parent: %3" "Parent's ID: %4", process->name(), (long int)process->pid(), parent_process->name(), (long int)process->parentPid()); } else { tooltip = xi18nc("@info:tooltip", "%1" "Process ID: %2" "Parent's ID: %3", process->name(), (long int)process->pid(), (long int)process->parentPid()); } } if(process->numThreads() >= 1) tooltip += xi18nc("@info:tooltip", "Number of threads: %1", process->numThreads()); if(!process->command().isEmpty()) { tooltip += xi18nc("@info:tooltip", "Command: %1", process->command()); } if(!process->tty().isEmpty()) tooltip += xi18nc("@info:tooltip", "Running on: %1", QString::fromUtf8(process->tty())); if(!tracer.isEmpty()) return QStringLiteral("%1
%2").arg(tooltip).arg(tracer); return tooltip; } case HeadingStartTime: { // NOTE: the next 6 lines are the same as in the previous occurrence of 'case HeadingStartTime:' => keep in sync or remove duplicate code const auto clockTicksSinceSystemBoot = process->startTime(); const auto clockTicksPerSecond = sysconf(_SC_CLK_TCK); const auto secondsSinceSystemBoot = (double)clockTicksSinceSystemBoot / clockTicksPerSecond; const auto systemBootTime = TimeUtil::systemUptimeAbsolute(); const auto absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot); const auto relativeStartTime = absoluteStartTime.secsTo(QDateTime::currentDateTime()); return xi18nc("@info:tooltip", "Clock ticks since system boot: %1" "Seconds since system boot: %2 (System boot time: %3)" "Absolute start time: %4" "Relative start time: %5", clockTicksSinceSystemBoot, secondsSinceSystemBoot, systemBootTime.toString(), absoluteStartTime.toString(), TimeUtil::secondsToHumanElapsedString(relativeStartTime)); } case HeadingCommand: { QString tooltip = xi18nc("@info:tooltip", "This process was run with the following command:" "%1", process->command()); if(!process->tty().isEmpty()) tooltip += xi18nc("@info:tooltip", "Running on: %1", QString::fromUtf8(process->tty())); if (!tracer.isEmpty()) { return QStringLiteral("%1
%2").arg(tooltip).arg(tracer); } return tooltip; } case HeadingUser: { QString tooltip = d->getTooltipForUser(process); if(tracer.isEmpty()) { return tooltip; } return QString(tooltip + QStringLiteral("
") + tracer); } case HeadingNiceness: { QString tooltip; switch(process->scheduler()) { case KSysGuard::Process::Other: case KSysGuard::Process::Batch: case KSysGuard::Process::Interactive: tooltip = xi18nc("@info:tooltip", "Nice level: %1 (%2)", process->niceLevel(), process->niceLevelAsString()); break; case KSysGuard::Process::RoundRobin: case KSysGuard::Process::Fifo: tooltip = xi18nc("@info:tooltip", "This is a real time process." "Scheduler priority: %1", process->niceLevel()); break; case KSysGuard::Process::SchedulerIdle: break; //has neither dynamic (niceness) or static (scheduler priority) priority } if(process->scheduler() != KSysGuard::Process::Other) tooltip += xi18nc("@info:tooltip", "Scheduler: %1", process->schedulerAsString()); if(process->ioPriorityClass() != KSysGuard::Process::None) { if((process->ioPriorityClass() == KSysGuard::Process::RealTime || process->ioPriorityClass() == KSysGuard::Process::BestEffort) && process->ioniceLevel() != -1) tooltip += xi18nc("@info:tooltip", "I/O Nice level: %1 (%2)", process->ioniceLevel(), process->ioniceLevelAsString()); tooltip += xi18nc("@info:tooltip", "I/O Class: %1", process->ioPriorityClassAsString()); } if(tracer.isEmpty()) return tooltip; return QString(tooltip + QStringLiteral("
") + tracer); } case HeadingCPUUsage: case HeadingCPUTime: { int divideby = (d->mNormalizeCPUUsage?d->mNumProcessorCores:1); QString tooltip = xi18nc("@info:tooltip", "Process status: %1 %2" "User CPU usage: %3%" "System CPU usage: %4%", /* Please do not add here - the tooltip is appended to */ process->translatedStatus(), d->getStatusDescription(process->status()), (float)(process->userUsage()) / divideby, (float)(process->sysUsage()) / divideby); if(process->numThreads() >= 1) tooltip += xi18nc("@info:tooltip", "Number of threads: %1", process->numThreads()); if(process->numChildren() > 0) { tooltip += xi18nc("@info:tooltip", "Number of children: %1" "Total User CPU usage: %2%" "Total System CPU usage: %3%" "Total CPU usage: %4%", process->numChildren(), (float)(process->totalUserUsage())/ divideby, (float)(process->totalSysUsage()) / divideby, (float)(process->totalUserUsage() + process->totalSysUsage()) / divideby); } if(process->userTime() > 0) tooltip += xi18nc("@info:tooltip", "CPU time spent running as user: %1 seconds", QString::number(process->userTime() / 100.0, 'f', 1)); if(process->sysTime() > 0) tooltip += xi18nc("@info:tooltip", "CPU time spent running in kernel: %1 seconds", QString::number(process->sysTime() / 100.0, 'f', 1)); if(process->niceLevel() != 0) tooltip += xi18nc("@info:tooltip", "Nice level: %1 (%2)", process->niceLevel(), process->niceLevelAsString() ); if(process->ioPriorityClass() != KSysGuard::Process::None) { if((process->ioPriorityClass() == KSysGuard::Process::RealTime || process->ioPriorityClass() == KSysGuard::Process::BestEffort) && process->ioniceLevel() != -1) tooltip += xi18nc("@info:tooltip", "I/O Nice level: %1 (%2)", process->ioniceLevel(), process->ioniceLevelAsString() ); tooltip += xi18nc("@info:tooltip", "I/O Class: %1", process->ioPriorityClassAsString() ); } if(!tracer.isEmpty()) return QString(tooltip + QStringLiteral("
") + tracer); return tooltip; } case HeadingVmSize: { return QVariant(); } case HeadingMemory: { QString tooltip; if(process->vmURSS() != -1) { //We don't have information about the URSS, so just fallback to RSS if(d->mMemTotal > 0) tooltip += xi18nc("@info:tooltip", "Memory usage: %1 out of %2 (%3 %)", format.formatByteSize(process->vmURSS() * 1024), format.formatByteSize(d->mMemTotal * 1024), process->vmURSS() * 100 / d->mMemTotal); else tooltip += xi18nc("@info:tooltip", "Memory usage: %1
", format.formatByteSize(process->vmURSS() * 1024)); } if(d->mMemTotal > 0) tooltip += xi18nc("@info:tooltip", "RSS Memory usage: %1 out of %2 (%3 %)", format.formatByteSize(process->vmRSS() * 1024), format.formatByteSize(d->mMemTotal * 1024), process->vmRSS() * 100 / d->mMemTotal); else tooltip += xi18nc("@info:tooltip", "RSS Memory usage: %1", format.formatByteSize(process->vmRSS() * 1024)); return tooltip; } case HeadingSharedMemory: { if(process->vmURSS() == -1) { return xi18nc("@info:tooltip", "Your system does not seem to have this information available to be read."); } if(d->mMemTotal >0) return xi18nc("@info:tooltip", "Shared library memory usage: %1 out of %2 (%3 %)", format.formatByteSize((process->vmRSS() - process->vmURSS()) * 1024), format.formatByteSize(d->mMemTotal * 1024), (process->vmRSS() - process->vmURSS()) * 100 / d->mMemTotal); else return xi18nc("@info:tooltip", "Shared library memory usage: %1", format.formatByteSize((process->vmRSS() - process->vmURSS()) * 1024)); } case HeadingIoWrite: case HeadingIoRead: { //FIXME - use the formatByteRate functions when added return kxi18nc("@info:tooltip", "Characters read: %1 (%2 KiB/s)" "Characters written: %3 (%4 KiB/s)" "Read syscalls: %5 (%6 s⁻¹)" "Write syscalls: %7 (%8 s⁻¹)" "Actual bytes read: %9 (%10 KiB/s)" "Actual bytes written: %11 (%12 KiB/s)") .subs(format.formatByteSize(process->ioCharactersRead())) .subs(QString::number(process->ioCharactersReadRate() / 1024)) .subs(format.formatByteSize(process->ioCharactersWritten())) .subs(QString::number(process->ioCharactersWrittenRate() / 1024)) .subs(QString::number(process->ioReadSyscalls())) .subs(QString::number(process->ioReadSyscallsRate())) .subs(QString::number(process->ioWriteSyscalls())) .subs(QString::number(process->ioWriteSyscallsRate())) .subs(format.formatByteSize(process->ioCharactersActuallyRead())) .subs(QString::number(process->ioCharactersActuallyReadRate() / 1024)) .subs(format.formatByteSize(process->ioCharactersActuallyWritten())) .subs(QString::number(process->ioCharactersActuallyWrittenRate() / 1024)) .toString(); } case HeadingXTitle: { #if HAVE_X11 const auto values = d->mPidToWindowInfo.values(process->pid()); if (values.count() == 1) { return values.first()->name; } QString tooltip; for (const auto &value : values) { if (!tooltip.isEmpty()) { tooltip += QLatin1Char('\n'); } tooltip += QStringLiteral("• ") + value->name; } return tooltip; #endif return QVariant(QVariant::String); } default: return QVariant(QVariant::String); } } case Qt::TextAlignmentRole: switch(index.column() ) { case HeadingUser: case HeadingCPUUsage: return QVariant(Qt::AlignCenter); case HeadingNiceness: case HeadingCPUTime: case HeadingStartTime: + case HeadingNoNewPrivileges: case HeadingPid: case HeadingMemory: case HeadingXMemory: case HeadingSharedMemory: case HeadingVmSize: case HeadingIoWrite: case HeadingIoRead: return QVariant(Qt::AlignRight | Qt::AlignVCenter); default: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); } case UidRole: { if(index.column() != 0) return QVariant(); //If we query with this role, then we want the raw UID for this. KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); return process->uid(); } case PlainValueRole: //Used to return a plain value. For copying to a clipboard etc { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); switch(index.column()) { case HeadingName: return process->name(); case HeadingPid: return (qlonglong)process->pid(); case HeadingUser: if(!process->login().isEmpty()) return process->login(); if(process->uid() == process->euid()) return d->getUsernameForUser(process->uid(), false); else return QString(d->getUsernameForUser(process->uid(), false) + QStringLiteral(", ") + d->getUsernameForUser(process->euid(), false)); case HeadingNiceness: return process->niceLevel(); case HeadingTty: return process->tty(); case HeadingCPUUsage: { double total; if(d->mShowChildTotals && !d->mSimple) total = process->totalUserUsage() + process->totalSysUsage(); else total = process->userUsage() + process->sysUsage(); if(d->mNormalizeCPUUsage) return total / d->mNumProcessorCores; else return total; } case HeadingCPUTime: return (qlonglong)(process->userTime() + process->sysTime()); case HeadingMemory: if(process->vmRSS() == 0) return QVariant(QVariant::String); if(process->vmURSS() == -1) { return (qlonglong)process->vmRSS(); } else { return (qlonglong)process->vmURSS(); } case HeadingVmSize: return (qlonglong)process->vmSize(); case HeadingSharedMemory: if(process->vmRSS() - process->vmURSS() < 0 || process->vmURSS() == -1) return QVariant(QVariant::String); return (qlonglong)(process->vmRSS() - process->vmURSS()); case HeadingStartTime: return process->startTime(); // 2015-01-03, gregormi: can maybe be replaced with something better later + case HeadingNoNewPrivileges: + return process->noNewPrivileges(); case HeadingCommand: return process->command(); case HeadingIoRead: switch(d->mIoInformation) { case ProcessModel::Bytes: return process->ioCharactersRead(); case ProcessModel::Syscalls: return process->ioReadSyscalls(); case ProcessModel::ActualBytes: return process->ioCharactersActuallyRead(); case ProcessModel::BytesRate: return (qlonglong)process->ioCharactersReadRate(); case ProcessModel::SyscallsRate: return (qlonglong)process->ioReadSyscallsRate(); case ProcessModel::ActualBytesRate: return (qlonglong)process->ioCharactersActuallyReadRate(); } return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through case HeadingIoWrite: switch(d->mIoInformation) { case ProcessModel::Bytes: return process->ioCharactersWritten(); case ProcessModel::Syscalls: return process->ioWriteSyscalls(); case ProcessModel::ActualBytes: return process->ioCharactersActuallyWritten(); case ProcessModel::BytesRate: return (qlonglong)process->ioCharactersWrittenRate(); case ProcessModel::SyscallsRate: return (qlonglong)process->ioWriteSyscallsRate(); case ProcessModel::ActualBytesRate: return (qlonglong)process->ioCharactersActuallyWrittenRate(); } return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through case HeadingXMemory: return (qulonglong)process->pixmapBytes(); #if HAVE_X11 case HeadingXTitle: { WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); if(!w) return QString(); return w->name; } #endif default: return QVariant(); } break; } #if HAVE_X11 case WindowIdRole: { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); if(!w) return QVariant(); else return (int)w->wid; } #endif case PercentageRole: { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); Q_CHECK_PTR(process); switch(index.column()) { case HeadingCPUUsage: { float cpu; if(d->mSimple || !d->mShowChildTotals) cpu = process->userUsage() + process->sysUsage(); else cpu = process->totalUserUsage() + process->totalSysUsage(); cpu = cpu / 100.0; if(!d->mNormalizeCPUUsage) return cpu; return cpu / d->mNumProcessorCores; } case HeadingMemory: if(d->mMemTotal <= 0) return -1; if(process->vmURSS() != -1) return float(process->vmURSS()) / d->mMemTotal; else return float(process->vmRSS()) / d->mMemTotal; case HeadingSharedMemory: if(process->vmURSS() == -1 || d->mMemTotal <= 0) return -1; return float(process->vmRSS() - process->vmURSS())/d->mMemTotal; default: return -1; } } case PercentageHistoryRole: { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); Q_CHECK_PTR(process); switch(index.column()) { case HeadingCPUUsage: { auto it = d->mMapProcessCPUHistory.find(process); if (it == d->mMapProcessCPUHistory.end()) { it = d->mMapProcessCPUHistory.insert(process, {}); it->reserve(ProcessModelPrivate::MAX_HIST_ENTRIES); } return QVariant::fromValue(*it); } default: {} } return QVariant::fromValue(QVector{}); } case Qt::DecorationRole: { if(index.column() == HeadingName) { #if HAVE_X11 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(!process->hasManagedGuiWindow()) { if(d->mSimple) //When not in tree mode, we need to pad the name column where we do not have an icon return QIcon(d->mBlankPixmap); else //When in tree mode, the padding looks bad, so do not pad in this case return QVariant(); } WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); if(w && !w->icon.isNull()) return w->icon; return QIcon(d->mBlankPixmap); #else return QVariant(); #endif } else if (index.column() == HeadingCPUUsage) { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(process->status() == KSysGuard::Process::Stopped || process->status() == KSysGuard::Process::Zombie) { // QPixmap pix = KIconLoader::global()->loadIcon("button_cancel", KIconLoader::Small, // KIconLoader::SizeSmall, KIconLoader::DefaultState, QStringList(), // 0L, true); } } return QVariant(); } case Qt::BackgroundRole: { if (index.column() != HeadingUser) { if (!d->mHaveTimer) //If there is no timer, then no processes are being killed, so no point looking for one return QVariant(); KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if (!process->timeKillWasSent().isNull()) { int elapsed = process->timeKillWasSent().elapsed(); if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) {//Only show red for about 7 seconds int transparency = 255 - elapsed*250/MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS; KColorScheme scheme(QPalette::Active, KColorScheme::Selection); QBrush brush = scheme.background(KColorScheme::NegativeBackground); QColor color = brush.color(); color.setAlpha(transparency); brush.setColor(color); return brush; } } return QVariant(); } KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(process->status() == KSysGuard::Process::Ended) { return QColor(Qt::lightGray); } if(process->tracerpid() >= 0) { //It's being debugged, so probably important. Let's mark it as such return QColor(Qt::yellow); } if(d->mIsLocalhost && process->uid() == getuid()) { //own user return QColor(0, 208, 214, 50); } if(process->uid() < 100 || !canUserLogin(process->uid())) return QColor(218, 220,215, 50); //no color for system tasks //other users return QColor(2, 154, 54, 50); } case Qt::FontRole: { if(index.column() == HeadingCPUUsage) { KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(process->userUsage() == 0) { QFont font; font.setItalic(true); return font; } } return QVariant(); } default: //This is a very very common case, so the route to this must be very minimal return QVariant(); } return QVariant(); //never get here, but make compiler happy } bool ProcessModel::hasGUIWindow(qlonglong pid) const { #if HAVE_X11 return d->mPidToWindowInfo.contains(pid); #else return false; #endif } bool ProcessModel::isLocalhost() const { return d->mIsLocalhost; } void ProcessModel::setupHeader() { //These must be in the same order that they are in the header file QStringList headings; headings << i18nc("process heading", "Name"); headings << i18nc("process heading", "Username"); headings << i18nc("process heading", "PID"); headings << i18nc("process heading", "TTY"); headings << i18nc("process heading", "Niceness"); // xgettext: no-c-format headings << i18nc("process heading", "CPU %"); headings << i18nc("process heading", "CPU Time"); headings << i18nc("process heading", "IO Read"); headings << i18nc("process heading", "IO Write"); headings << i18nc("process heading", "Virtual Size"); headings << i18nc("process heading", "Memory"); headings << i18nc("process heading", "Shared Mem"); headings << i18nc("process heading", "Relative Start Time"); + headings << i18nc("process heading", "NNP"); headings << i18nc("process heading", "Command"); #if HAVE_X11 if (d->mIsX11) { headings << i18nc("process heading", "X11 Memory"); headings << i18nc("process heading", "Window Title"); } #endif if(d->mHeadings.isEmpty()) { // If it's empty, this is the first time this has been called, so insert the headings beginInsertColumns(QModelIndex(), 0, headings.count()-1); { d->mHeadings = headings; } endInsertColumns(); } else { // This was called to retranslate the headings. Just use the new translations and call headerDataChanged Q_ASSERT(d->mHeadings.count() == headings.count()); d->mHeadings = headings; headerDataChanged(Qt::Horizontal, 0 , headings.count()-1); } } void ProcessModel::retranslateUi() { setupHeader(); } KSysGuard::Process *ProcessModel::getProcess(qlonglong pid) { return d->mProcesses->getProcess(pid); } bool ProcessModel::showTotals() const { return d->mShowChildTotals; } void ProcessModel::setShowTotals(bool showTotals) //slot { if(showTotals == d->mShowChildTotals) return; d->mShowChildTotals = showTotals; QModelIndex index; foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { if(process->numChildren() > 0) { int row; if(d->mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); index = createIndex(row, HeadingCPUUsage, process); emit dataChanged(index, index); } } } qlonglong ProcessModel::totalMemory() const { return d->mMemTotal; } void ProcessModel::setUnits(Units units) { if(d->mUnits == units) return; d->mUnits = units; QModelIndex index; foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { int row; if(d->mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); index = createIndex(row, HeadingMemory, process); emit dataChanged(index, index); index = createIndex(row, HeadingXMemory, process); emit dataChanged(index, index); index = createIndex(row, HeadingSharedMemory, process); emit dataChanged(index, index); index = createIndex(row, HeadingVmSize, process); emit dataChanged(index, index); } } ProcessModel::Units ProcessModel::units() const { return (Units) d->mUnits; } void ProcessModel::setIoUnits(Units units) { if(d->mIoUnits == units) return; d->mIoUnits = units; QModelIndex index; foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { int row; if(d->mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); index = createIndex(row, HeadingIoRead, process); emit dataChanged(index, index); index = createIndex(row, HeadingIoWrite, process); emit dataChanged(index, index); } } ProcessModel::Units ProcessModel::ioUnits() const { return (Units) d->mIoUnits; } void ProcessModel::setIoInformation( ProcessModel::IoInformation ioInformation ) { d->mIoInformation = ioInformation; } ProcessModel::IoInformation ProcessModel::ioInformation() const { return d->mIoInformation; } QString ProcessModel::formatMemoryInfo(qlonglong amountInKB, Units units, bool returnEmptyIfValueIsZero) const { //We cache the result of i18n for speed reasons. We call this function //hundreds of times, every second or so if(returnEmptyIfValueIsZero && amountInKB == 0) return QString(); static QString percentageString = i18n("%1%", QString::fromLatin1("%1")); if (units == UnitsPercentage) { if(d->mMemTotal == 0) return QLatin1String(""); //memory total not determined yet. Shouldn't happen, but don't crash if it does float percentage = amountInKB*100.0/d->mMemTotal; if(percentage < 0.1) percentage = 0.1; return percentageString.arg(percentage, 0, 'f', 1); } else return formatByteSize(amountInKB, units); } QString ProcessModel::hostName() const { return d->mHostName; } QStringList ProcessModel::mimeTypes() const { QStringList types; types << QStringLiteral("text/plain"); types << QStringLiteral("text/csv"); types << QStringLiteral("text/html"); return types; } QMimeData *ProcessModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QString textCsv; QString textCsvHeaders; QString textPlain; QString textPlainHeaders; QString textHtml; QString textHtmlHeaders; QString display; int firstColumn = -1; bool firstrow = true; foreach (const QModelIndex &index, indexes) { if (index.isValid()) { if(firstColumn == -1) firstColumn = index.column(); else if(firstColumn != index.column()) continue; else { textCsv += QLatin1Char('\n'); textPlain += QLatin1Char('\n'); textHtml += QLatin1String("
") + heading + QStringLiteral("") + display.toHtmlEscaped() + QStringLiteral("
") + textHtmlHeaders + QStringLiteral("") + textHtml + QStringLiteral("
"); textCsv = textCsvHeaders + QLatin1Char('\n') + textCsv; textPlain = textPlainHeaders + QLatin1Char('\n') + textPlain; mimeData->setText(textPlain); mimeData->setHtml(textHtml); mimeData->setData(QStringLiteral("text/csv"), textCsv.toUtf8()); return mimeData; } Qt::ItemFlags ProcessModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; //Would this ever happen? KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer()); if(process->status() == KSysGuard::Process::Ended) return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable; else return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled; } bool ProcessModel::isShowCommandLineOptions() const { return d->mShowCommandLineOptions; } void ProcessModel::setShowCommandLineOptions(bool showCommandLineOptions) { d->mShowCommandLineOptions = showCommandLineOptions; } bool ProcessModel::isShowingTooltips() const { return d->mShowingTooltips; } void ProcessModel::setShowingTooltips(bool showTooltips) { d->mShowingTooltips = showTooltips; } bool ProcessModel::isNormalizedCPUUsage() const { return d->mNormalizeCPUUsage; } void ProcessModel::setNormalizedCPUUsage(bool normalizeCPUUsage) { d->mNormalizeCPUUsage = normalizeCPUUsage; } void ProcessModelPrivate::timerEvent( QTimerEvent * event ) { Q_UNUSED(event); foreach (qlonglong pid, mPidsToUpdate) { KSysGuard::Process *process = mProcesses->getProcess(pid); if (process && !process->timeKillWasSent().isNull() && process->timeKillWasSent().elapsed() < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) { int row; if(mSimple) row = process->index(); else row = process->parent()->children().indexOf(process); QModelIndex index1 = q->createIndex(row, 0, process); QModelIndex index2 = q->createIndex(row, mHeadings.count()-1, process); emit q->dataChanged(index1, index2); } else { mPidsToUpdate.removeAll(pid); } } if (mPidsToUpdate.isEmpty()) { mHaveTimer = false; killTimer(mTimerId); mTimerId = -1; } } diff --git a/processui/ProcessModel.h b/processui/ProcessModel.h index 624076d..00205b8 100644 --- a/processui/ProcessModel.h +++ b/processui/ProcessModel.h @@ -1,214 +1,215 @@ /* KSysGuard, the KDE System Guard Copyright (c) 1999, 2000 Chris Schlaeger Copyright (c) 2006 John Tapsell This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PROCESSMODEL_H_ #define PROCESSMODEL_H_ #include #include namespace KSysGuard { class Processes; class Process; } class ProcessModelPrivate; #ifdef Q_OS_WIN // this workaround is needed to make krunner link under msvc // please keep it this way even if you port this library to have a _export.h header file #define KSYSGUARD_EXPORT #else #define KSYSGUARD_EXPORT Q_DECL_EXPORT #endif class KSYSGUARD_EXPORT ProcessModel : public QAbstractItemModel { Q_OBJECT Q_ENUMS(Units) public: /** Storage for history values. PercentageHistoryRole returns a QVector of this. */ struct PercentageHistoryEntry { unsigned long timestamp; // in ms, origin undefined as only the delta matters float value; }; explicit ProcessModel(QObject* parent = nullptr, const QString &host = QString() ); ~ProcessModel() override; /* Functions for our Model for QAbstractItemModel*/ int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount ( const QModelIndex & parent = QModelIndex() ) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QModelIndex index ( int row, int column, const QModelIndex & parent = QModelIndex() ) const override; QModelIndex parent ( const QModelIndex & index ) const override; bool hasChildren ( const QModelIndex & parent) const override; /** Returns if (left < right), used by the sort-filter proxy model to sort the columns */ bool lessThan( const QModelIndex & left, const QModelIndex & right) const; /* Functions for drag and drop and copying to clipboard, inherited from QAbstractItemModel */ QStringList mimeTypes() const override; QMimeData *mimeData(const QModelIndexList &indexes) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; /* Functions for setting the model */ /** Setup the column headings by inserting the appropriate headings into the model. * Can be called more than once to retranslate the headings if the system language changes. */ void setupHeader(); /** Update data. You can pass in the time between updates to only update if there hasn't * been an update within the last @p updateDurationMSecs milliseconds. 0 indicate to update * regardless of when the last update was. * The updateFlags indicates what to additional update, as well as the usual details. */ void update(long updateDurationMSecs = 0, KSysGuard::Processes::UpdateFlags updateFlags = KSysGuard::Processes::IOStatistics); /** Return a string with the pid of the process and the name of the process. E.g. 13343: ksysguard */ QString getStringForProcess(KSysGuard::Process *process) const; KSysGuard::Process *getProcess(qlonglong pid); /** This is used from ProcessFilter to get the process at a given index when in flat mode */ KSysGuard::Process *getProcessAtIndex(int index) const; /** Returns whether this user can log in or not. * @see mUidCanLogin */ bool canUserLogin(long uid) const; /** In simple mode, everything is flat, with no icons, few if any colors, no xres etc. * This can be changed at any time. It is a fairly quick operation. Basically it resets the model */ void setSimpleMode(bool simple); /** In simple mode, everything is flat, with no icons, few if any colors, no xres etc */ bool isSimpleMode() const; /** Returns the total amount of physical memory in the machine. */ qlonglong totalMemory() const; /** This returns a QModelIndex for the given process. It has to look up the parent for this pid, find the offset this * pid is from the parent, and return that. It's not that slow, but does involve a couple of hash table lookups. */ QModelIndex getQModelIndex ( KSysGuard::Process *process, int column) const; /** Whether this is showing the processes for the current machine */ bool isLocalhost() const; /** The host name that this widget is showing the processes of */ QString hostName() const; /** Whether this process has a GUI window */ bool hasGUIWindow(qlonglong pid) const; /** Returns for process controller pointer for this model */ KSysGuard::Processes *processController() const; //The processes instance /** Convenience function to get the number of processes. * * Equivalent to processController->processCount() */ int processCount() const { return processController()->processCount(); } /** The headings in the model. The order here is the order that they are shown * in. If you change this, make sure you also change the * setup header function, and make sure you increase PROCESSHEADERVERSION. This will ensure * that old saved settings won't be used */ -#define PROCESSHEADERVERSION 6 +#define PROCESSHEADERVERSION 7 enum { HeadingName=0, HeadingUser, HeadingPid, HeadingTty, HeadingNiceness, HeadingCPUUsage, HeadingCPUTime, HeadingIoRead, HeadingIoWrite, HeadingVmSize, HeadingMemory, HeadingSharedMemory, HeadingStartTime, + HeadingNoNewPrivileges, HeadingCommand, HeadingXMemory, HeadingXTitle }; enum { UidRole = Qt::UserRole, SortingValueRole, WindowIdRole, PlainValueRole, PercentageRole, PercentageHistoryRole }; bool showTotals() const; /** When displaying memory sizes, this is the units it should be displayed in */ enum Units { UnitsAuto, UnitsKB, UnitsMB, UnitsGB, UnitsTB, UnitsPB, UnitsPercentage }; /** Set the units memory sizes etc should be displayed in */ void setUnits(Units units); /** The units memory sizes etc should be displayed in */ Units units() const; /** Set the I/O units sizes etc should be displayed in */ void setIoUnits(Units units); /** The units I/O sizes etc should be displayed in */ Units ioUnits() const; enum IoInformation { Bytes, Syscalls, ActualBytes, BytesRate, SyscallsRate, ActualBytesRate }; /** Set the information to show in the Io Read and Io Write columns */ void setIoInformation( IoInformation ioInformation ); /** The information to show in the Io Read and Io Write columns */ IoInformation ioInformation() const; /** Take an amount in kb, and return a string in the units set by setUnits() */ QString formatMemoryInfo(qlonglong amountInKB, Units units, bool returnEmptyIfValueIsZero = false) const; /** Whether to show the command line options in the process name column */ bool isShowCommandLineOptions() const; /** Set whether to show the command line options in the process name column */ void setShowCommandLineOptions(bool showCommandLineOptions); /** Whether to show tooltips when the mouse hovers over a process */ bool isShowingTooltips() const; /** Set whether to show tooltips when the mouse hovers over a process */ void setShowingTooltips(bool showTooltips); /** Whether to divide CPU usage by the number of CPUs */ bool isNormalizedCPUUsage() const; /** Set whether to divide CPU usage by the number of CPUs */ void setNormalizedCPUUsage(bool normalizeCPUUsage); /** Retranslate the GUI, for when the system language changes */ void retranslateUi(); public Q_SLOTS: /** Whether to show the total cpu for the process plus all of its children */ void setShowTotals(bool showTotals); private: ProcessModelPrivate* const d; friend class ProcessModelPrivate; }; Q_DECLARE_METATYPE(QVector); Q_DECLARE_TYPEINFO(ProcessModel::PercentageHistoryEntry, Q_PRIMITIVE_TYPE); #endif