diff --git a/processcore/processes.h b/processcore/processes.h --- a/processcore/processes.h +++ b/processcore/processes.h @@ -199,7 +199,11 @@ QDateTime viewingTime() const; bool loadHistoryFile(const QString &filename); QString historyFileName() const; - + + /** Show threads */ + void setShowingThreads(bool showThreads); + bool canShowThreads() const; + public Q_SLOTS: /** The abstract processes has updated its list of processes */ void processesUpdated(); diff --git a/processcore/processes.cpp b/processcore/processes.cpp --- a/processcore/processes.cpp +++ b/processcore/processes.cpp @@ -539,6 +539,16 @@ return d->mHistoricProcesses->isHistoryAvailable(); } +bool Processes::canShowThreads() const +{ + return d->mAbstractProcesses->canShowThreads(); +} + +void Processes::setShowingThreads(bool showThreads) +{ + return d->mAbstractProcesses->setShowingThreads(showThreads); +} + } diff --git a/processcore/processes_atop_p.h b/processcore/processes_atop_p.h --- a/processcore/processes_atop_p.h +++ b/processcore/processes_atop_p.h @@ -49,6 +49,8 @@ long long totalPhysicalMemory() override; bool setIoNiceness(long pid, int priorityClass, int priority) override; bool supportsIoNiceness() override; + bool canShowThreads() override; + void setShowingThreads(bool) override; long numberProcessorCores() override #ifdef _SC_NPROCESSORS_ONLN { return sysconf(_SC_NPROCESSORS_ONLN); } // Should work on any recent posix system diff --git a/processcore/processes_atop_p.cpp b/processcore/processes_atop_p.cpp --- a/processcore/processes_atop_p.cpp +++ b/processcore/processes_atop_p.cpp @@ -371,6 +371,15 @@ return 0; } +bool ProcessesATop::canShowThreads() +{ + return false; +} + +void ProcessesATop::setShowingThreads(bool) +{ +} + ProcessesATop::~ProcessesATop() { delete d; diff --git a/processcore/processes_base_p.h b/processcore/processes_base_p.h --- a/processcore/processes_base_p.h +++ b/processcore/processes_base_p.h @@ -139,6 +139,13 @@ */ virtual void updateAllProcesses( Processes::UpdateFlags updateFlags ) = 0; + /** \brief Show threads as if they were processes. + */ + virtual void setShowingThreads( bool ) = 0; + /** \brief Return true if we can show threads as if they were processes. + */ + virtual bool canShowThreads() = 0; + Processes::Error errorCode; Q_SIGNALS: /** \brief This is emitted when the processes have been updated, and the view should be refreshed. diff --git a/processcore/processes_dragonfly_p.cpp b/processcore/processes_dragonfly_p.cpp --- a/processcore/processes_dragonfly_p.cpp +++ b/processcore/processes_dragonfly_p.cpp @@ -266,6 +266,15 @@ return Total; } +bool ProcessesLocal::canShowThreads() +{ + return false; +} + +void ProcessesLocal::setShowingThreads(bool) +{ +} + ProcessesLocal::~ProcessesLocal() { delete d; diff --git a/processcore/processes_freebsd_p.cpp b/processcore/processes_freebsd_p.cpp --- a/processcore/processes_freebsd_p.cpp +++ b/processcore/processes_freebsd_p.cpp @@ -262,6 +262,15 @@ return Total /= 1024; } +bool ProcessesLocal::canShowThreads() +{ + return false; +} + +void ProcessesLocal::setShowingThreads(bool) +{ +} + ProcessesLocal::~ProcessesLocal() { delete d; diff --git a/processcore/processes_gnu_p.cpp b/processcore/processes_gnu_p.cpp --- a/processcore/processes_gnu_p.cpp +++ b/processcore/processes_gnu_p.cpp @@ -89,6 +89,15 @@ return false; } +bool ProcessesLocal::canShowThreads() +{ + return false; +} + +void ProcessesLocal::setShowingThreads(bool) +{ +} + long long ProcessesLocal::totalPhysicalMemory() { long long memory = 0; diff --git a/processcore/processes_linux_p.cpp b/processcore/processes_linux_p.cpp --- a/processcore/processes_linux_p.cpp +++ b/processcore/processes_linux_p.cpp @@ -113,11 +113,14 @@ inline bool readProcStatm(const QString &dir, Process *process); inline bool readProcCmdline(const QString &dir, Process *process); inline bool readProcCGroup(const QString &dir, Process *process); + inline long readTgid(long pid); + inline long readPpid(long pid); 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; + bool mShowingThreads; }; ProcessesLocal::Private::~Private() @@ -217,21 +220,39 @@ 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)) +long ProcessesLocal::Private::readTgid(long pid) +{ + long tgid = -1; + mFile.setFileName(QStringLiteral("/proc/") + QString::number(pid) + QStringLiteral("/status")); + if(!mFile.open(QIODevice::ReadOnly)) + return false; /* process has terminated in the meantime */ + + int size; + while( (size = mFile.readLine( mBuffer, sizeof(mBuffer))) > 0) { //-1 indicates an error + switch( mBuffer[0]) { + case 'T': + if((unsigned int)size > sizeof("Tgid:") && qstrncmp(mBuffer, "Tgid:", sizeof("Tgid:")-1) == 0) + tgid = atol(mBuffer + sizeof("Tgid:")-1); + } + } + mFile.close(); + return tgid; +} + +long ProcessesLocal::Private::readPpid(long pid) +{ + mFile.setFileName(QStringLiteral("/proc/") + QString::number(pid) + QStringLiteral("/stat")); + if(!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(); + if( (size = mFile.readLine( mBuffer, sizeof(mBuffer))) <= 0) { //-1 indicates nothing read + mFile.close(); return -1; } - d->mFile.close(); - char *word = d->mBuffer; + 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, ')'); @@ -249,8 +270,24 @@ } word++; } - long ppid = atol(++word); - if (ppid == 0) + return atol(++word); +} + +long ProcessesLocal::getParentPid(long pid) { + long ppid; + if (pid <= 0) + return -1; + + if (d->mShowingThreads) { + long tgid = d->readTgid(pid); // Thread group ID + if (tgid == pid) // Thread group leader: show real PPID + ppid = d->readPpid(pid); + else + ppid = tgid; // Non-leader thread: show TGID as PPID + } else + ppid = d->readPpid(pid); + + if (ppid <= 0) return -1; return ppid; } @@ -548,15 +585,38 @@ return success; } +static void getPidsFromDir(DIR *dir, QSet *pids) +{ + struct dirent* entry; + while ( ( entry = readdir( dir ) ) ) + if ( entry->d_name[ 0 ] >= '0' && entry->d_name[ 0 ] <= '9' ) + pids->insert(atol( entry->d_name )); +} + QSet ProcessesLocal::getAllPids( ) { QSet pids; - if(d->mProcDir==nullptr) return pids; //There's not much we can do without /proc struct dirent* entry; + if(d->mProcDir==nullptr) return pids; //There's not much we can do without /proc 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 )); + if(d->mShowingThreads) { + while ( ( entry = readdir( d->mProcDir ) ) ) + if ( entry->d_name[ 0 ] >= '0' && entry->d_name[ 0 ] <= '9' ) { + char *s; + int chars; + chars = asprintf(&s, "/proc/%s/task/", entry->d_name); + if (chars == -1) + return pids; + DIR *dir = opendir(s); + if (dir) { + getPidsFromDir(dir, &pids); + closedir(dir); + } + free(s); + } + } else { + getPidsFromDir(d->mProcDir, &pids); + } return pids; } @@ -733,6 +793,17 @@ return 0; // Not found. Probably will never happen #endif } + +bool ProcessesLocal::canShowThreads() +{ + return true; +} + +void ProcessesLocal::setShowingThreads(bool showThreads) +{ + d->mShowingThreads = showThreads; +} + ProcessesLocal::~ProcessesLocal() { delete d; diff --git a/processcore/processes_local_p.h b/processcore/processes_local_p.h --- a/processcore/processes_local_p.h +++ b/processcore/processes_local_p.h @@ -48,6 +48,8 @@ long long totalPhysicalMemory() override; bool setIoNiceness(long pid, int priorityClass, int priority) override; bool supportsIoNiceness() override; + bool canShowThreads() override; + void setShowingThreads(bool) override; long numberProcessorCores() override #ifdef _SC_NPROCESSORS_ONLN { return sysconf(_SC_NPROCESSORS_ONLN); } // Should work on any recent posix system diff --git a/processcore/processes_netbsd_p.cpp b/processcore/processes_netbsd_p.cpp --- a/processcore/processes_netbsd_p.cpp +++ b/processcore/processes_netbsd_p.cpp @@ -284,6 +284,15 @@ return Total /= 1024; } +bool ProcessesLocal::canShowThreads() +{ + return false; +} + +void ProcessesLocal::setShowingThreads(bool) +{ +} + ProcessesLocal::~ProcessesLocal() { delete d; diff --git a/processcore/processes_openbsd_p.cpp b/processcore/processes_openbsd_p.cpp --- a/processcore/processes_openbsd_p.cpp +++ b/processcore/processes_openbsd_p.cpp @@ -298,6 +298,16 @@ return 1; return len; } + +bool ProcessesLocal::canShowThreads() +{ + return false; +} + +void setShowingThreads() +{ +} + ProcessesLocal::~ProcessesLocal() { delete d; diff --git a/processcore/processes_remote_p.h b/processcore/processes_remote_p.h --- a/processcore/processes_remote_p.h +++ b/processcore/processes_remote_p.h @@ -45,6 +45,8 @@ bool supportsIoNiceness() override; long numberProcessorCores() override; void updateAllProcesses( Processes::UpdateFlags updateFlags ) override; + bool canShowThreads() override; + void setShowingThreads(bool) override; Q_SIGNALS: diff --git a/processcore/processes_remote_p.cpp b/processcore/processes_remote_p.cpp --- a/processcore/processes_remote_p.cpp +++ b/processcore/processes_remote_p.cpp @@ -270,6 +270,15 @@ } +bool ProcessesRemote::canShowThreads() +{ + return false; +} + +void ProcessesRemote::setShowingThreads(bool) +{ +} + ProcessesRemote::~ProcessesRemote() { delete d; diff --git a/processcore/processes_solaris_p.cpp b/processcore/processes_solaris_p.cpp --- a/processcore/processes_solaris_p.cpp +++ b/processcore/processes_solaris_p.cpp @@ -236,6 +236,15 @@ return 0; } +bool ProcessesLocal::canShowThreads() +{ + return false; +} + +void ProcessesLocal::setShowingThreads(bool) +{ +} + ProcessesLocal::~ProcessesLocal() { delete d; diff --git a/processui/ProcessModel.h b/processui/ProcessModel.h --- a/processui/ProcessModel.h +++ b/processui/ProcessModel.h @@ -195,6 +195,12 @@ bool isNormalizedCPUUsage() const; /** Set whether to divide CPU usage by the number of CPUs */ void setNormalizedCPUUsage(bool normalizeCPUUsage); + /** Whether to show threads as if they were processes */ + bool isShowingThreads() const; + /** Set whether to show threads as if they were processes */ + void setShowingThreads(bool showThreads); + /** Whether we can show threads */ + bool canShowThreads() const; /** Retranslate the GUI, for when the system language changes */ void retranslateUi(); diff --git a/processui/ProcessModel.cpp b/processui/ProcessModel.cpp --- a/processui/ProcessModel.cpp +++ b/processui/ProcessModel.cpp @@ -132,6 +132,7 @@ #else mIsX11 = false; #endif + mShowingThreads = false; } ProcessModelPrivate::~ProcessModelPrivate() @@ -2228,6 +2229,22 @@ d->mNormalizeCPUUsage = normalizeCPUUsage; } +bool ProcessModel::isShowingThreads() const +{ + return d->mShowingThreads; +} + +void ProcessModel::setShowingThreads(bool showThreads) +{ + d->mShowingThreads = showThreads; + d->mProcesses->setShowingThreads(showThreads); +} + +bool ProcessModel::canShowThreads() const +{ + return d->mProcesses->canShowThreads(); +} + void ProcessModelPrivate::timerEvent( QTimerEvent * event ) { Q_UNUSED(event); diff --git a/processui/ProcessModel_p.h b/processui/ProcessModel_p.h --- a/processui/ProcessModel_p.h +++ b/processui/ProcessModel_p.h @@ -194,6 +194,7 @@ /** When displaying memory sizes, this is the units it should be displayed in */ ProcessModel::Units mUnits; ProcessModel::Units mIoUnits; + bool mShowingThreads; ProcessModel::IoInformation mIoInformation; diff --git a/processui/ksysguardprocesslist.cpp b/processui/ksysguardprocesslist.cpp --- a/processui/ksysguardprocesslist.cpp +++ b/processui/ksysguardprocesslist.cpp @@ -752,6 +752,7 @@ QAction *actionShowCmdlineOptions = nullptr; QAction *actionShowTooltips = nullptr; QAction *actionNormalizeCPUUsage = nullptr; + QAction *actionShowThreads = nullptr; QAction *actionIoCharacters = nullptr; QAction *actionIoSyscalls = nullptr; @@ -837,6 +838,13 @@ actionNormalizeCPUUsage->setCheckable(true); actionNormalizeCPUUsage->setChecked(d->mModel.isNormalizedCPUUsage()); menu.addAction(actionNormalizeCPUUsage); + } else if(index == ProcessModel::HeadingPid && d->mModel.canShowThreads()) { + menu.addSeparator(); + actionShowThreads = new QAction(&menu); + actionShowThreads->setText(i18n("Show threads as if they were processes")); + actionShowThreads->setCheckable(true); + actionShowThreads->setChecked(d->mModel.isShowingThreads()); + menu.addAction(actionShowThreads); } if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) { @@ -955,6 +963,9 @@ default: break; } + } else if(result == actionShowThreads) { + d->mModel.setShowingThreads(actionShowThreads->isChecked()); + return; } int i = result->data().toInt();