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