File indexing completed on 2024-04-28 16:49:55
0001 /* 0002 SPDX-FileCopyrightText: 2007 John Tapsell <tapsell@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "processes_atop_p.h" 0008 #include "atop_p.h" 0009 #include "process.h" 0010 #include "processcore_debug.h" 0011 0012 #include <zlib.h> 0013 0014 #include <QByteArray> 0015 #include <QFile> 0016 #include <QHash> 0017 #include <QTextStream> 0018 #include <QtEndian> 0019 0020 #include <QDebug> 0021 0022 namespace KSysGuard 0023 { 0024 class ProcessesATop::Private 0025 { 0026 public: 0027 Private(); 0028 ~Private(); 0029 QFile atopLog; 0030 bool ready; 0031 0032 bool loadDataForHistory(int index); 0033 bool loadHistoryFile(const QString &filename); 0034 0035 RawHeader rh; 0036 RawRecord rr; 0037 // SStat sstats; 0038 PStat *pstats; 0039 QList<long> pids; // This is a list of process pid's, in the exact same order as pstats 0040 QString lastError; 0041 0042 QList<long> historyOffsets; //< The file offset where each history is stored 0043 QList<QPair<QDateTime, uint>> historyTimes; //< The end time for each history record and its interval, probably in order from oldest to newest 0044 int currentlySelectedIndex; 0045 }; 0046 0047 ProcessesATop::Private::Private() 0048 : ready(false) 0049 , pstats(nullptr) 0050 , currentlySelectedIndex(-1) 0051 { 0052 } 0053 0054 ProcessesATop::Private::~Private() 0055 { 0056 } 0057 0058 QString ProcessesATop::historyFileName() const 0059 { 0060 return d->atopLog.fileName(); 0061 } 0062 0063 bool ProcessesATop::loadHistoryFile(const QString &filename) 0064 { 0065 return d->loadHistoryFile(filename); 0066 } 0067 0068 bool ProcessesATop::Private::loadHistoryFile(const QString &filename) 0069 { 0070 atopLog.setFileName(filename); 0071 ready = false; 0072 currentlySelectedIndex = -1; 0073 if (!atopLog.exists()) { 0074 lastError = QLatin1String("File ") + filename + QLatin1String(" does not exist"); 0075 return false; 0076 } 0077 0078 if (!atopLog.open(QIODevice::ReadOnly)) { 0079 lastError = QLatin1String("Could not open file ") + filename; 0080 return false; 0081 } 0082 0083 int sizeRead = atopLog.read((char *)(&rh), sizeof(RawHeader)); 0084 if (sizeRead != sizeof(RawHeader)) { 0085 lastError = QLatin1String("Could not read header from file ") + filename; 0086 return false; 0087 } 0088 if (rh.magic != ATOPLOGMAGIC) { 0089 lastError = QLatin1String("File ") + filename + QLatin1String(" does not contain raw atop/atopsar output (wrong magic number)"); 0090 return false; 0091 } 0092 if (/*rh.sstatlen != sizeof(SStat) ||*/ 0093 rh.pstatlen != sizeof(PStat) || rh.rawheadlen != sizeof(RawHeader) || rh.rawreclen != sizeof(RawRecord)) { 0094 lastError = QLatin1String("File ") + filename + QLatin1String(" has incompatible format"); 0095 if (rh.aversion & 0x8000) { 0096 lastError = QStringLiteral("(created by version %1.%2. This program understands the format written by version 1.23") 0097 .arg((rh.aversion >> 8) & 0x7f) 0098 .arg(rh.aversion & 0xff); 0099 } 0100 return false; 0101 } 0102 0103 /* Read the first data header */ 0104 int offset = atopLog.pos(); 0105 historyTimes.clear(); 0106 historyOffsets.clear(); 0107 while (!atopLog.atEnd() && atopLog.read((char *)(&rr), sizeof(RawRecord)) == sizeof(RawRecord)) { 0108 historyOffsets << offset; 0109 historyTimes << QPair<QDateTime, uint>(QDateTime::fromSecsSinceEpoch(rr.curtime), rr.interval); 0110 offset += sizeof(RawRecord) + rr.scomplen + rr.pcomplen; 0111 atopLog.seek(offset); 0112 } 0113 if (currentlySelectedIndex >= historyOffsets.size()) 0114 currentlySelectedIndex = historyOffsets.size() - 1; 0115 0116 ready = true; 0117 return true; 0118 } 0119 0120 bool ProcessesATop::Private::loadDataForHistory(int index) 0121 { 0122 delete[] pstats; 0123 pstats = nullptr; 0124 atopLog.seek(historyOffsets.at(index)); 0125 /*Read the first data header */ 0126 if (atopLog.read((char *)(&rr), sizeof(RawRecord)) != sizeof(RawRecord)) { 0127 lastError = QStringLiteral("Could not read data header"); 0128 return false; 0129 } 0130 0131 if (historyTimes.at(index).first != QDateTime::fromSecsSinceEpoch(rr.curtime) || historyTimes.at(index).second != rr.interval) { 0132 lastError = QStringLiteral("INTERNAL ERROR WITH loadDataForHistory"); 0133 ready = false; 0134 return false; 0135 } 0136 0137 atopLog.seek(atopLog.pos() + rr.scomplen); 0138 QByteArray processRecord; 0139 processRecord.resize(rr.pcomplen); 0140 // qToBigEndian( rr.pcomplen, (uchar*)processRecord.data() ); 0141 unsigned int dataRead = 0; 0142 do { 0143 int ret = atopLog.read(processRecord.data() + dataRead, rr.pcomplen - dataRead); 0144 if (ret == -1) { 0145 lastError = QStringLiteral("Stream interrupted while being read"); 0146 return false; 0147 } 0148 dataRead += ret; 0149 } while (dataRead < rr.pcomplen); 0150 Q_ASSERT(dataRead == rr.pcomplen); 0151 // Q_ASSERT( (index + 1 ==historyTimes.count()) || atopLog.pos() == historyTimes.at(index+1)); 0152 0153 pstats = new PStat[rr.nlist]; 0154 unsigned long uncompressedLength = sizeof(struct PStat) * rr.nlist; 0155 int ret = uncompress((Byte *)pstats, &uncompressedLength, (Byte *)processRecord.constData(), rr.pcomplen); 0156 if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_NEED_DICT) { 0157 switch (ret) { 0158 case Z_MEM_ERROR: 0159 lastError = QStringLiteral("Could not uncompress record data due to lack of memory"); 0160 break; 0161 case Z_BUF_ERROR: 0162 lastError = QStringLiteral("Could not uncompress record data due to lack of room in buffer"); 0163 break; 0164 case Z_DATA_ERROR: 0165 lastError = QStringLiteral("Could not uncompress record data due to corrupted data"); 0166 break; 0167 default: 0168 lastError = QLatin1String("Could not uncompress record data due to unexpected error: ") + QString::number(ret); 0169 break; 0170 } 0171 delete[] pstats; 0172 pstats = nullptr; 0173 return false; 0174 } 0175 0176 pids.clear(); 0177 for (uint i = 0; i < rr.nlist; i++) { 0178 pids << pstats[i].gen.pid; 0179 } 0180 return true; 0181 } 0182 0183 ProcessesATop::ProcessesATop(bool loadDefaultFile) 0184 : d(new Private()) 0185 { 0186 if (loadDefaultFile) 0187 loadHistoryFile(QStringLiteral("/var/log/atop.log")); 0188 } 0189 0190 bool ProcessesATop::isHistoryAvailable() const 0191 { 0192 return d->ready; 0193 } 0194 0195 long ProcessesATop::getParentPid(long pid) 0196 { 0197 int index = d->pids.indexOf(pid); 0198 if (index < 0) 0199 return 0; 0200 return d->pstats[index].gen.ppid; 0201 } 0202 0203 bool ProcessesATop::updateProcessInfo(long pid, Process *process) 0204 { 0205 int index = d->pids.indexOf(pid); 0206 if (index < 0) 0207 return false; 0208 PStat &p = d->pstats[index]; 0209 process->setParentPid(p.gen.ppid); 0210 process->setUid(p.gen.ruid); 0211 process->setEuid(p.gen.ruid); 0212 process->setSuid(p.gen.ruid); 0213 process->setFsuid(p.gen.ruid); 0214 process->setGid(p.gen.rgid); 0215 process->setEgid(p.gen.rgid); 0216 process->setSgid(p.gen.rgid); 0217 process->setFsgid(p.gen.rgid); 0218 process->setTracerpid(-1); 0219 process->setNumThreads(p.gen.nthr); 0220 // process->setTty 0221 process->setUserTime(p.cpu.utime * 100 / d->rh.hertz); // check - divide by interval maybe? 0222 process->setSysTime(p.cpu.stime * 100 / d->rh.hertz); // check 0223 process->setUserUsage(process->userTime() / d->rr.interval); 0224 process->setSysUsage(process->sysTime() / d->rr.interval); 0225 process->setNiceLevel(p.cpu.nice); 0226 // process->setscheduler(p.cpu.policy); 0227 process->setVmSize(p.mem.vmem); 0228 process->setVmRSS(p.mem.rmem); 0229 process->vmSizeChange() = p.mem.vgrow; 0230 process->vmRSSChange() = p.mem.rgrow; 0231 process->setVmURSS(0); 0232 process->vmURSSChange() = 0; 0233 0234 /* Fill in name and command */ 0235 QString name = QString::fromUtf8(p.gen.name, qstrnlen(p.gen.name, PNAMLEN)); 0236 QString command = QString::fromUtf8(p.gen.cmdline, qstrnlen(p.gen.cmdline, CMDLEN)); 0237 // cmdline separates parameters with the NULL character 0238 if (!command.isEmpty()) { 0239 if (command.startsWith(name)) { 0240 int index = command.indexOf(QLatin1Char('\0')); 0241 name = command.left(index); 0242 } 0243 command.replace(QLatin1Char('\0'), QLatin1Char(' ')); 0244 } 0245 process->setName(name); 0246 process->setCommand(command); 0247 0248 /* Fill in state */ 0249 switch (p.gen.state) { 0250 case 'E': 0251 process->setStatus(Process::Ended); 0252 break; 0253 case 'R': 0254 process->setStatus(Process::Running); 0255 break; 0256 case 'S': 0257 process->setStatus(Process::Sleeping); 0258 break; 0259 case 'D': 0260 process->setStatus(Process::DiskSleep); 0261 break; 0262 case 'Z': 0263 process->setStatus(Process::Zombie); 0264 break; 0265 case 'T': 0266 process->setStatus(Process::Stopped); 0267 break; 0268 case 'W': 0269 process->setStatus(Process::Paging); 0270 break; 0271 default: 0272 process->setStatus(Process::OtherStatus); 0273 break; 0274 } 0275 0276 return true; 0277 } 0278 QDateTime ProcessesATop::viewingTime() const 0279 { 0280 if (!d->ready) 0281 return QDateTime(); 0282 return d->historyTimes.at(d->currentlySelectedIndex).first; 0283 } 0284 bool ProcessesATop::setViewingTime(const QDateTime &when) 0285 { 0286 QPair<QDateTime, uint> tmpWhen(when, 0); 0287 QList<QPair<QDateTime, uint>>::iterator i = std::upper_bound(d->historyTimes.begin(), d->historyTimes.end(), tmpWhen); 0288 0289 if (i->first == when || (i->first > when && i->first.addSecs(-i->second) <= when)) { 0290 // We found the time :) 0291 d->currentlySelectedIndex = i - d->historyTimes.begin(); 0292 bool success = d->loadDataForHistory(d->currentlySelectedIndex); 0293 if (!success) 0294 qCWarning(LIBKSYSGUARD_PROCESSCORE) << d->lastError; 0295 return success; 0296 } 0297 return false; 0298 } 0299 QList<QPair<QDateTime, uint>> ProcessesATop::historiesAvailable() const 0300 { 0301 return d->historyTimes; 0302 } 0303 0304 QSet<long> ProcessesATop::getAllPids() 0305 { 0306 const QSet<long> pids(d->pids.cbegin(), d->pids.cend()); 0307 return pids; 0308 } 0309 0310 Processes::Error ProcessesATop::sendSignal(long pid, int sig) 0311 { 0312 Q_UNUSED(pid); 0313 Q_UNUSED(sig); 0314 0315 return Processes::NotSupported; 0316 } 0317 0318 Processes::Error ProcessesATop::setNiceness(long pid, int priority) 0319 { 0320 Q_UNUSED(pid); 0321 Q_UNUSED(priority); 0322 0323 return Processes::NotSupported; 0324 } 0325 0326 Processes::Error ProcessesATop::setScheduler(long pid, int priorityClass, int priority) 0327 { 0328 Q_UNUSED(pid); 0329 Q_UNUSED(priorityClass); 0330 Q_UNUSED(priority); 0331 0332 return Processes::NotSupported; 0333 } 0334 0335 Processes::Error ProcessesATop::setIoNiceness(long pid, int priorityClass, int priority) 0336 { 0337 Q_UNUSED(pid); 0338 Q_UNUSED(priorityClass); 0339 Q_UNUSED(priority); 0340 0341 return Processes::NotSupported; 0342 } 0343 0344 bool ProcessesATop::supportsIoNiceness() 0345 { 0346 return false; 0347 } 0348 0349 long long ProcessesATop::totalPhysicalMemory() 0350 { 0351 return 0; 0352 } 0353 0354 ProcessesATop::~ProcessesATop() 0355 { 0356 delete d; 0357 } 0358 0359 }