Warning, file /plasma/libksysguard/processui/ProcessModel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 KSysGuard, the KDE System Guard 0003 0004 SPDX-FileCopyrightText: 1999, 2000 Chris Schlaeger <cs@kde.org> 0005 SPDX-FileCopyrightText: 2006-2007 John Tapsell <john.tapsell@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 0009 */ 0010 0011 #include "ProcessModel.h" 0012 #include "ProcessModel_p.h" 0013 #include "timeutil.h" 0014 0015 #include "processcore/extended_process_list.h" 0016 #include "processcore/formatter.h" 0017 #include "processcore/process.h" 0018 #include "processcore/process_attribute.h" 0019 #include "processcore/process_data_provider.h" 0020 0021 #include "processui_debug.h" 0022 0023 #include <KFormat> 0024 #include <KLocalizedString> 0025 #include <QApplication> 0026 #include <QBitmap> 0027 #include <QDebug> 0028 #include <QFont> 0029 #include <QIcon> 0030 #include <QList> 0031 #include <QLocale> 0032 #include <QMimeData> 0033 #include <QPixmap> 0034 #include <QRegExp> 0035 #include <QTextDocument> 0036 #include <kcolorscheme.h> 0037 #include <kiconloader.h> 0038 0039 #define HEADING_X_ICON_SIZE 16 0040 #define MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS 2000 0041 #define GET_OWN_ID 0042 0043 #ifdef GET_OWN_ID 0044 /* For getuid*/ 0045 #include <sys/types.h> 0046 #include <unistd.h> 0047 #endif 0048 0049 #if HAVE_XRES 0050 #include <X11/extensions/XRes.h> 0051 #endif 0052 0053 #if HAVE_X11 0054 #include <KX11Extras> 0055 #endif 0056 0057 extern QApplication *Qapp; 0058 0059 static QString formatByteSize(qlonglong amountInKB, int units) 0060 { 0061 enum { UnitsAuto, UnitsKB, UnitsMB, UnitsGB, UnitsTB, UnitsPB }; 0062 static QString kString = i18n("%1 K", QString::fromLatin1("%1")); 0063 static QString mString = i18n("%1 M", QString::fromLatin1("%1")); 0064 static QString gString = i18n("%1 G", QString::fromLatin1("%1")); 0065 static QString tString = i18n("%1 T", QString::fromLatin1("%1")); 0066 static QString pString = i18n("%1 P", QString::fromLatin1("%1")); 0067 double amount; 0068 0069 if (units == UnitsAuto) { 0070 if (amountInKB < 1024.0 * 0.9) 0071 units = UnitsKB; // amount < 0.9 MiB == KiB 0072 else if (amountInKB < 1024.0 * 1024.0 * 0.9) 0073 units = UnitsMB; // amount < 0.9 GiB == MiB 0074 else if (amountInKB < 1024.0 * 1024.0 * 1024.0 * 0.9) 0075 units = UnitsGB; // amount < 0.9 TiB == GiB 0076 else if (amountInKB < 1024.0 * 1024.0 * 1024.0 * 1024.0 * 0.9) 0077 units = UnitsTB; // amount < 0.9 PiB == TiB 0078 else 0079 units = UnitsPB; 0080 } 0081 0082 switch (units) { 0083 case UnitsKB: 0084 return kString.arg(QLocale().toString(amountInKB)); 0085 case UnitsMB: 0086 amount = amountInKB / 1024.0; 0087 return mString.arg(QLocale().toString(amount, 'f', 1)); 0088 case UnitsGB: 0089 amount = amountInKB / (1024.0 * 1024.0); 0090 if (amount < 0.1 && amount > 0.05) 0091 amount = 0.1; 0092 return gString.arg(QLocale().toString(amount, 'f', 1)); 0093 case UnitsTB: 0094 amount = amountInKB / (1024.0 * 1024.0 * 1024.0); 0095 if (amount < 0.1 && amount > 0.05) 0096 amount = 0.1; 0097 return tString.arg(QLocale().toString(amount, 'f', 1)); 0098 case UnitsPB: 0099 amount = amountInKB / (1024.0 * 1024.0 * 1024.0 * 1024.0); 0100 if (amount < 0.1 && amount > 0.05) 0101 amount = 0.1; 0102 return pString.arg(QLocale().toString(amount, 'f', 1)); 0103 default: 0104 return QLatin1String(""); // error 0105 } 0106 } 0107 0108 ProcessModelPrivate::ProcessModelPrivate() 0109 : mBlankPixmap(HEADING_X_ICON_SIZE, 1) 0110 { 0111 mBlankPixmap.fill(QColor(0, 0, 0, 0)); 0112 mSimple = true; 0113 mIsLocalhost = true; 0114 mMemTotal = -1; 0115 mNumProcessorCores = 1; 0116 mProcesses = nullptr; 0117 mShowChildTotals = true; 0118 mShowCommandLineOptions = false; 0119 mShowingTooltips = true; 0120 mNormalizeCPUUsage = true; 0121 mIoInformation = ProcessModel::ActualBytes; 0122 #if HAVE_XRES 0123 mHaveXRes = false; 0124 #endif 0125 mHaveTimer = false, mTimerId = -1, mMovingRow = false; 0126 mRemovingRow = false; 0127 mInsertingRow = false; 0128 #if HAVE_X11 0129 mIsX11 = QX11Info::isPlatformX11(); 0130 #else 0131 mIsX11 = false; 0132 #endif 0133 } 0134 0135 ProcessModelPrivate::~ProcessModelPrivate() 0136 { 0137 #if HAVE_X11 0138 qDeleteAll(mPidToWindowInfo); 0139 #endif 0140 mProcesses.clear(); 0141 } 0142 0143 ProcessModel::ProcessModel(QObject *parent, const QString &host) 0144 : QAbstractItemModel(parent) 0145 , d(new ProcessModelPrivate) 0146 { 0147 d->q = this; 0148 #if HAVE_XRES 0149 if (d->mIsX11) { 0150 int event, error, major, minor; 0151 d->mHaveXRes = XResQueryExtension(QX11Info::display(), &event, &error) && XResQueryVersion(QX11Info::display(), &major, &minor); 0152 } 0153 #endif 0154 0155 if (host.isEmpty() || host == QLatin1String("localhost")) { 0156 d->mHostName = QString(); 0157 d->mIsLocalhost = true; 0158 } else { 0159 d->mHostName = host; 0160 d->mIsLocalhost = false; 0161 } 0162 setupHeader(); 0163 d->setupProcesses(); 0164 #if HAVE_X11 0165 d->setupWindows(); 0166 #endif 0167 d->mUnits = UnitsKB; 0168 d->mIoUnits = UnitsKB; 0169 } 0170 0171 bool ProcessModel::lessThan(const QModelIndex &left, const QModelIndex &right) const 0172 { 0173 // Because we need to sort Descendingly by default for most of the headings, we often return left > right 0174 KSysGuard::Process *processLeft = reinterpret_cast<KSysGuard::Process *>(left.internalPointer()); 0175 KSysGuard::Process *processRight = reinterpret_cast<KSysGuard::Process *>(right.internalPointer()); 0176 Q_ASSERT(processLeft); 0177 Q_ASSERT(processRight); 0178 Q_ASSERT(left.column() == right.column()); 0179 switch (left.column()) { 0180 case HeadingUser: { 0181 /* Sorting by user will be the default and the most common. 0182 We want to sort in the most useful way that we can. We need to return a number though. 0183 This code is based on that sorting ascendingly should put the current user at the top 0184 First the user we are running as should be at the top. 0185 Then any other users in the system. 0186 Then at the bottom the 'system' processes. 0187 We then sort by cpu usage to sort by that, then finally sort by memory usage */ 0188 0189 /* First, place traced processes at the very top, ignoring any other sorting criteria */ 0190 if (processLeft->tracerpid() >= 0) 0191 return true; 0192 if (processRight->tracerpid() >= 0) 0193 return false; 0194 0195 /* Sort by username. First group into own user, normal users, system users */ 0196 if (processLeft->uid() != processRight->uid()) { 0197 // We primarily sort by username 0198 if (d->mIsLocalhost) { 0199 int ownUid = getuid(); 0200 if (processLeft->uid() == ownUid) 0201 return true; // Left is our user, right is not. So left is above right 0202 if (processRight->uid() == ownUid) 0203 return false; // Left is not our user, right is. So right is above left 0204 } 0205 bool isLeftSystemUser = processLeft->uid() < 100 || !canUserLogin(processLeft->uid()); 0206 bool isRightSystemUser = processRight->uid() < 100 || !canUserLogin(processRight->uid()); 0207 if (isLeftSystemUser && !isRightSystemUser) 0208 return false; // System users are less than non-system users 0209 if (!isLeftSystemUser && isRightSystemUser) 0210 return true; 0211 // They are either both system users, or both non-system users. 0212 // So now sort by username 0213 return d->getUsernameForUser(processLeft->uid(), false) < d->getUsernameForUser(processRight->uid(), false); 0214 } 0215 0216 /* 2nd sort order - Graphics Windows */ 0217 // Both columns have the same user. Place processes with windows at the top 0218 if (processLeft->hasManagedGuiWindow() && !processRight->hasManagedGuiWindow()) 0219 return true; 0220 if (!processLeft->hasManagedGuiWindow() && processRight->hasManagedGuiWindow()) 0221 return false; 0222 0223 /* 3rd sort order - CPU Usage */ 0224 int leftCpu, rightCpu; 0225 if (d->mSimple || !d->mShowChildTotals) { 0226 leftCpu = processLeft->userUsage() + processLeft->sysUsage(); 0227 rightCpu = processRight->userUsage() + processRight->sysUsage(); 0228 } else { 0229 leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage(); 0230 rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage(); 0231 } 0232 if (leftCpu != rightCpu) 0233 return leftCpu > rightCpu; 0234 0235 /* 4th sort order - Memory Usage */ 0236 qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS(); 0237 qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS(); 0238 return memoryLeft > memoryRight; 0239 } 0240 case HeadingCPUUsage: { 0241 int leftCpu, rightCpu; 0242 if (d->mSimple || !d->mShowChildTotals) { 0243 leftCpu = processLeft->userUsage() + processLeft->sysUsage(); 0244 rightCpu = processRight->userUsage() + processRight->sysUsage(); 0245 } else { 0246 leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage(); 0247 rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage(); 0248 } 0249 return leftCpu > rightCpu; 0250 } 0251 case HeadingCPUTime: { 0252 return (processLeft->userTime() + processLeft->sysTime()) > (processRight->userTime() + processRight->sysTime()); 0253 } 0254 case HeadingMemory: { 0255 qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS(); 0256 qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS(); 0257 return memoryLeft > memoryRight; 0258 } 0259 case HeadingVmPSS: { 0260 return processLeft->vmPSS() > processRight->vmPSS(); 0261 } 0262 case HeadingStartTime: { 0263 return processLeft->startTime() > processRight->startTime(); 0264 } 0265 case HeadingNoNewPrivileges: 0266 return processLeft->noNewPrivileges() > processRight->noNewPrivileges(); 0267 case HeadingXMemory: 0268 return processLeft->pixmapBytes() > processRight->pixmapBytes(); 0269 case HeadingVmSize: 0270 return processLeft->vmSize() > processRight->vmSize(); 0271 case HeadingSharedMemory: { 0272 qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmRSS() - processLeft->vmURSS() : 0; 0273 qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmRSS() - processRight->vmURSS() : 0; 0274 return memoryLeft > memoryRight; 0275 } 0276 case HeadingPid: 0277 return processLeft->pid() > processRight->pid(); 0278 case HeadingNiceness: 0279 // Sort by scheduler first 0280 if (processLeft->scheduler() != processRight->scheduler()) { 0281 if (processLeft->scheduler() == KSysGuard::Process::RoundRobin || processLeft->scheduler() == KSysGuard::Process::Fifo) 0282 return true; 0283 if (processRight->scheduler() == KSysGuard::Process::RoundRobin || processRight->scheduler() == KSysGuard::Process::Fifo) 0284 return false; 0285 if (processLeft->scheduler() == KSysGuard::Process::Other) 0286 return true; 0287 if (processRight->scheduler() == KSysGuard::Process::Other) 0288 return false; 0289 if (processLeft->scheduler() == KSysGuard::Process::Batch) 0290 return true; 0291 } 0292 if (processLeft->niceLevel() == processRight->niceLevel()) 0293 return processLeft->pid() < processRight->pid(); // Subsort by pid if the niceLevel is the same 0294 return processLeft->niceLevel() < processRight->niceLevel(); 0295 case HeadingTty: { 0296 if (processLeft->tty() == processRight->tty()) 0297 return processLeft->pid() < processRight->pid(); // Both ttys are the same. Sort by pid 0298 if (processLeft->tty().isEmpty()) 0299 return false; // Only left is empty (since they aren't the same) 0300 else if (processRight->tty().isEmpty()) 0301 return true; // Only right is empty 0302 0303 // Neither left or right is empty. The tty string is like "tty10" so split this into "tty" and "10" 0304 // and sort by the string first, then sort by the number 0305 QRegExp regexpLeft(QStringLiteral("^(\\D*)(\\d*)$")); 0306 QRegExp regexpRight(regexpLeft); 0307 if (regexpLeft.indexIn(QString::fromUtf8(processLeft->tty())) == -1 || regexpRight.indexIn(QString::fromUtf8(processRight->tty())) == -1) 0308 return processLeft->tty() < processRight->tty(); 0309 int nameMatch = regexpLeft.cap(1).compare(regexpRight.cap(1)); 0310 if (nameMatch < 0) 0311 return true; 0312 if (nameMatch > 0) 0313 return false; 0314 return regexpLeft.cap(2).toInt() < regexpRight.cap(2).toInt(); 0315 } 0316 case HeadingIoRead: 0317 switch (d->mIoInformation) { 0318 case ProcessModel::Bytes: 0319 return processLeft->ioCharactersRead() > processRight->ioCharactersRead(); 0320 case ProcessModel::Syscalls: 0321 return processLeft->ioReadSyscalls() > processRight->ioReadSyscalls(); 0322 case ProcessModel::ActualBytes: 0323 return processLeft->ioCharactersActuallyRead() > processRight->ioCharactersActuallyRead(); 0324 case ProcessModel::BytesRate: 0325 return processLeft->ioCharactersReadRate() > processRight->ioCharactersReadRate(); 0326 case ProcessModel::SyscallsRate: 0327 return processLeft->ioReadSyscallsRate() > processRight->ioReadSyscallsRate(); 0328 case ProcessModel::ActualBytesRate: 0329 return processLeft->ioCharactersActuallyReadRate() > processRight->ioCharactersActuallyReadRate(); 0330 } 0331 return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through 0332 case HeadingIoWrite: 0333 switch (d->mIoInformation) { 0334 case ProcessModel::Bytes: 0335 return processLeft->ioCharactersWritten() > processRight->ioCharactersWritten(); 0336 case ProcessModel::Syscalls: 0337 return processLeft->ioWriteSyscalls() > processRight->ioWriteSyscalls(); 0338 case ProcessModel::ActualBytes: 0339 return processLeft->ioCharactersActuallyWritten() > processRight->ioCharactersActuallyWritten(); 0340 case ProcessModel::BytesRate: 0341 return processLeft->ioCharactersWrittenRate() > processRight->ioCharactersWrittenRate(); 0342 case ProcessModel::SyscallsRate: 0343 return processLeft->ioWriteSyscallsRate() > processRight->ioWriteSyscallsRate(); 0344 case ProcessModel::ActualBytesRate: 0345 return processLeft->ioCharactersActuallyWrittenRate() > processRight->ioCharactersActuallyWrittenRate(); 0346 } 0347 } 0348 // Sort by the display string if we do not have an explicit sorting here 0349 0350 if (data(left, ProcessModel::PlainValueRole).toInt() == data(right, ProcessModel::PlainValueRole).toInt()) { 0351 return data(left, Qt::DisplayRole).toString() < data(right, Qt::DisplayRole).toString(); 0352 } 0353 return data(left, ProcessModel::PlainValueRole).toInt() < data(right, ProcessModel::PlainValueRole).toInt(); 0354 } 0355 0356 ProcessModel::~ProcessModel() 0357 { 0358 delete d; 0359 } 0360 0361 KSysGuard::Processes *ProcessModel::processController() const 0362 { 0363 return d->mProcesses.get(); 0364 } 0365 0366 const QVector<KSysGuard::ProcessAttribute *> ProcessModel::extraAttributes() const 0367 { 0368 return d->mExtraAttributes; 0369 } 0370 0371 #if HAVE_X11 0372 void ProcessModelPrivate::windowRemoved(WId wid) 0373 { 0374 WindowInfo *window = mWIdToWindowInfo.take(wid); 0375 if (!window) 0376 return; 0377 qlonglong pid = window->pid; 0378 0379 QMultiHash<qlonglong, WindowInfo *>::iterator i = mPidToWindowInfo.find(pid); 0380 while (i != mPidToWindowInfo.end() && i.key() == pid) { 0381 if (i.value()->wid == wid) { 0382 i = mPidToWindowInfo.erase(i); 0383 break; 0384 } else 0385 i++; 0386 } 0387 delete window; 0388 0389 // Update the model so that it redraws and resorts 0390 KSysGuard::Process *process = mProcesses->getProcess(pid); 0391 if (!process) 0392 return; 0393 0394 int row; 0395 if (mSimple) 0396 row = process->index(); 0397 else 0398 row = process->parent()->children().indexOf(process); 0399 QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process); 0400 Q_EMIT q->dataChanged(index2, index2); 0401 } 0402 #endif 0403 0404 #if HAVE_X11 0405 void ProcessModelPrivate::setupWindows() 0406 { 0407 if (!mIsX11) { 0408 return; 0409 } 0410 connect(KX11Extras::self(), &KX11Extras::windowChanged, this, &ProcessModelPrivate::windowChanged); 0411 connect(KX11Extras::self(), &KX11Extras::windowAdded, this, &ProcessModelPrivate::windowAdded); 0412 connect(KX11Extras::self(), &KX11Extras::windowRemoved, this, &ProcessModelPrivate::windowRemoved); 0413 0414 // Add all the windows that KWin is managing - i.e. windows that the user can see 0415 const QList<WId> windows = KX11Extras::windows(); 0416 for (auto it = windows.begin(); it != windows.end(); ++it) { 0417 updateWindowInfo(*it, static_cast<NET::Properties>(~0u), NET::Properties2{}, true); 0418 } 0419 } 0420 #endif 0421 0422 #if HAVE_XRES 0423 bool ProcessModelPrivate::updateXResClientData() 0424 { 0425 if (!mIsX11) { 0426 return false; 0427 } 0428 XResClient *clients; 0429 int count; 0430 0431 XResQueryClients(QX11Info::display(), &count, &clients); 0432 0433 mXResClientResources.clear(); 0434 for (int i = 0; i < count; i++) 0435 mXResClientResources.insert(-(qlonglong)(clients[i].resource_base), clients[i].resource_mask); 0436 0437 if (clients) 0438 XFree(clients); 0439 return true; 0440 } 0441 0442 void ProcessModelPrivate::queryForAndUpdateAllXWindows() 0443 { 0444 if (!mIsX11) { 0445 return; 0446 } 0447 updateXResClientData(); 0448 Window *children, dummy; 0449 unsigned int count; 0450 Status result = XQueryTree(QX11Info::display(), QX11Info::appRootWindow(), &dummy, &dummy, &children, &count); 0451 if (!result) 0452 return; 0453 if (!updateXResClientData()) 0454 return; 0455 for (uint i = 0; i < count; ++i) { 0456 WId wid = children[i]; 0457 QMap<qlonglong, XID>::iterator iter = mXResClientResources.lowerBound(-(qlonglong)(wid)); 0458 if (iter == mXResClientResources.end()) 0459 continue; // We couldn't find it this time :-/ 0460 0461 if (-iter.key() != (qlonglong)(wid & ~iter.value())) 0462 continue; // Already added this window 0463 0464 // Get the PID for this window if we do not know it 0465 NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::WMPid, NET::Properties2()); 0466 0467 qlonglong pid = info.pid(); 0468 if (!pid) 0469 continue; 0470 // We found a window with this client 0471 mXResClientResources.erase(iter); 0472 KSysGuard::Process *process = mProcesses->getProcess(pid); 0473 if (!process) 0474 return; // shouldn't really happen.. maybe race condition etc 0475 unsigned long previousPixmapBytes = process->pixmapBytes(); 0476 // Now update the pixmap bytes for this window 0477 bool success = XResQueryClientPixmapBytes(QX11Info::display(), wid, &process->pixmapBytes()); 0478 if (!success) 0479 process->pixmapBytes() = 0; 0480 0481 if (previousPixmapBytes != process->pixmapBytes()) { 0482 int row; 0483 if (mSimple) 0484 row = process->index(); 0485 else 0486 row = process->parent()->children().indexOf(process); 0487 QModelIndex index = q->createIndex(row, ProcessModel::HeadingXMemory, process); 0488 Q_EMIT q->dataChanged(index, index); 0489 } 0490 } 0491 if (children) 0492 XFree((char *)children); 0493 } 0494 #endif 0495 0496 void ProcessModelPrivate::setupProcesses() 0497 { 0498 if (mProcesses) { 0499 #ifdef Q_WS_X11_DISABLE 0500 mWIdToWindowInfo.clear(); 0501 mPidToWindowInfo.clear(); 0502 #endif 0503 mProcesses.clear(); 0504 q->beginResetModel(); 0505 q->endResetModel(); 0506 } 0507 0508 mProcesses = KSysGuard::ExtendedProcesses::instance(); 0509 0510 connect(mProcesses.get(), &KSysGuard::Processes::processChanged, this, &ProcessModelPrivate::processChanged); 0511 connect(mProcesses.get(), &KSysGuard::Processes::beginAddProcess, this, &ProcessModelPrivate::beginInsertRow); 0512 connect(mProcesses.get(), &KSysGuard::Processes::endAddProcess, this, &ProcessModelPrivate::endInsertRow); 0513 connect(mProcesses.get(), &KSysGuard::Processes::beginRemoveProcess, this, &ProcessModelPrivate::beginRemoveRow); 0514 connect(mProcesses.get(), &KSysGuard::Processes::endRemoveProcess, this, &ProcessModelPrivate::endRemoveRow); 0515 connect(mProcesses.get(), &KSysGuard::Processes::beginMoveProcess, this, &ProcessModelPrivate::beginMoveProcess); 0516 connect(mProcesses.get(), &KSysGuard::Processes::endMoveProcess, this, &ProcessModelPrivate::endMoveRow); 0517 mNumProcessorCores = mProcesses->numberProcessorCores(); 0518 if (mNumProcessorCores < 1) 0519 mNumProcessorCores = 1; // Default to 1 if there was an error getting the number 0520 0521 mExtraAttributes = mProcesses->extendedAttributes(); 0522 for (int i = 0; i < mExtraAttributes.count(); i++) { 0523 connect(mExtraAttributes[i], &KSysGuard::ProcessAttribute::dataChanged, this, [this, i](KSysGuard::Process *process) { 0524 const QModelIndex index = q->getQModelIndex(process, mHeadings.count() + i); 0525 Q_EMIT q->dataChanged(index, index); 0526 }); 0527 } 0528 } 0529 0530 #if HAVE_X11 0531 void ProcessModelPrivate::windowChanged(WId wid, NET::Properties properties, NET::Properties2 properties2) 0532 { 0533 updateWindowInfo(wid, properties, properties2, false); 0534 } 0535 0536 void ProcessModelPrivate::windowAdded(WId wid) 0537 { 0538 updateWindowInfo(wid, NET::Properties{}, NET::Properties2{}, true); 0539 } 0540 0541 void ProcessModelPrivate::updateWindowInfo(WId wid, NET::Properties properties, NET::Properties2 /*properties2*/, bool newWindow) 0542 { 0543 if (!mIsX11) { 0544 return; 0545 } 0546 properties &= (NET::WMPid | NET::WMVisibleName | NET::WMName | NET::WMIcon); 0547 0548 if (!properties) { 0549 return; // Nothing interesting changed 0550 } 0551 0552 WindowInfo *w = mWIdToWindowInfo.value(wid); 0553 const qreal dpr = qApp->devicePixelRatio(); 0554 0555 if (!w && !newWindow) 0556 return; // We do not have a record of this window and this is not a new window 0557 0558 if (properties == NET::WMIcon) { 0559 if (w) { 0560 w->icon = KX11Extras::icon(wid, HEADING_X_ICON_SIZE * dpr, HEADING_X_ICON_SIZE * dpr, true); 0561 w->icon.setDevicePixelRatio(dpr); 0562 } 0563 return; 0564 } 0565 /* Get PID for window */ 0566 NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), properties & ~NET::WMIcon, NET::Properties2{}); 0567 0568 if (!w) { 0569 // We know that this must be a newWindow 0570 qlonglong pid = info.pid(); 0571 if (!(properties & NET::WMPid && pid)) 0572 return; // No PID for the window - this happens if the process did not set _NET_WM_PID 0573 0574 // If we are to get the PID only, we are only interested in the XRes info for this, 0575 // so don't bother if we already have this info 0576 if (properties == NET::WMPid && mPidToWindowInfo.contains(pid)) 0577 return; 0578 0579 w = new WindowInfo(wid, pid); 0580 mPidToWindowInfo.insert(pid, w); 0581 mWIdToWindowInfo.insert(wid, w); 0582 } 0583 0584 if (w && (properties & NET::WMIcon)) { 0585 w->icon = KX11Extras::icon(wid, HEADING_X_ICON_SIZE * dpr, HEADING_X_ICON_SIZE * dpr, true); 0586 w->icon.setDevicePixelRatio(dpr); 0587 } 0588 if (properties & NET::WMVisibleName && info.visibleName()) 0589 w->name = QString::fromUtf8(info.visibleName()); 0590 else if (properties & NET::WMName) 0591 w->name = QString::fromUtf8(info.name()); 0592 else if (properties & (NET::WMName | NET::WMVisibleName)) 0593 w->name.clear(); 0594 0595 KSysGuard::Process *process = mProcesses->getProcess(w->pid); 0596 if (!process) { 0597 return; // This happens when a new window is detected before we've read in the process 0598 } 0599 0600 int row; 0601 if (mSimple) 0602 row = process->index(); 0603 else 0604 row = process->parent()->children().indexOf(process); 0605 if (!process->hasManagedGuiWindow()) { 0606 process->hasManagedGuiWindow() = true; 0607 // Since this is the first window for a process, invalidate HeadingName so that 0608 // if we are sorting by name this gets taken into account 0609 QModelIndex index1 = q->createIndex(row, ProcessModel::HeadingName, process); 0610 Q_EMIT q->dataChanged(index1, index1); 0611 } 0612 QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process); 0613 Q_EMIT q->dataChanged(index2, index2); 0614 } 0615 #endif 0616 0617 void ProcessModel::update(long updateDurationMSecs, KSysGuard::Processes::UpdateFlags updateFlags) 0618 { 0619 if (updateFlags != KSysGuard::Processes::XMemory) { 0620 d->mProcesses->updateAllProcesses(updateDurationMSecs, updateFlags); 0621 if (d->mMemTotal <= 0) 0622 d->mMemTotal = d->mProcesses->totalPhysicalMemory(); 0623 } 0624 0625 #if HAVE_XRES 0626 // Add all the rest of the windows 0627 if (d->mHaveXRes && updateFlags.testFlag(KSysGuard::Processes::XMemory)) 0628 d->queryForAndUpdateAllXWindows(); 0629 #endif 0630 } 0631 0632 QString ProcessModelPrivate::getStatusDescription(KSysGuard::Process::ProcessStatus status) const 0633 { 0634 switch (status) { 0635 case KSysGuard::Process::Running: 0636 return i18n("- Process is doing some work."); 0637 case KSysGuard::Process::Sleeping: 0638 return i18n("- Process is waiting for something to happen."); 0639 case KSysGuard::Process::Stopped: 0640 return i18n("- Process has been stopped. It will not respond to user input at the moment."); 0641 case KSysGuard::Process::Zombie: 0642 return i18n("- Process has finished and is now dead, but the parent process has not cleaned up."); 0643 case KSysGuard::Process::Ended: 0644 // return i18n("- Process has finished and no longer exists."); 0645 default: 0646 return QString(); 0647 } 0648 } 0649 0650 KSysGuard::Process *ProcessModel::getProcessAtIndex(int index) const 0651 { 0652 Q_ASSERT(d->mSimple); 0653 return d->mProcesses->getAllProcesses().at(index); 0654 } 0655 0656 int ProcessModel::rowCount(const QModelIndex &parent) const 0657 { 0658 if (d->mSimple) { 0659 if (parent.isValid()) 0660 return 0; // In flat mode, none of the processes have children 0661 return d->mProcesses->processCount(); 0662 } 0663 0664 // Deal with the case that we are showing it as a tree 0665 KSysGuard::Process *process; 0666 if (parent.isValid()) { 0667 if (parent.column() != 0) 0668 return 0; // For a treeview we say that only the first column has children 0669 process = reinterpret_cast<KSysGuard::Process *>(parent.internalPointer()); // when parent is invalid, it must be the root level which we set as 0 0670 } else { 0671 process = d->mProcesses->getProcess(-1); 0672 } 0673 Q_ASSERT(process); 0674 int num_rows = process->children().count(); 0675 return num_rows; 0676 } 0677 0678 int ProcessModel::columnCount(const QModelIndex &) const 0679 { 0680 return d->mHeadings.count() + d->mExtraAttributes.count(); 0681 } 0682 0683 bool ProcessModel::hasChildren(const QModelIndex &parent = QModelIndex()) const 0684 { 0685 if (d->mSimple) { 0686 if (parent.isValid()) 0687 return 0; // In flat mode, none of the processes have children 0688 return !d->mProcesses->getAllProcesses().isEmpty(); 0689 } 0690 0691 // Deal with the case that we are showing it as a tree 0692 KSysGuard::Process *process; 0693 if (parent.isValid()) { 0694 if (parent.column() != 0) 0695 return false; // For a treeview we say that only the first column has children 0696 process = reinterpret_cast<KSysGuard::Process *>(parent.internalPointer()); // when parent is invalid, it must be the root level which we set as 0 0697 } else { 0698 process = d->mProcesses->getProcess(-1); 0699 } 0700 Q_ASSERT(process); 0701 bool has_children = !process->children().isEmpty(); 0702 0703 Q_ASSERT((rowCount(parent) > 0) == has_children); 0704 return has_children; 0705 } 0706 0707 QModelIndex ProcessModel::index(int row, int column, const QModelIndex &parent) const 0708 { 0709 if (row < 0) 0710 return QModelIndex(); 0711 if (column < 0 || column >= columnCount()) 0712 return QModelIndex(); 0713 0714 if (d->mSimple) { 0715 if (parent.isValid()) 0716 return QModelIndex(); 0717 if (d->mProcesses->processCount() <= row) 0718 return QModelIndex(); 0719 return createIndex(row, column, d->mProcesses->getAllProcesses().at(row)); 0720 } 0721 0722 // Deal with the case that we are showing it as a tree 0723 KSysGuard::Process *parent_process = nullptr; 0724 0725 if (parent.isValid()) // not valid for init or children without parents, so use our special item with pid of 0 0726 parent_process = reinterpret_cast<KSysGuard::Process *>(parent.internalPointer()); 0727 else 0728 parent_process = d->mProcesses->getProcess(-1); 0729 Q_ASSERT(parent_process); 0730 0731 if (parent_process->children().count() > row) 0732 return createIndex(row, column, parent_process->children()[row]); 0733 else { 0734 return QModelIndex(); 0735 } 0736 } 0737 0738 bool ProcessModel::isSimpleMode() const 0739 { 0740 return d->mSimple; 0741 } 0742 0743 void ProcessModelPrivate::processChanged(KSysGuard::Process *process, bool onlyTotalCpu) 0744 { 0745 int row; 0746 if (mSimple) 0747 row = process->index(); 0748 else 0749 row = process->parent()->children().indexOf(process); 0750 0751 if (process->timeKillWasSent().isValid()) { 0752 int elapsed = process->timeKillWasSent().elapsed(); 0753 if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) { 0754 if (!mPidsToUpdate.contains(process->pid())) 0755 mPidsToUpdate.append(process->pid()); 0756 QModelIndex index1 = q->createIndex(row, 0, process); 0757 QModelIndex index2 = q->createIndex(row, mHeadings.count() - 1, process); 0758 Q_EMIT q->dataChanged(index1, index2); 0759 if (!mHaveTimer) { 0760 mHaveTimer = true; 0761 mTimerId = startTimer(100); 0762 } 0763 } 0764 } 0765 int totalUpdated = 0; 0766 Q_ASSERT(row != -1); // Something has gone very wrong 0767 if (onlyTotalCpu) { 0768 if (mShowChildTotals) { 0769 // Only the total cpu usage changed, so only update that 0770 QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process); 0771 Q_EMIT q->dataChanged(index, index); 0772 } 0773 return; 0774 } else { 0775 if (process->changes() & KSysGuard::Process::Uids) { 0776 totalUpdated++; 0777 QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process); 0778 Q_EMIT q->dataChanged(index, index); 0779 } 0780 if (process->changes() & KSysGuard::Process::Tty) { 0781 totalUpdated++; 0782 QModelIndex index = q->createIndex(row, ProcessModel::HeadingTty, process); 0783 Q_EMIT q->dataChanged(index, index); 0784 } 0785 if (process->changes() & (KSysGuard::Process::Usage | KSysGuard::Process::Status) 0786 || (process->changes() & KSysGuard::Process::TotalUsage && mShowChildTotals)) { 0787 totalUpdated += 2; 0788 QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process); 0789 Q_EMIT q->dataChanged(index, index); 0790 index = q->createIndex(row, ProcessModel::HeadingCPUTime, process); 0791 Q_EMIT q->dataChanged(index, index); 0792 // Because of our sorting, changing usage needs to also invalidate the User column 0793 index = q->createIndex(row, ProcessModel::HeadingUser, process); 0794 Q_EMIT q->dataChanged(index, index); 0795 } 0796 if (process->changes() & KSysGuard::Process::Status) { 0797 totalUpdated += 2; 0798 QModelIndex index = q->createIndex(row, ProcessModel::HeadingNoNewPrivileges, process); 0799 Q_EMIT q->dataChanged(index, index); 0800 index = q->createIndex(row, ProcessModel::HeadingCGroup, process); 0801 Q_EMIT q->dataChanged(index, index); 0802 index = q->createIndex(row, ProcessModel::HeadingMACContext, process); 0803 Q_EMIT q->dataChanged(index, index); 0804 } 0805 if (process->changes() & KSysGuard::Process::NiceLevels) { 0806 totalUpdated++; 0807 QModelIndex index = q->createIndex(row, ProcessModel::HeadingNiceness, process); 0808 Q_EMIT q->dataChanged(index, index); 0809 } 0810 if (process->changes() & KSysGuard::Process::VmSize) { 0811 totalUpdated++; 0812 QModelIndex index = q->createIndex(row, ProcessModel::HeadingVmSize, process); 0813 Q_EMIT q->dataChanged(index, index); 0814 } 0815 if (process->changes() & (KSysGuard::Process::VmSize | KSysGuard::Process::VmRSS | KSysGuard::Process::VmURSS)) { 0816 totalUpdated += 2; 0817 QModelIndex index = q->createIndex(row, ProcessModel::HeadingMemory, process); 0818 Q_EMIT q->dataChanged(index, index); 0819 QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingSharedMemory, process); 0820 Q_EMIT q->dataChanged(index2, index2); 0821 // Because of our sorting, changing usage needs to also invalidate the User column 0822 index = q->createIndex(row, ProcessModel::HeadingUser, process); 0823 Q_EMIT q->dataChanged(index, index); 0824 } 0825 if (process->changes() & KSysGuard::Process::VmPSS) { 0826 totalUpdated++; 0827 auto index = q->createIndex(row, ProcessModel::HeadingVmPSS, process); 0828 Q_EMIT q->dataChanged(index, index); 0829 } 0830 if (process->changes() & KSysGuard::Process::Name) { 0831 totalUpdated++; 0832 QModelIndex index = q->createIndex(row, ProcessModel::HeadingName, process); 0833 Q_EMIT q->dataChanged(index, index); 0834 } 0835 if (process->changes() & KSysGuard::Process::Command) { 0836 totalUpdated++; 0837 QModelIndex index = q->createIndex(row, ProcessModel::HeadingCommand, process); 0838 Q_EMIT q->dataChanged(index, index); 0839 } 0840 if (process->changes() & KSysGuard::Process::Login) { 0841 totalUpdated++; 0842 QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process); 0843 Q_EMIT q->dataChanged(index, index); 0844 } 0845 if (process->changes() & KSysGuard::Process::IO) { 0846 totalUpdated++; 0847 QModelIndex index = q->createIndex(row, ProcessModel::HeadingIoRead, process); 0848 Q_EMIT q->dataChanged(index, index); 0849 index = q->createIndex(row, ProcessModel::HeadingIoWrite, process); 0850 Q_EMIT q->dataChanged(index, index); 0851 } 0852 0853 /* Normally this would only be called if changes() tells 0854 * us to. We need to update the timestamp even if the value 0855 * didn't change though. */ 0856 auto historyMapEntry = mMapProcessCPUHistory.find(process); 0857 if (historyMapEntry != mMapProcessCPUHistory.end()) { 0858 auto &history = *historyMapEntry; 0859 unsigned long timestamp = QDateTime::currentMSecsSinceEpoch(); 0860 // Only add an entry if the latest one is older than MIN_HIST_AGE 0861 if (history.isEmpty() || timestamp - history.constLast().timestamp > MIN_HIST_AGE) { 0862 if (history.size() == MAX_HIST_ENTRIES) { 0863 history.removeFirst(); 0864 } 0865 0866 float usage = (process->totalUserUsage() + process->totalSysUsage()) / (100.0f * mNumProcessorCores); 0867 history.push_back({static_cast<unsigned long>(QDateTime::currentMSecsSinceEpoch()), usage}); 0868 } 0869 } 0870 } 0871 } 0872 0873 void ProcessModelPrivate::beginInsertRow(KSysGuard::Process *process) 0874 { 0875 Q_ASSERT(process); 0876 Q_ASSERT(!mRemovingRow); 0877 Q_ASSERT(!mInsertingRow); 0878 Q_ASSERT(!mMovingRow); 0879 mInsertingRow = true; 0880 0881 #if HAVE_X11 0882 process->hasManagedGuiWindow() = mPidToWindowInfo.contains(process->pid()); 0883 #endif 0884 if (mSimple) { 0885 int row = mProcesses->processCount(); 0886 q->beginInsertRows(QModelIndex(), row, row); 0887 return; 0888 } 0889 0890 // Deal with the case that we are showing it as a tree 0891 int row = process->parent()->children().count(); 0892 QModelIndex parentModelIndex = q->getQModelIndex(process->parent(), 0); 0893 0894 // Only here can we actually change the model. First notify the view/proxy models then modify 0895 q->beginInsertRows(parentModelIndex, row, row); 0896 } 0897 0898 void ProcessModelPrivate::endInsertRow() 0899 { 0900 Q_ASSERT(!mRemovingRow); 0901 Q_ASSERT(mInsertingRow); 0902 Q_ASSERT(!mMovingRow); 0903 mInsertingRow = false; 0904 0905 q->endInsertRows(); 0906 } 0907 void ProcessModelPrivate::beginRemoveRow(KSysGuard::Process *process) 0908 { 0909 Q_ASSERT(process); 0910 Q_ASSERT(process->pid() >= 0); 0911 Q_ASSERT(!mRemovingRow); 0912 Q_ASSERT(!mInsertingRow); 0913 Q_ASSERT(!mMovingRow); 0914 mRemovingRow = true; 0915 0916 mMapProcessCPUHistory.remove(process); 0917 0918 if (mSimple) { 0919 return q->beginRemoveRows(QModelIndex(), process->index(), process->index()); 0920 } else { 0921 int row = process->parent()->children().indexOf(process); 0922 if (row == -1) { 0923 qCDebug(LIBKSYSGUARD_PROCESSUI) << "A serious problem occurred in remove row."; 0924 mRemovingRow = false; 0925 return; 0926 } 0927 0928 return q->beginRemoveRows(q->getQModelIndex(process->parent(), 0), row, row); 0929 } 0930 } 0931 0932 void ProcessModelPrivate::endRemoveRow() 0933 { 0934 Q_ASSERT(!mInsertingRow); 0935 Q_ASSERT(!mMovingRow); 0936 if (!mRemovingRow) 0937 return; 0938 mRemovingRow = false; 0939 0940 q->endRemoveRows(); 0941 } 0942 0943 void ProcessModelPrivate::beginMoveProcess(KSysGuard::Process *process, KSysGuard::Process *new_parent) 0944 { 0945 Q_ASSERT(!mRemovingRow); 0946 Q_ASSERT(!mInsertingRow); 0947 Q_ASSERT(!mMovingRow); 0948 0949 if (mSimple) 0950 return; // We don't need to move processes when in simple mode 0951 mMovingRow = true; 0952 0953 int current_row = process->parent()->children().indexOf(process); 0954 Q_ASSERT(current_row != -1); 0955 int new_row = new_parent->children().count(); 0956 QModelIndex sourceParent = q->getQModelIndex(process->parent(), 0); 0957 QModelIndex destinationParent = q->getQModelIndex(new_parent, 0); 0958 mMovingRow = q->beginMoveRows(sourceParent, current_row, current_row, destinationParent, new_row); 0959 Q_ASSERT(mMovingRow); 0960 } 0961 0962 void ProcessModelPrivate::endMoveRow() 0963 { 0964 Q_ASSERT(!mInsertingRow); 0965 Q_ASSERT(!mRemovingRow); 0966 if (!mMovingRow) 0967 return; 0968 mMovingRow = false; 0969 0970 q->endMoveRows(); 0971 } 0972 0973 QModelIndex ProcessModel::getQModelIndex(KSysGuard::Process *process, int column) const 0974 { 0975 Q_ASSERT(process); 0976 int pid = process->pid(); 0977 if (pid == -1) 0978 return QModelIndex(); // pid -1 is our fake process meaning the very root (never drawn). To represent that, we return QModelIndex() which also means 0979 // the top element 0980 int row = 0; 0981 if (d->mSimple) { 0982 row = process->index(); 0983 } else { 0984 row = process->parent()->children().indexOf(process); 0985 } 0986 Q_ASSERT(row != -1); 0987 return createIndex(row, column, process); 0988 } 0989 0990 QModelIndex ProcessModel::parent(const QModelIndex &index) const 0991 { 0992 if (!index.isValid()) 0993 return QModelIndex(); 0994 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 0995 Q_ASSERT(process); 0996 0997 if (d->mSimple) 0998 return QModelIndex(); 0999 else 1000 return getQModelIndex(process->parent(), 0); 1001 } 1002 1003 static inline QVariant columnAlignment(const int section) 1004 { 1005 switch (section) { 1006 case ProcessModel::HeadingUser: 1007 case ProcessModel::HeadingCPUUsage: 1008 case ProcessModel::HeadingNoNewPrivileges: 1009 return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); 1010 case ProcessModel::HeadingPid: 1011 case ProcessModel::HeadingNiceness: 1012 case ProcessModel::HeadingCPUTime: 1013 case ProcessModel::HeadingStartTime: 1014 case ProcessModel::HeadingMemory: 1015 case ProcessModel::HeadingXMemory: 1016 case ProcessModel::HeadingSharedMemory: 1017 case ProcessModel::HeadingVmSize: 1018 case ProcessModel::HeadingIoWrite: 1019 case ProcessModel::HeadingIoRead: 1020 case ProcessModel::HeadingVmPSS: 1021 return QVariant(Qt::AlignRight | Qt::AlignVCenter); 1022 case ProcessModel::HeadingTty: 1023 return QVariant(Qt::AlignLeft | Qt::AlignVCenter); 1024 default: 1025 return QVariant(); 1026 } 1027 } 1028 1029 QVariant ProcessModel::headerData(int section, Qt::Orientation orientation, int role) const 1030 { 1031 if (orientation != Qt::Horizontal) 1032 return QVariant(); 1033 if (section < 0) 1034 return QVariant(); // is this needed? 1035 1036 if (section >= d->mHeadings.count() && section < columnCount()) { 1037 int attr = section - d->mHeadings.count(); 1038 switch (role) { 1039 case Qt::DisplayRole: 1040 return d->mExtraAttributes[attr]->shortName(); 1041 } 1042 return QVariant(); 1043 } 1044 1045 switch (role) { 1046 case Qt::TextAlignmentRole: { 1047 return columnAlignment(section); 1048 } 1049 case Qt::ToolTipRole: { 1050 if (!d->mShowingTooltips) 1051 return QVariant(); 1052 switch (section) { 1053 case HeadingName: 1054 return i18n("The process name."); 1055 case HeadingUser: 1056 return i18n("The user who owns this process."); 1057 case HeadingTty: 1058 return i18n("The controlling terminal on which this process is running."); 1059 case HeadingNiceness: 1060 return i18n( 1061 "The priority with which this process is being run. For the normal scheduler, this ranges from 19 (very nice, least priority) to -19 (top " 1062 "priority)."); 1063 case HeadingCPUUsage: 1064 if (d->mNumProcessorCores == 1) 1065 return i18n("The current CPU usage of the process."); 1066 else 1067 // i18n: %1 is always greater than 1, so do not worry about 1068 // nonsensical verbosity of the singular part. 1069 if (d->mNormalizeCPUUsage) 1070 return i18np("The current total CPU usage of the process, divided by the %1 processor core in the machine.", 1071 "The current total CPU usage of the process, divided by the %1 processor cores in the machine.", 1072 d->mNumProcessorCores); 1073 else 1074 return i18n("The current total CPU usage of the process."); 1075 case HeadingCPUTime: 1076 return i18n("<qt>The total user and system time that this process has been running for, displayed as minutes:seconds."); 1077 case HeadingVmSize: 1078 return i18n( 1079 "<qt>This is the amount of virtual memory space that the process is using, included shared libraries, graphics memory, files on disk, and so " 1080 "on. This number is almost meaningless.</qt>"); 1081 case HeadingMemory: 1082 return i18n( 1083 "<qt>This is the amount of real physical memory that this process is using by itself, and approximates the Private memory usage of the " 1084 "process.<br>It does not include any swapped out memory, nor the code size of its shared libraries.<br>This is often the most useful figure to " 1085 "judge the memory use of a program. See What's This for more information.</qt>"); 1086 case HeadingSharedMemory: 1087 return i18n( 1088 "<qt>This is approximately the amount of real physical memory that this process's shared libraries are using.<br>This memory is shared among " 1089 "all processes that use this library.</qt>"); 1090 case HeadingStartTime: 1091 return i18n("<qt>The elapsed time since the process was started.</qt>"); 1092 case HeadingNoNewPrivileges: 1093 return i18n("<qt>Linux flag NoNewPrivileges, if set the process can't gain further privileges via setuid etc.</qt>"); 1094 case HeadingCommand: 1095 return i18n("<qt>The command with which this process was launched.</qt>"); 1096 case HeadingXMemory: 1097 return i18n("<qt>The amount of pixmap memory that this process is using.</qt>"); 1098 case HeadingXTitle: 1099 return i18n("<qt>The title of any windows that this process is showing.</qt>"); 1100 case HeadingPid: 1101 return i18n("The unique Process ID that identifies this process."); 1102 case HeadingIoRead: 1103 return i18n("The number of bytes read. See What's This for more information."); 1104 case HeadingIoWrite: 1105 return i18n("The number of bytes written. See What's This for more information."); 1106 case HeadingCGroup: 1107 return i18n("<qt>The control group (cgroup) where this process belongs.</qt>"); 1108 case HeadingMACContext: 1109 return i18n("<qt>Mandatory Access Control (SELinux or AppArmor) context for this process.</qt>"); 1110 case HeadingVmPSS: 1111 return i18n( 1112 "The amount of private physical memory used by a process, with the amount of shared memory divided by the amount of processes using that " 1113 "shared memory added."); 1114 default: 1115 return QVariant(); 1116 } 1117 } 1118 case Qt::WhatsThisRole: { 1119 switch (section) { 1120 case HeadingName: 1121 return i18n( 1122 "<qt><i>Technical information: </i>The kernel process name is a maximum of 8 characters long, so the full command is examined. If the first " 1123 "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."); 1124 case HeadingUser: 1125 return i18n( 1126 "<qt>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 " 1127 "the effective user. The ToolTip contains the full information. <p>" 1128 "<table>" 1129 "<tr><td>Login Name/Group</td><td>The username of the Real User/Group who created this process</td></tr>" 1130 "<tr><td>Effective User/Group</td><td>The process is running with privileges of the Effective User/Group. This is shown if different from the " 1131 "real user.</td></tr>" 1132 "<tr><td>Setuid User/Group</td><td>The saved username of the binary. The process can escalate its Effective User/Group to the Setuid " 1133 "User/Group.</td></tr>" 1134 #ifdef Q_OS_LINUX 1135 "<tr><td>File System User/Group</td><td>Accesses to the filesystem are checked with the File System User/Group. This is a Linux specific " 1136 "call. See setfsuid(2) for more information.</td></tr>" 1137 #endif 1138 "</table>"); 1139 case HeadingVmSize: 1140 return i18n( 1141 "<qt>This is the size of allocated address space - not memory, but address space. This value in practice means next to nothing. When a process " 1142 "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. " 1143 "<p><i>Technical information: </i>This is VmSize in proc/*/status and VIRT in top."); 1144 case HeadingMemory: 1145 return i18n( 1146 "<qt><i>Technical information: </i>This is an approximation of the Private memory usage, calculated as VmRSS - Shared, from /proc/*/statm. " 1147 "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 " 1148 "that is fast to determine. This is sometimes known as URSS (Unique Resident Set Size). For an individual process, see \"Detailed Memory " 1149 "Information\" for a more accurate, but slower, calculation of the true Private memory usage."); 1150 case HeadingCPUUsage: 1151 return i18n("The CPU usage of a process and all of its threads."); 1152 case HeadingCPUTime: 1153 return i18n( 1154 "<qt>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 " 1155 "clock time if the process has been across multiple CPU cores."); 1156 case HeadingSharedMemory: 1157 return i18n( 1158 "<qt><i>Technical information: </i>This is an approximation of the Shared memory, called SHR in top. It is the number of pages that are " 1159 "backed by a file (see kernel Documentation/filesystems/proc.txt). For an individual process, see \"Detailed Memory Information\" for a more " 1160 "accurate, but slower, calculation of the true Shared memory usage."); 1161 case HeadingStartTime: 1162 return i18n("<qt><i>Technical information: </i>The underlying value (clock ticks since system boot) is retrieved from /proc/[pid]/stat"); 1163 case HeadingNoNewPrivileges: 1164 return i18n("<qt><i>Technical information: </i>The flag is retrieved from /proc/[pid]/status"); 1165 case HeadingCommand: 1166 return i18n("<qt><i>Technical information: </i>This is from /proc/*/cmdline"); 1167 case HeadingXMemory: 1168 return i18n( 1169 "<qt><i>Technical information: </i>This is the amount of memory used by the Xorg process for images for this process. This is memory used in " 1170 "addition to Memory and Shared Memory.<br><i>Technical information: </i>This only counts the pixmap memory, and does not include resource " 1171 "memory used by fonts, cursors, glyphsets etc. See the <code>xrestop</code> program for a more detailed breakdown."); 1172 case HeadingXTitle: 1173 return i18n( 1174 "<qt><i>Technical information: </i>For each X11 window, the X11 property _NET_WM_PID is used to map the window to a PID. If a process' " 1175 "windows are not shown, then that application incorrectly is not setting _NET_WM_PID."); 1176 case HeadingPid: 1177 return i18n( 1178 "<qt><i>Technical information: </i>This is the Process ID. A multi-threaded application is treated a single process, with all threads sharing " 1179 "the same PID. The CPU usage etc will be the total, accumulated, CPU usage of all the threads."); 1180 case HeadingIoRead: 1181 case HeadingIoWrite: 1182 return i18n( 1183 "<qt>This column shows the IO statistics for each process. The tooltip provides the following information:<br>" 1184 "<table>" 1185 "<tr><td>Characters Read</td><td>The number of bytes which this task has caused to be read from storage. This is simply the sum of bytes which " 1186 "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 " 1187 "required (the read might have been satisfied from pagecache).</td></tr>" 1188 "<tr><td>Characters Written</td><td>The number of bytes which this task has caused, or shall cause to be written to disk. Similar caveats " 1189 "apply here as with Characters Read.</td></tr>" 1190 "<tr><td>Read Syscalls</td><td>The number of read I/O operations, i.e. syscalls like read() and pread().</td></tr>" 1191 "<tr><td>Write Syscalls</td><td>The number of write I/O operations, i.e. syscalls like write() and pwrite().</td></tr>" 1192 "<tr><td>Actual Bytes Read</td><td>The number of bytes which this process really did cause to be fetched from the storage layer. Done at the " 1193 "submit_bio() level, so it is accurate for block-backed filesystems. This may not give sensible values for NFS and CIFS filesystems.</td></tr>" 1194 "<tr><td>Actual Bytes Written</td><td>Attempt to count the number of bytes which this process caused to be sent to the storage layer. This is " 1195 "done at page-dirtying time.</td>" 1196 "</table><p>" 1197 "The number in brackets shows the rate at which each value is changing, determined from taking the difference between the previous value and " 1198 "the new value, and dividing by the update interval.<p>" 1199 "<i>Technical information: </i>This data is collected from /proc/*/io and is documented further in Documentation/accounting and " 1200 "Documentation/filesystems/proc.txt in the kernel source."); 1201 case HeadingCGroup: 1202 return i18n( 1203 "<qt><i>Technical information: </i>This shows Linux Control Group (cgroup) membership, retrieved from /proc/[pid]/cgroup. Control groups are " 1204 "used by Systemd and containers for limiting process group's usage of resources and to monitor them."); 1205 case HeadingMACContext: 1206 return i18n( 1207 "<qt><i>Technical information: </i>This shows Mandatory Access Control (SELinux or AppArmor) context, retrieved from " 1208 "/proc/[pid]/attr/current."); 1209 case HeadingVmPSS: 1210 return i18n( 1211 "<i>Technical information:</i> This is often referred to as \"Proportional Set Size\" and is the closest approximation of the real amount of " 1212 "total memory used by a process. Note that the number of applications sharing shared memory is determined per shared memory section and thus " 1213 "can vary per memory section."); 1214 default: 1215 return QVariant(); 1216 } 1217 } 1218 case Qt::DisplayRole: 1219 return d->mHeadings[section]; 1220 default: 1221 return QVariant(); 1222 } 1223 } 1224 1225 void ProcessModel::setSimpleMode(bool simple) 1226 { 1227 if (d->mSimple == simple) 1228 return; 1229 1230 Q_EMIT layoutAboutToBeChanged(); 1231 1232 d->mSimple = simple; 1233 1234 int flatrow; 1235 int treerow; 1236 QList<QModelIndex> flatIndexes; 1237 QList<QModelIndex> treeIndexes; 1238 foreach (KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { 1239 flatrow = process->index(); 1240 treerow = process->parent()->children().indexOf(process); 1241 flatIndexes.clear(); 1242 treeIndexes.clear(); 1243 1244 for (int i = 0; i < columnCount(); i++) { 1245 flatIndexes << createIndex(flatrow, i, process); 1246 treeIndexes << createIndex(treerow, i, process); 1247 } 1248 if (d->mSimple) // change from tree mode to flat mode 1249 changePersistentIndexList(treeIndexes, flatIndexes); 1250 else // change from flat mode to tree mode 1251 changePersistentIndexList(flatIndexes, treeIndexes); 1252 } 1253 1254 Q_EMIT layoutChanged(); 1255 } 1256 1257 bool ProcessModel::canUserLogin(long uid) const 1258 { 1259 if (uid == 65534) { 1260 // nobody user 1261 return false; 1262 } 1263 1264 if (!d->mIsLocalhost) 1265 return true; // We only deal with localhost. Just always return true for non localhost 1266 1267 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 1268 if (canLogin != -1) 1269 return canLogin; // We know whether they can log in 1270 1271 // We got the default, -1, so we don't know. Look it up 1272 1273 KUser user(uid); 1274 if (!user.isValid()) { 1275 // for some reason the user isn't recognised. This might happen under certain security situations. 1276 // Just return true to be safe 1277 d->mUidCanLogin[uid] = 1; 1278 return true; 1279 } 1280 QString shell = user.shell(); 1281 if (shell == QLatin1String("/bin/false")) // FIXME - add in any other shells it could be for false 1282 { 1283 d->mUidCanLogin[uid] = 0; 1284 return false; 1285 } 1286 d->mUidCanLogin[uid] = 1; 1287 return true; 1288 } 1289 1290 QString ProcessModelPrivate::getTooltipForUser(const KSysGuard::Process *ps) const 1291 { 1292 QString userTooltip; 1293 if (!mIsLocalhost) { 1294 return xi18nc("@info:tooltip", "<para><emphasis strong='true'>Login Name:</emphasis> %1</para>", getUsernameForUser(ps->uid(), true)); 1295 } else { 1296 KUser user(ps->uid()); 1297 if (!user.isValid()) 1298 userTooltip += xi18nc("@info:tooltip", "<para>This user is not recognized for some reason.</para>"); 1299 else { 1300 if (!user.property(KUser::FullName).isValid()) 1301 userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>%1</emphasis></para>", user.property(KUser::FullName).toString()); 1302 userTooltip += xi18nc("@info:tooltip", 1303 "<para><emphasis strong='true'>Login Name:</emphasis> %1 (uid: %2)</para>", 1304 user.loginName(), 1305 QString::number(ps->uid())); 1306 if (!user.property(KUser::RoomNumber).isValid()) 1307 userTooltip += 1308 xi18nc("@info:tooltip", "<para><emphasis strong='true'> Room Number:</emphasis> %1</para>", user.property(KUser::RoomNumber).toString()); 1309 if (!user.property(KUser::WorkPhone).isValid()) 1310 userTooltip += 1311 xi18nc("@info:tooltip", "<para><emphasis strong='true'> Work Phone:</emphasis> %1</para>", user.property(KUser::WorkPhone).toString()); 1312 } 1313 } 1314 if ((ps->uid() != ps->euid() && ps->euid() != -1) || (ps->uid() != ps->suid() && ps->suid() != -1) || (ps->uid() != ps->fsuid() && ps->fsuid() != -1)) { 1315 if (ps->euid() != -1) 1316 userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Effective User:</emphasis> %1</para>", getUsernameForUser(ps->euid(), true)); 1317 if (ps->suid() != -1) 1318 userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Setuid User:</emphasis> %1</para>", getUsernameForUser(ps->suid(), true)); 1319 if (ps->fsuid() != -1) 1320 userTooltip += 1321 xi18nc("@info:tooltip", "<para><emphasis strong='true'>File System User:</emphasis> %1</para>", getUsernameForUser(ps->fsuid(), true)); 1322 userTooltip += QLatin1String("<br/>"); 1323 } 1324 if (ps->gid() != -1) { 1325 userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Group:</emphasis> %1</para>", getGroupnameForGroup(ps->gid())); 1326 if ((ps->gid() != ps->egid() && ps->egid() != -1) || (ps->gid() != ps->sgid() && ps->sgid() != -1) || (ps->gid() != ps->fsgid() && ps->fsgid() != -1)) { 1327 if (ps->egid() != -1) 1328 userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Effective Group:</emphasis> %1</para>", getGroupnameForGroup(ps->egid())); 1329 if (ps->sgid() != -1) 1330 userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Setuid Group:</emphasis> %1</para>", getGroupnameForGroup(ps->sgid())); 1331 if (ps->fsgid() != -1) 1332 userTooltip += 1333 xi18nc("@info:tooltip", "<para><emphasis strong='true'>File System Group:</emphasis> %1</para>", getGroupnameForGroup(ps->fsgid())); 1334 } 1335 } 1336 return userTooltip; 1337 } 1338 1339 QString ProcessModel::getStringForProcess(KSysGuard::Process *process) const 1340 { 1341 return i18nc("Short description of a process. PID, name, user", 1342 "%1: %2, owned by user %3", 1343 QString::number(process->pid()), 1344 process->name(), 1345 d->getUsernameForUser(process->uid(), false)); 1346 } 1347 1348 QString ProcessModelPrivate::getGroupnameForGroup(long gid) const 1349 { 1350 if (mIsLocalhost) { 1351 QString groupname = KUserGroup(gid).name(); 1352 if (!groupname.isEmpty()) 1353 return i18nc("Group name and group id", "%1 (gid: %2)", groupname, QString::number(gid)); 1354 } 1355 return QString::number(gid); 1356 } 1357 1358 QString ProcessModelPrivate::getUsernameForUser(long uid, bool withuid) const 1359 { 1360 QString &username = mUserUsername[uid]; 1361 if (username.isNull()) { 1362 if (!mIsLocalhost) { 1363 username = QLatin1String(""); // empty, but not null 1364 } else { 1365 KUser user(uid); 1366 if (!user.isValid()) 1367 username = QLatin1String(""); 1368 else 1369 username = user.loginName(); 1370 } 1371 } 1372 if (username.isEmpty()) 1373 return QString::number(uid); 1374 if (withuid) 1375 return i18nc("User name and user id", "%1 (uid: %2)", username, QString::number(uid)); 1376 return username; 1377 } 1378 1379 QVariant ProcessModel::data(const QModelIndex &index, int role) const 1380 { 1381 // This function must be super duper ultra fast because it's called thousands of times every few second :( 1382 // I think it should be optimised for role first, hence the switch statement (fastest possible case) 1383 1384 if (!index.isValid()) { 1385 return QVariant(); 1386 } 1387 1388 if (index.column() > columnCount()) { 1389 return QVariant(); 1390 } 1391 // plugin stuff first 1392 if (index.column() >= d->mHeadings.count()) { 1393 int attr = index.column() - d->mHeadings.count(); 1394 switch (role) { 1395 case ProcessModel::PlainValueRole: { 1396 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 1397 const QVariant value = d->mExtraAttributes[attr]->data(process); 1398 return value; 1399 } 1400 case Qt::DisplayRole: { 1401 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 1402 const QVariant value = d->mExtraAttributes[attr]->data(process); 1403 return KSysGuard::Formatter::formatValue(value, d->mExtraAttributes[attr]->unit()); 1404 } 1405 case Qt::TextAlignmentRole: { 1406 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 1407 const QVariant value = d->mExtraAttributes[attr]->data(process); 1408 if (value.canConvert(QMetaType::LongLong) && static_cast<QMetaType::Type>(value.type()) != QMetaType::QString) { 1409 return (int)(Qt::AlignRight | Qt::AlignVCenter); 1410 } 1411 return (int)(Qt::AlignLeft | Qt::AlignVCenter); 1412 } 1413 } 1414 return QVariant(); 1415 } 1416 1417 KFormat format; 1418 switch (role) { 1419 case Qt::DisplayRole: { 1420 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 1421 switch (index.column()) { 1422 case HeadingName: 1423 if (d->mShowCommandLineOptions) 1424 return process->name(); 1425 else 1426 return process->name().section(QLatin1Char(' '), 0, 0); 1427 case HeadingPid: 1428 return (qlonglong)process->pid(); 1429 case HeadingUser: 1430 if (!process->login().isEmpty()) 1431 return process->login(); 1432 if (process->uid() == process->euid()) 1433 return d->getUsernameForUser(process->uid(), false); 1434 else 1435 return QString(d->getUsernameForUser(process->uid(), false) + QStringLiteral(", ") + d->getUsernameForUser(process->euid(), false)); 1436 case HeadingNiceness: 1437 switch (process->scheduler()) { 1438 case KSysGuard::Process::Other: 1439 return process->niceLevel(); 1440 case KSysGuard::Process::SchedulerIdle: 1441 return i18nc("scheduler", "Idle"); // neither static nor dynamic priority matter 1442 case KSysGuard::Process::Batch: 1443 return i18nc("scheduler", "(Batch) %1", process->niceLevel()); // only dynamic priority matters 1444 case KSysGuard::Process::RoundRobin: 1445 return i18nc("Round robin scheduler", "RR %1", process->niceLevel()); 1446 case KSysGuard::Process::Fifo: 1447 if (process->niceLevel() == 99) 1448 return i18nc("Real Time scheduler", "RT"); 1449 else 1450 return i18nc("First in first out scheduler", "FIFO %1", process->niceLevel()); 1451 case KSysGuard::Process::Interactive: 1452 return i18nc("scheduler", "(IA) %1", process->niceLevel()); 1453 } 1454 return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through 1455 case HeadingTty: 1456 return process->tty(); 1457 case HeadingCPUUsage: { 1458 double total; 1459 if (d->mShowChildTotals && !d->mSimple) 1460 total = process->totalUserUsage() + process->totalSysUsage(); 1461 else 1462 total = process->userUsage() + process->sysUsage(); 1463 if (d->mNormalizeCPUUsage) 1464 total = total / d->mNumProcessorCores; 1465 1466 if (total < 1 && process->status() != KSysGuard::Process::Sleeping && process->status() != KSysGuard::Process::Running 1467 && process->status() != KSysGuard::Process::Ended) 1468 return process->translatedStatus(); // tell the user when the process is a zombie or stopped 1469 if (total < 0.5) 1470 return QString(); 1471 1472 return QString(QString::number((int)(total + 0.5)) + QLatin1Char('%')); 1473 } 1474 case HeadingCPUTime: { 1475 qlonglong seconds = (process->userTime() + process->sysTime()) / 100; 1476 return QStringLiteral("%1:%2").arg(seconds / 60).arg((int)seconds % 60, 2, 10, QLatin1Char('0')); 1477 } 1478 case HeadingMemory: 1479 if (process->vmURSS() == -1) { 1480 // If we don't have the URSS (the memory used by only the process, not the shared libraries) 1481 // then return the RSS (physical memory used by the process + shared library) as the next best thing 1482 return formatMemoryInfo(process->vmRSS(), d->mUnits, true); 1483 } else { 1484 return formatMemoryInfo(process->vmURSS(), d->mUnits, true); 1485 } 1486 case HeadingVmSize: 1487 return formatMemoryInfo(process->vmSize(), d->mUnits, true); 1488 case HeadingSharedMemory: 1489 if (process->vmRSS() - process->vmURSS() <= 0 || process->vmURSS() == -1) 1490 return QVariant(QVariant::String); 1491 return formatMemoryInfo(process->vmRSS() - process->vmURSS(), d->mUnits); 1492 case HeadingStartTime: { 1493 // NOTE: the next 6 lines are the same as in the next occurrence of 'case HeadingStartTime:' => keep in sync or remove duplicate code 1494 const auto clockTicksSinceSystemBoot = process->startTime(); 1495 const auto clockTicksPerSecond = sysconf(_SC_CLK_TCK); // see man proc or https://superuser.com/questions/101183/what-is-a-cpu-tick 1496 const auto secondsSinceSystemBoot = (double)clockTicksSinceSystemBoot / clockTicksPerSecond; 1497 const auto systemBootTime = TimeUtil::systemUptimeAbsolute(); 1498 const auto absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot); 1499 const auto relativeStartTime = absoluteStartTime.secsTo(QDateTime::currentDateTime()); 1500 return TimeUtil::secondsToHumanElapsedString(relativeStartTime); 1501 } 1502 case HeadingNoNewPrivileges: 1503 return QString::number(process->noNewPrivileges()); 1504 case HeadingCommand: { 1505 return process->command().replace(QLatin1Char('\n'), QLatin1Char(' ')); 1506 // It would be nice to embolden the process name in command, but this requires that the itemdelegate to support html text 1507 // QString command = process->command; 1508 // command.replace(process->name, "<b>" + process->name + "</b>"); 1509 // return "<qt>" + command; 1510 } 1511 case HeadingIoRead: { 1512 switch (d->mIoInformation) { 1513 case ProcessModel::Bytes: // divide by 1024 to convert to kB 1514 return formatMemoryInfo(process->ioCharactersRead() / 1024, d->mIoUnits, true); 1515 case ProcessModel::Syscalls: 1516 if (process->ioReadSyscalls()) 1517 return QString::number(process->ioReadSyscalls()); 1518 break; 1519 case ProcessModel::ActualBytes: 1520 return formatMemoryInfo(process->ioCharactersActuallyRead() / 1024, d->mIoUnits, true); 1521 case ProcessModel::BytesRate: 1522 if (process->ioCharactersReadRate() / 1024) 1523 return i18n("%1/s", formatMemoryInfo(process->ioCharactersReadRate() / 1024, d->mIoUnits, true)); 1524 break; 1525 case ProcessModel::SyscallsRate: 1526 if (process->ioReadSyscallsRate()) 1527 return QString::number(process->ioReadSyscallsRate()); 1528 break; 1529 case ProcessModel::ActualBytesRate: 1530 if (process->ioCharactersActuallyReadRate() / 1024) 1531 return i18n("%1/s", formatMemoryInfo(process->ioCharactersActuallyReadRate() / 1024, d->mIoUnits, true)); 1532 break; 1533 } 1534 return QVariant(); 1535 } 1536 case HeadingIoWrite: { 1537 switch (d->mIoInformation) { 1538 case ProcessModel::Bytes: 1539 return formatMemoryInfo(process->ioCharactersWritten() / 1024, d->mIoUnits, true); 1540 case ProcessModel::Syscalls: 1541 if (process->ioWriteSyscalls()) 1542 return QString::number(process->ioWriteSyscalls()); 1543 break; 1544 case ProcessModel::ActualBytes: 1545 return formatMemoryInfo(process->ioCharactersActuallyWritten() / 1024, d->mIoUnits, true); 1546 case ProcessModel::BytesRate: 1547 if (process->ioCharactersWrittenRate() / 1024) 1548 return i18n("%1/s", formatMemoryInfo(process->ioCharactersWrittenRate() / 1024, d->mIoUnits, true)); 1549 break; 1550 case ProcessModel::SyscallsRate: 1551 if (process->ioWriteSyscallsRate()) 1552 return QString::number(process->ioWriteSyscallsRate()); 1553 break; 1554 case ProcessModel::ActualBytesRate: 1555 if (process->ioCharactersActuallyWrittenRate() / 1024) 1556 return i18n("%1/s", formatMemoryInfo(process->ioCharactersActuallyWrittenRate() / 1024, d->mIoUnits, true)); 1557 break; 1558 } 1559 return QVariant(); 1560 } 1561 #if HAVE_X11 1562 case HeadingXMemory: 1563 return formatMemoryInfo(process->pixmapBytes() / 1024, d->mUnits, true); 1564 case HeadingXTitle: { 1565 if (!process->hasManagedGuiWindow()) 1566 return QVariant(QVariant::String); 1567 1568 WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); 1569 if (!w) 1570 return QVariant(QVariant::String); 1571 else 1572 return w->name; 1573 } 1574 #endif 1575 case HeadingCGroup: 1576 return process->cGroup(); 1577 case HeadingMACContext: 1578 return process->macContext(); 1579 case HeadingVmPSS: 1580 return process->vmPSS() >= 0 ? formatMemoryInfo(process->vmPSS(), d->mUnits, true) : QVariant{}; 1581 default: 1582 return QVariant(); 1583 } 1584 break; 1585 } 1586 case Qt::ToolTipRole: { 1587 if (!d->mShowingTooltips) 1588 return QVariant(); 1589 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 1590 QString tracer; 1591 if (process->tracerpid() >= 0) { 1592 KSysGuard::Process *process_tracer = d->mProcesses->getProcess(process->tracerpid()); 1593 if (process_tracer) // it is possible for this to be not the case in certain race conditions 1594 tracer = 1595 xi18nc("tooltip. name,pid ", "This process is being debugged by %1 (%2)", process_tracer->name(), QString::number(process->tracerpid())); 1596 } 1597 switch (index.column()) { 1598 case HeadingName: { 1599 /* It would be nice to be able to show the icon in the tooltip, but Qt4 won't let us put 1600 * a picture in a tooltip :( 1601 1602 QIcon icon; 1603 if(mPidToWindowInfo.contains(process->pid())) { 1604 WId wid; 1605 wid = mPidToWindowInfo[process->pid()].wid; 1606 icon = KWindowSystem::icon(wid); 1607 } 1608 if(icon.isValid()) { 1609 tooltip = i18n("<qt><table><tr><td>%1", icon); 1610 } 1611 */ 1612 QString tooltip; 1613 if (process->parentPid() == -1) { 1614 // Give a quick explanation of init and kthreadd 1615 if (process->name() == QLatin1String("init") || process->name() == QLatin1String("systemd")) { 1616 tooltip = xi18nc("@info:tooltip", 1617 "<title>%1</title><para>The parent of all other processes and cannot be killed.</para><para><emphasis " 1618 "strong='true'>Process ID:</emphasis> %2</para>", 1619 process->name(), 1620 QString::number(process->pid())); 1621 } else if (process->name() == QLatin1String("kthreadd")) { 1622 tooltip = xi18nc("@info:tooltip", 1623 "<title>KThreadd</title><para>Manages kernel threads. The children processes run in the kernel, controlling hard disk " 1624 "access, etc.</para>"); 1625 } else { 1626 tooltip = xi18nc("@info:tooltip", 1627 "<title>%1</title><para><emphasis strong='true'>Process ID:</emphasis> %2</para>", 1628 process->name(), 1629 QString::number(process->pid())); 1630 } 1631 } else { 1632 KSysGuard::Process *parent_process = d->mProcesses->getProcess(process->parentPid()); 1633 if (parent_process) { // In race conditions, it's possible for this process to not exist 1634 tooltip = xi18nc("@info:tooltip", 1635 "<title>%1</title>" 1636 "<para><emphasis strong='true'>Process ID:</emphasis> %2</para>" 1637 "<para><emphasis strong='true'>Parent:</emphasis> %3</para>" 1638 "<para><emphasis strong='true'>Parent's ID:</emphasis> %4</para>", 1639 process->name(), 1640 QString::number(process->pid()), 1641 parent_process->name(), 1642 QString::number(process->parentPid())); 1643 } else { 1644 tooltip = xi18nc("@info:tooltip", 1645 "<title>%1</title>" 1646 "<para><emphasis strong='true'>Process ID:</emphasis> %2</para>" 1647 "<para><emphasis strong='true'>Parent's ID:</emphasis> %3</para>", 1648 process->name(), 1649 QString::number(process->pid()), 1650 QString::number(process->parentPid())); 1651 } 1652 } 1653 if (process->numThreads() >= 1) 1654 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Number of threads:</emphasis> %1</para>", process->numThreads()); 1655 if (!process->command().isEmpty()) { 1656 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Command:</emphasis> %1</para>", process->command()); 1657 } 1658 if (!process->tty().isEmpty()) 1659 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Running on:</emphasis> %1</para>", QString::fromUtf8(process->tty())); 1660 if (!tracer.isEmpty()) 1661 return QStringLiteral("%1<br />%2").arg(tooltip).arg(tracer); 1662 1663 return tooltip; 1664 } 1665 case HeadingStartTime: { 1666 // NOTE: the next 6 lines are the same as in the previous occurrence of 'case HeadingStartTime:' => keep in sync or remove duplicate code 1667 const auto clockTicksSinceSystemBoot = process->startTime(); 1668 const auto clockTicksPerSecond = sysconf(_SC_CLK_TCK); 1669 const auto secondsSinceSystemBoot = (double)clockTicksSinceSystemBoot / clockTicksPerSecond; 1670 const auto systemBootTime = TimeUtil::systemUptimeAbsolute(); 1671 const auto absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot); 1672 const auto relativeStartTime = absoluteStartTime.secsTo(QDateTime::currentDateTime()); 1673 return xi18nc("@info:tooltip", 1674 "<para><emphasis strong='true'>Clock ticks since system boot:</emphasis> %1</para>" 1675 "<para><emphasis strong='true'>Seconds since system boot:</emphasis> %2 (System boot time: %3)</para>" 1676 "<para><emphasis strong='true'>Absolute start time:</emphasis> %4</para>" 1677 "<para><emphasis strong='true'>Relative start time:</emphasis> %5</para>", 1678 clockTicksSinceSystemBoot, 1679 secondsSinceSystemBoot, 1680 systemBootTime.toString(), 1681 absoluteStartTime.toString(), 1682 TimeUtil::secondsToHumanElapsedString(relativeStartTime)); 1683 } 1684 case HeadingCommand: { 1685 QString tooltip = xi18nc("@info:tooltip", 1686 "<para><emphasis strong='true'>This process was run with the following command:</emphasis></para>" 1687 "<para>%1</para>", 1688 process->command()); 1689 if (!process->tty().isEmpty()) 1690 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Running on:</emphasis> %1</para>", QString::fromUtf8(process->tty())); 1691 if (!tracer.isEmpty()) { 1692 return QStringLiteral("%1<br/>%2").arg(tooltip).arg(tracer); 1693 } 1694 return tooltip; 1695 } 1696 case HeadingUser: { 1697 QString tooltip = d->getTooltipForUser(process); 1698 if (tracer.isEmpty()) { 1699 return tooltip; 1700 } 1701 1702 return QString(tooltip + QStringLiteral("<br/>") + tracer); 1703 } 1704 case HeadingNiceness: { 1705 QString tooltip; 1706 switch (process->scheduler()) { 1707 case KSysGuard::Process::Other: 1708 case KSysGuard::Process::Batch: 1709 case KSysGuard::Process::Interactive: 1710 tooltip = xi18nc("@info:tooltip", 1711 "<para><emphasis strong='true'>Nice level:</emphasis> %1 (%2)</para>", 1712 process->niceLevel(), 1713 process->niceLevelAsString()); 1714 break; 1715 case KSysGuard::Process::RoundRobin: 1716 case KSysGuard::Process::Fifo: 1717 tooltip = xi18nc("@info:tooltip", 1718 "<para><emphasis strong='true'>This is a real time process.</emphasis></para>" 1719 "<para><emphasis strong='true'>Scheduler priority:</emphasis> %1</para>", 1720 process->niceLevel()); 1721 break; 1722 case KSysGuard::Process::SchedulerIdle: 1723 break; // has neither dynamic (niceness) or static (scheduler priority) priority 1724 } 1725 if (process->scheduler() != KSysGuard::Process::Other) 1726 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Scheduler:</emphasis> %1</para>", process->schedulerAsString()); 1727 1728 if (process->ioPriorityClass() != KSysGuard::Process::None) { 1729 if ((process->ioPriorityClass() == KSysGuard::Process::RealTime || process->ioPriorityClass() == KSysGuard::Process::BestEffort) 1730 && process->ioniceLevel() != -1) 1731 tooltip += xi18nc("@info:tooltip", 1732 "<para><emphasis strong='true'>I/O Nice level:</emphasis> %1 (%2)</para>", 1733 process->ioniceLevel(), 1734 process->ioniceLevelAsString()); 1735 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>I/O Class:</emphasis> %1</para>", process->ioPriorityClassAsString()); 1736 } 1737 if (tracer.isEmpty()) 1738 return tooltip; 1739 return QString(tooltip + QStringLiteral("<br/>") + tracer); 1740 } 1741 case HeadingCPUUsage: 1742 case HeadingCPUTime: { 1743 int divideby = (d->mNormalizeCPUUsage ? d->mNumProcessorCores : 1); 1744 QString tooltip = 1745 xi18nc("@info:tooltip", 1746 "<para><emphasis strong='true'>Process status:</emphasis> %1 %2</para>" 1747 "<para><emphasis strong='true'>User CPU usage:</emphasis> %3%</para>" 1748 "<para><emphasis strong='true'>System CPU usage:</emphasis> %4%</para>", /* Please do not add </qt> here - the tooltip is appended to */ 1749 process->translatedStatus(), 1750 d->getStatusDescription(process->status()), 1751 (float)(process->userUsage()) / divideby, 1752 (float)(process->sysUsage()) / divideby); 1753 1754 if (process->numThreads() >= 1) 1755 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Number of threads:</emphasis> %1</para>", process->numThreads()); 1756 if (process->numChildren() > 0) { 1757 tooltip += xi18nc("@info:tooltip", 1758 "<para><emphasis strong='true'>Number of children:</emphasis> %1</para>" 1759 "<para><emphasis strong='true'>Total User CPU usage:</emphasis> %2%</para>" 1760 "<para><emphasis strong='true'>Total System CPU usage:</emphasis> %3%</para>" 1761 "<para><emphasis strong='true'>Total CPU usage:</emphasis> %4%</para>", 1762 process->numChildren(), 1763 (float)(process->totalUserUsage()) / divideby, 1764 (float)(process->totalSysUsage()) / divideby, 1765 (float)(process->totalUserUsage() + process->totalSysUsage()) / divideby); 1766 } 1767 if (process->userTime() > 0) 1768 tooltip += xi18nc("@info:tooltip", 1769 "<para><emphasis strong='true'>CPU time spent running as user:</emphasis> %1 seconds</para>", 1770 QString::number(process->userTime() / 100.0, 'f', 1)); 1771 if (process->sysTime() > 0) 1772 tooltip += xi18nc("@info:tooltip", 1773 "<para><emphasis strong='true'>CPU time spent running in kernel:</emphasis> %1 seconds</para>", 1774 QString::number(process->sysTime() / 100.0, 'f', 1)); 1775 if (process->niceLevel() != 0) 1776 tooltip += xi18nc("@info:tooltip", 1777 "<para><emphasis strong='true'>Nice level:</emphasis> %1 (%2)</para>", 1778 process->niceLevel(), 1779 process->niceLevelAsString()); 1780 if (process->ioPriorityClass() != KSysGuard::Process::None) { 1781 if ((process->ioPriorityClass() == KSysGuard::Process::RealTime || process->ioPriorityClass() == KSysGuard::Process::BestEffort) 1782 && process->ioniceLevel() != -1) 1783 tooltip += xi18nc("@info:tooltip", 1784 "<para><emphasis strong='true'>I/O Nice level:</emphasis> %1 (%2)</para>", 1785 process->ioniceLevel(), 1786 process->ioniceLevelAsString()); 1787 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>I/O Class:</emphasis> %1</para>", process->ioPriorityClassAsString()); 1788 } 1789 1790 if (!tracer.isEmpty()) 1791 return QString(tooltip + QStringLiteral("<br/>") + tracer); 1792 return tooltip; 1793 } 1794 case HeadingVmSize: { 1795 return QVariant(); 1796 } 1797 case HeadingMemory: { 1798 QString tooltip; 1799 if (process->vmURSS() != -1) { 1800 // We don't have information about the URSS, so just fallback to RSS 1801 if (d->mMemTotal > 0) 1802 tooltip += xi18nc("@info:tooltip", 1803 "<para><emphasis strong='true'>Memory usage:</emphasis> %1 out of %2 (%3 %)</para>", 1804 format.formatByteSize(process->vmURSS() * 1024), 1805 format.formatByteSize(d->mMemTotal * 1024), 1806 process->vmURSS() * 100 / d->mMemTotal); 1807 else 1808 tooltip += 1809 xi18nc("@info:tooltip", "<emphasis strong='true'>Memory usage:</emphasis> %1<br />", format.formatByteSize(process->vmURSS() * 1024)); 1810 } 1811 if (d->mMemTotal > 0) 1812 tooltip += xi18nc("@info:tooltip", 1813 "<para><emphasis strong='true'>RSS Memory usage:</emphasis> %1 out of %2 (%3 %)</para>", 1814 format.formatByteSize(process->vmRSS() * 1024), 1815 format.formatByteSize(d->mMemTotal * 1024), 1816 process->vmRSS() * 100 / d->mMemTotal); 1817 else 1818 tooltip += xi18nc("@info:tooltip", 1819 "<para><emphasis strong='true'>RSS Memory usage:</emphasis> %1</para>", 1820 format.formatByteSize(process->vmRSS() * 1024)); 1821 return tooltip; 1822 } 1823 case HeadingSharedMemory: { 1824 if (process->vmURSS() == -1) { 1825 return xi18nc("@info:tooltip", 1826 "<para><emphasis strong='true'>Your system does not seem to have this information available to be read.</emphasis></para>"); 1827 } 1828 if (d->mMemTotal > 0) 1829 return xi18nc("@info:tooltip", 1830 "<para><emphasis strong='true'>Shared library memory usage:</emphasis> %1 out of %2 (%3 %)</para>", 1831 format.formatByteSize((process->vmRSS() - process->vmURSS()) * 1024), 1832 format.formatByteSize(d->mMemTotal * 1024), 1833 (process->vmRSS() - process->vmURSS()) * 100 / d->mMemTotal); 1834 else 1835 return xi18nc("@info:tooltip", 1836 "<para><emphasis strong='true'>Shared library memory usage:</emphasis> %1</para>", 1837 format.formatByteSize((process->vmRSS() - process->vmURSS()) * 1024)); 1838 } 1839 case HeadingIoWrite: 1840 case HeadingIoRead: { 1841 // FIXME - use the formatByteRate functions when added 1842 return kxi18nc("@info:tooltip", 1843 "<para><emphasis strong='true'>Characters read:</emphasis> %1 (%2 KiB/s)</para>" 1844 "<para><emphasis strong='true'>Characters written:</emphasis> %3 (%4 KiB/s)</para>" 1845 "<para><emphasis strong='true'>Read syscalls:</emphasis> %5 (%6 s⁻¹)</para>" 1846 "<para><emphasis strong='true'>Write syscalls:</emphasis> %7 (%8 s⁻¹)</para>" 1847 "<para><emphasis strong='true'>Actual bytes read:</emphasis> %9 (%10 KiB/s)</para>" 1848 "<para><emphasis strong='true'>Actual bytes written:</emphasis> %11 (%12 KiB/s)</para>") 1849 .subs(format.formatByteSize(process->ioCharactersRead())) 1850 .subs(QString::number(process->ioCharactersReadRate() / 1024)) 1851 .subs(format.formatByteSize(process->ioCharactersWritten())) 1852 .subs(QString::number(process->ioCharactersWrittenRate() / 1024)) 1853 .subs(QString::number(process->ioReadSyscalls())) 1854 .subs(QString::number(process->ioReadSyscallsRate())) 1855 .subs(QString::number(process->ioWriteSyscalls())) 1856 .subs(QString::number(process->ioWriteSyscallsRate())) 1857 .subs(format.formatByteSize(process->ioCharactersActuallyRead())) 1858 .subs(QString::number(process->ioCharactersActuallyReadRate() / 1024)) 1859 .subs(format.formatByteSize(process->ioCharactersActuallyWritten())) 1860 .subs(QString::number(process->ioCharactersActuallyWrittenRate() / 1024)) 1861 .toString(); 1862 } 1863 case HeadingXTitle: { 1864 #if HAVE_X11 1865 const auto values = d->mPidToWindowInfo.values(process->pid()); 1866 if (values.count() == 1) { 1867 return values.first()->name; 1868 } 1869 1870 QString tooltip; 1871 1872 for (const auto &value : values) { 1873 if (!tooltip.isEmpty()) { 1874 tooltip += QLatin1Char('\n'); 1875 } 1876 tooltip += QStringLiteral("• ") + value->name; 1877 } 1878 1879 return tooltip; 1880 #else 1881 return QVariant(QVariant::String); 1882 #endif 1883 } 1884 case HeadingVmPSS: { 1885 if (process->vmPSS() == -1) { 1886 return xi18nc("@info:tooltip", 1887 "<para><emphasis strong='true'>Your system does not seem to have this information available to be read.</emphasis></para>"); 1888 } 1889 if (d->mMemTotal > 0) { 1890 return xi18nc("@info:tooltip", 1891 "<para><emphasis strong='true'>Total memory usage:</emphasis> %1 out of %2 (%3 %)</para>", 1892 format.formatByteSize(process->vmPSS() * 1024), 1893 format.formatByteSize(d->mMemTotal * 1024), 1894 qRound(process->vmPSS() * 1000.0 / d->mMemTotal) / 10.0); 1895 } else { 1896 return xi18nc("@info:tooltip", 1897 "<para><emphasis strong='true'>Shared library memory usage:</emphasis> %1</para>", 1898 format.formatByteSize(process->vmPSS() * 1024)); 1899 } 1900 } 1901 default: 1902 return QVariant(QVariant::String); 1903 } 1904 } 1905 case Qt::TextAlignmentRole: 1906 return columnAlignment(index.column()); 1907 case UidRole: { 1908 if (index.column() != 0) 1909 return QVariant(); // If we query with this role, then we want the raw UID for this. 1910 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 1911 return process->uid(); 1912 } 1913 case PlainValueRole: // Used to return a plain value. For copying to a clipboard etc 1914 { 1915 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 1916 switch (index.column()) { 1917 case HeadingName: 1918 return process->name(); 1919 case HeadingPid: 1920 return (qlonglong)process->pid(); 1921 case HeadingUser: 1922 if (!process->login().isEmpty()) 1923 return process->login(); 1924 if (process->uid() == process->euid()) 1925 return d->getUsernameForUser(process->uid(), false); 1926 else 1927 return QString(d->getUsernameForUser(process->uid(), false) + QStringLiteral(", ") + d->getUsernameForUser(process->euid(), false)); 1928 case HeadingNiceness: 1929 return process->niceLevel(); 1930 case HeadingTty: 1931 return process->tty(); 1932 case HeadingCPUUsage: { 1933 double total; 1934 if (d->mShowChildTotals && !d->mSimple) 1935 total = process->totalUserUsage() + process->totalSysUsage(); 1936 else 1937 total = process->userUsage() + process->sysUsage(); 1938 1939 if (d->mNormalizeCPUUsage) 1940 return total / d->mNumProcessorCores; 1941 else 1942 return total; 1943 } 1944 case HeadingCPUTime: 1945 return (qlonglong)(process->userTime() + process->sysTime()); 1946 case HeadingMemory: 1947 if (process->vmRSS() == 0) 1948 return QVariant(QVariant::String); 1949 if (process->vmURSS() == -1) { 1950 return (qlonglong)process->vmRSS(); 1951 } else { 1952 return (qlonglong)process->vmURSS(); 1953 } 1954 case HeadingVmSize: 1955 return (qlonglong)process->vmSize(); 1956 case HeadingSharedMemory: 1957 if (process->vmRSS() - process->vmURSS() < 0 || process->vmURSS() == -1) 1958 return QVariant(QVariant::String); 1959 return (qlonglong)(process->vmRSS() - process->vmURSS()); 1960 case HeadingStartTime: 1961 return process->startTime(); // 2015-01-03, gregormi: can maybe be replaced with something better later 1962 case HeadingNoNewPrivileges: 1963 return process->noNewPrivileges(); 1964 case HeadingCommand: 1965 return process->command(); 1966 case HeadingIoRead: 1967 switch (d->mIoInformation) { 1968 case ProcessModel::Bytes: 1969 return process->ioCharactersRead(); 1970 case ProcessModel::Syscalls: 1971 return process->ioReadSyscalls(); 1972 case ProcessModel::ActualBytes: 1973 return process->ioCharactersActuallyRead(); 1974 case ProcessModel::BytesRate: 1975 return (qlonglong)process->ioCharactersReadRate(); 1976 case ProcessModel::SyscallsRate: 1977 return (qlonglong)process->ioReadSyscallsRate(); 1978 case ProcessModel::ActualBytesRate: 1979 return (qlonglong)process->ioCharactersActuallyReadRate(); 1980 } 1981 return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through 1982 case HeadingIoWrite: 1983 switch (d->mIoInformation) { 1984 case ProcessModel::Bytes: 1985 return process->ioCharactersWritten(); 1986 case ProcessModel::Syscalls: 1987 return process->ioWriteSyscalls(); 1988 case ProcessModel::ActualBytes: 1989 return process->ioCharactersActuallyWritten(); 1990 case ProcessModel::BytesRate: 1991 return (qlonglong)process->ioCharactersWrittenRate(); 1992 case ProcessModel::SyscallsRate: 1993 return (qlonglong)process->ioWriteSyscallsRate(); 1994 case ProcessModel::ActualBytesRate: 1995 return (qlonglong)process->ioCharactersActuallyWrittenRate(); 1996 } 1997 return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through 1998 case HeadingXMemory: 1999 return (qulonglong)process->pixmapBytes(); 2000 #if HAVE_X11 2001 case HeadingXTitle: { 2002 WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); 2003 if (!w) 2004 return QString(); 2005 return w->name; 2006 } 2007 #endif 2008 case HeadingCGroup: 2009 return process->cGroup(); 2010 case HeadingMACContext: 2011 return process->macContext(); 2012 case HeadingVmPSS: 2013 return process->vmPSS() >= 0 ? process->vmPSS() : QVariant{}; 2014 default: 2015 return QVariant(); 2016 } 2017 break; 2018 } 2019 #if HAVE_X11 2020 case WindowIdRole: { 2021 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 2022 WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); 2023 if (!w) 2024 return QVariant(); 2025 else 2026 return (int)w->wid; 2027 } 2028 #endif 2029 case PercentageRole: { 2030 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 2031 Q_CHECK_PTR(process); 2032 switch (index.column()) { 2033 case HeadingCPUUsage: { 2034 float cpu; 2035 if (d->mSimple || !d->mShowChildTotals) 2036 cpu = process->userUsage() + process->sysUsage(); 2037 else 2038 cpu = process->totalUserUsage() + process->totalSysUsage(); 2039 cpu = cpu / 100.0; 2040 if (!d->mNormalizeCPUUsage) 2041 return cpu; 2042 return cpu / d->mNumProcessorCores; 2043 } 2044 case HeadingMemory: 2045 if (d->mMemTotal <= 0) 2046 return -1; 2047 if (process->vmURSS() != -1) 2048 return float(process->vmURSS()) / d->mMemTotal; 2049 else 2050 return float(process->vmRSS()) / d->mMemTotal; 2051 case HeadingSharedMemory: 2052 if (process->vmURSS() == -1 || d->mMemTotal <= 0) 2053 return -1; 2054 return float(process->vmRSS() - process->vmURSS()) / d->mMemTotal; 2055 case HeadingVmPSS: 2056 if (process->vmPSS() == -1 || d->mMemTotal <= 0) { 2057 return -1; 2058 } 2059 return float(process->vmPSS()) / d->mMemTotal; 2060 default: 2061 return -1; 2062 } 2063 } 2064 case PercentageHistoryRole: { 2065 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 2066 Q_CHECK_PTR(process); 2067 switch (index.column()) { 2068 case HeadingCPUUsage: { 2069 auto it = d->mMapProcessCPUHistory.find(process); 2070 if (it == d->mMapProcessCPUHistory.end()) { 2071 it = d->mMapProcessCPUHistory.insert(process, {}); 2072 it->reserve(ProcessModelPrivate::MAX_HIST_ENTRIES); 2073 } 2074 return QVariant::fromValue(*it); 2075 } 2076 default: { 2077 } 2078 } 2079 return QVariant::fromValue(QVector<PercentageHistoryEntry>{}); 2080 } 2081 case Qt::DecorationRole: { 2082 if (index.column() == HeadingName) { 2083 #if HAVE_X11 2084 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 2085 if (!process->hasManagedGuiWindow()) { 2086 if (d->mSimple) // When not in tree mode, we need to pad the name column where we do not have an icon 2087 return QIcon(d->mBlankPixmap); 2088 else // When in tree mode, the padding looks bad, so do not pad in this case 2089 return QVariant(); 2090 } 2091 WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL); 2092 if (w && !w->icon.isNull()) 2093 return w->icon; 2094 return QIcon(d->mBlankPixmap); 2095 #else 2096 return QVariant(); 2097 #endif 2098 2099 } else if (index.column() == HeadingCPUUsage) { 2100 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 2101 if (process->status() == KSysGuard::Process::Stopped || process->status() == KSysGuard::Process::Zombie) { 2102 // QPixmap pix = KIconLoader::global()->loadIcon("button_cancel", KIconLoader::Small, 2103 // KIconLoader::SizeSmall, KIconLoader::DefaultState, QStringList(), 2104 // 0L, true); 2105 } 2106 } 2107 return QVariant(); 2108 } 2109 case Qt::BackgroundRole: { 2110 if (index.column() != HeadingUser) { 2111 if (!d->mHaveTimer) // If there is no timer, then no processes are being killed, so no point looking for one 2112 return QVariant(); 2113 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 2114 if (process->timeKillWasSent().isValid()) { 2115 int elapsed = process->timeKillWasSent().elapsed(); 2116 if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) { // Only show red for about 7 seconds 2117 int transparency = 255 - elapsed * 250 / MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS; 2118 2119 KColorScheme scheme(QPalette::Active, KColorScheme::Selection); 2120 QBrush brush = scheme.background(KColorScheme::NegativeBackground); 2121 QColor color = brush.color(); 2122 color.setAlpha(transparency); 2123 brush.setColor(color); 2124 return brush; 2125 } 2126 } 2127 return QVariant(); 2128 } 2129 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 2130 if (process->status() == KSysGuard::Process::Ended) { 2131 return QColor(Qt::lightGray); 2132 } 2133 if (process->tracerpid() >= 0) { 2134 // It's being debugged, so probably important. Let's mark it as such 2135 return QColor(Qt::yellow); 2136 } 2137 if (d->mIsLocalhost && process->uid() == getuid()) { // own user 2138 return QColor(0, 208, 214, 50); 2139 } 2140 if (process->uid() < 100 || !canUserLogin(process->uid())) 2141 return QColor(218, 220, 215, 50); // no color for system tasks 2142 // other users 2143 return QColor(2, 154, 54, 50); 2144 } 2145 case Qt::FontRole: { 2146 if (index.column() == HeadingCPUUsage) { 2147 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 2148 if (process->userUsage() == 0) { 2149 QFont font; 2150 font.setItalic(true); 2151 return font; 2152 } 2153 } 2154 return QVariant(); 2155 } 2156 default: // This is a very very common case, so the route to this must be very minimal 2157 return QVariant(); 2158 } 2159 2160 return QVariant(); // never get here, but make compiler happy 2161 } 2162 2163 bool ProcessModel::hasGUIWindow(qlonglong pid) const 2164 { 2165 #if HAVE_X11 2166 return d->mPidToWindowInfo.contains(pid); 2167 #else 2168 return false; 2169 #endif 2170 } 2171 2172 bool ProcessModel::isLocalhost() const 2173 { 2174 return d->mIsLocalhost; 2175 } 2176 2177 void ProcessModel::setupHeader() 2178 { 2179 // These must be in the same order that they are in the header file 2180 QStringList headings; 2181 headings << i18nc("process heading", "Name"); 2182 headings << i18nc("process heading", "Username"); 2183 headings << i18nc("process heading", "PID"); 2184 headings << i18nc("process heading", "TTY"); 2185 headings << i18nc("process heading", "Niceness"); 2186 // xgettext: no-c-format 2187 headings << i18nc("process heading", "CPU %"); 2188 headings << i18nc("process heading", "CPU Time"); 2189 headings << i18nc("process heading", "IO Read"); 2190 headings << i18nc("process heading", "IO Write"); 2191 headings << i18nc("process heading", "Virtual Size"); 2192 headings << i18nc("process heading", "Memory"); 2193 headings << i18nc("process heading", "Shared Mem"); 2194 headings << i18nc("process heading", "Relative Start Time"); 2195 headings << i18nc("process heading", "NNP"); 2196 headings << i18nc("process heading", "Command"); 2197 #if HAVE_X11 2198 if (d->mIsX11) { 2199 headings << i18nc("process heading", "X11 Memory"); 2200 headings << i18nc("process heading", "Window Title"); 2201 } 2202 #endif 2203 headings << i18nc("process heading", "CGroup"); 2204 headings << i18nc("process heading", "MAC Context"); 2205 headings << i18nc("process heading", "Total Memory"); 2206 2207 if (d->mHeadings.isEmpty()) { // If it's empty, this is the first time this has been called, so insert the headings 2208 d->mHeadings = headings; 2209 } else { 2210 // This was called to retranslate the headings. Just use the new translations and call headerDataChanged 2211 Q_ASSERT(d->mHeadings.count() == headings.count()); 2212 d->mHeadings = headings; 2213 headerDataChanged(Qt::Horizontal, 0, headings.count() - 1); 2214 } 2215 } 2216 2217 void ProcessModel::retranslateUi() 2218 { 2219 setupHeader(); 2220 } 2221 2222 KSysGuard::Process *ProcessModel::getProcess(qlonglong pid) 2223 { 2224 return d->mProcesses->getProcess(pid); 2225 } 2226 2227 bool ProcessModel::showTotals() const 2228 { 2229 return d->mShowChildTotals; 2230 } 2231 2232 void ProcessModel::setShowTotals(bool showTotals) // slot 2233 { 2234 if (showTotals == d->mShowChildTotals) 2235 return; 2236 d->mShowChildTotals = showTotals; 2237 2238 QModelIndex index; 2239 foreach (KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { 2240 if (process->numChildren() > 0) { 2241 int row; 2242 if (d->mSimple) 2243 row = process->index(); 2244 else 2245 row = process->parent()->children().indexOf(process); 2246 index = createIndex(row, HeadingCPUUsage, process); 2247 Q_EMIT dataChanged(index, index); 2248 } 2249 } 2250 } 2251 2252 qlonglong ProcessModel::totalMemory() const 2253 { 2254 return d->mMemTotal; 2255 } 2256 2257 void ProcessModel::setUnits(Units units) 2258 { 2259 if (d->mUnits == units) 2260 return; 2261 d->mUnits = units; 2262 2263 QModelIndex index; 2264 foreach (KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { 2265 int row; 2266 if (d->mSimple) 2267 row = process->index(); 2268 else 2269 row = process->parent()->children().indexOf(process); 2270 index = createIndex(row, HeadingMemory, process); 2271 Q_EMIT dataChanged(index, index); 2272 index = createIndex(row, HeadingXMemory, process); 2273 Q_EMIT dataChanged(index, index); 2274 index = createIndex(row, HeadingSharedMemory, process); 2275 Q_EMIT dataChanged(index, index); 2276 index = createIndex(row, HeadingVmSize, process); 2277 Q_EMIT dataChanged(index, index); 2278 } 2279 } 2280 2281 ProcessModel::Units ProcessModel::units() const 2282 { 2283 return (Units)d->mUnits; 2284 } 2285 2286 void ProcessModel::setIoUnits(Units units) 2287 { 2288 if (d->mIoUnits == units) 2289 return; 2290 d->mIoUnits = units; 2291 2292 QModelIndex index; 2293 foreach (KSysGuard::Process *process, d->mProcesses->getAllProcesses()) { 2294 int row; 2295 if (d->mSimple) 2296 row = process->index(); 2297 else 2298 row = process->parent()->children().indexOf(process); 2299 index = createIndex(row, HeadingIoRead, process); 2300 Q_EMIT dataChanged(index, index); 2301 index = createIndex(row, HeadingIoWrite, process); 2302 Q_EMIT dataChanged(index, index); 2303 } 2304 } 2305 2306 ProcessModel::Units ProcessModel::ioUnits() const 2307 { 2308 return (Units)d->mIoUnits; 2309 } 2310 2311 void ProcessModel::setIoInformation(ProcessModel::IoInformation ioInformation) 2312 { 2313 d->mIoInformation = ioInformation; 2314 } 2315 2316 ProcessModel::IoInformation ProcessModel::ioInformation() const 2317 { 2318 return d->mIoInformation; 2319 } 2320 2321 QString ProcessModel::formatMemoryInfo(qlonglong amountInKB, Units units, bool returnEmptyIfValueIsZero) const 2322 { 2323 // We cache the result of i18n for speed reasons. We call this function 2324 // hundreds of times, every second or so 2325 if (returnEmptyIfValueIsZero && amountInKB == 0) 2326 return QString(); 2327 static QString percentageString = i18n("%1%", QString::fromLatin1("%1")); 2328 if (units == UnitsPercentage) { 2329 if (d->mMemTotal == 0) 2330 return QLatin1String(""); // memory total not determined yet. Shouldn't happen, but don't crash if it does 2331 float percentage = amountInKB * 100.0 / d->mMemTotal; 2332 if (percentage < 0.1) 2333 percentage = 0.1; 2334 return percentageString.arg(percentage, 0, 'f', 1); 2335 } else 2336 return formatByteSize(amountInKB, units); 2337 } 2338 2339 QString ProcessModel::hostName() const 2340 { 2341 return d->mHostName; 2342 } 2343 2344 QStringList ProcessModel::mimeTypes() const 2345 { 2346 QStringList types; 2347 types << QStringLiteral("text/plain"); 2348 types << QStringLiteral("text/csv"); 2349 types << QStringLiteral("text/html"); 2350 return types; 2351 } 2352 2353 QMimeData *ProcessModel::mimeData(const QModelIndexList &indexes) const 2354 { 2355 QMimeData *mimeData = new QMimeData(); 2356 QString textCsv; 2357 QString textCsvHeaders; 2358 QString textPlain; 2359 QString textPlainHeaders; 2360 QString textHtml; 2361 QString textHtmlHeaders; 2362 QString display; 2363 int firstColumn = -1; 2364 bool firstrow = true; 2365 foreach (const QModelIndex &index, indexes) { 2366 if (index.isValid()) { 2367 if (firstColumn == -1) 2368 firstColumn = index.column(); 2369 else if (firstColumn != index.column()) 2370 continue; 2371 else { 2372 textCsv += QLatin1Char('\n'); 2373 textPlain += QLatin1Char('\n'); 2374 textHtml += QLatin1String("</tr><tr>"); 2375 firstrow = false; 2376 } 2377 for (int i = 0; i < d->mHeadings.size(); i++) { 2378 if (firstrow) { 2379 QString heading = d->mHeadings[i]; 2380 textHtmlHeaders += QLatin1String("<th>") + heading + QLatin1String("</th>"); 2381 if (i) { 2382 textCsvHeaders += QLatin1Char(','); 2383 textPlainHeaders += QLatin1String(", "); 2384 } 2385 textPlainHeaders += heading; 2386 heading.replace(QLatin1Char('"'), QLatin1String("\"\"")); 2387 textCsvHeaders += QLatin1Char('"') + heading + QLatin1Char('"'); 2388 } 2389 QModelIndex index2 = createIndex(index.row(), i, reinterpret_cast<KSysGuard::Process *>(index.internalPointer())); 2390 QString display = data(index2, PlainValueRole).toString(); 2391 if (i) { 2392 textCsv += QLatin1Char(','); 2393 textPlain += QLatin1String(", "); 2394 } 2395 textHtml += QLatin1String("<td>") + display.toHtmlEscaped() + QLatin1String("</td>"); 2396 textPlain += display; 2397 display.replace(QLatin1Char('"'), QLatin1String("\"\"")); 2398 textCsv += QLatin1Char('"') + display + QLatin1Char('"'); 2399 } 2400 } 2401 } 2402 textHtml = QLatin1String("<html><table><tr>") + textHtmlHeaders + QLatin1String("</tr><tr>") + textHtml + QLatin1String("</tr></table>"); 2403 textCsv = textCsvHeaders + QLatin1Char('\n') + textCsv; 2404 textPlain = textPlainHeaders + QLatin1Char('\n') + textPlain; 2405 2406 mimeData->setText(textPlain); 2407 mimeData->setHtml(textHtml); 2408 mimeData->setData(QStringLiteral("text/csv"), textCsv.toUtf8()); 2409 return mimeData; 2410 } 2411 2412 Qt::ItemFlags ProcessModel::flags(const QModelIndex &index) const 2413 { 2414 if (!index.isValid()) 2415 return Qt::NoItemFlags; // Would this ever happen? 2416 2417 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 2418 if (process->status() == KSysGuard::Process::Ended) 2419 return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable; 2420 else 2421 return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled; 2422 } 2423 2424 bool ProcessModel::isShowCommandLineOptions() const 2425 { 2426 return d->mShowCommandLineOptions; 2427 } 2428 2429 void ProcessModel::setShowCommandLineOptions(bool showCommandLineOptions) 2430 { 2431 d->mShowCommandLineOptions = showCommandLineOptions; 2432 } 2433 2434 bool ProcessModel::isShowingTooltips() const 2435 { 2436 return d->mShowingTooltips; 2437 } 2438 2439 void ProcessModel::setShowingTooltips(bool showTooltips) 2440 { 2441 d->mShowingTooltips = showTooltips; 2442 } 2443 2444 bool ProcessModel::isNormalizedCPUUsage() const 2445 { 2446 return d->mNormalizeCPUUsage; 2447 } 2448 2449 void ProcessModel::setNormalizedCPUUsage(bool normalizeCPUUsage) 2450 { 2451 d->mNormalizeCPUUsage = normalizeCPUUsage; 2452 } 2453 2454 void ProcessModelPrivate::timerEvent(QTimerEvent *event) 2455 { 2456 Q_UNUSED(event); 2457 foreach (qlonglong pid, mPidsToUpdate) { 2458 KSysGuard::Process *process = mProcesses->getProcess(pid); 2459 if (process && process->timeKillWasSent().isValid() && process->timeKillWasSent().elapsed() < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) { 2460 int row; 2461 if (mSimple) 2462 row = process->index(); 2463 else 2464 row = process->parent()->children().indexOf(process); 2465 2466 QModelIndex index1 = q->createIndex(row, 0, process); 2467 QModelIndex index2 = q->createIndex(row, mHeadings.count() - 1, process); 2468 Q_EMIT q->dataChanged(index1, index2); 2469 } else { 2470 mPidsToUpdate.removeAll(pid); 2471 } 2472 } 2473 2474 if (mPidsToUpdate.isEmpty()) { 2475 mHaveTimer = false; 2476 killTimer(mTimerId); 2477 mTimerId = -1; 2478 } 2479 }