File indexing completed on 2024-04-28 05:31:38

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 
0117     ready = true;
0118     return true;
0119 }
0120 
0121 bool ProcessesATop::Private::loadDataForHistory(int index)
0122 {
0123     delete[] pstats;
0124     pstats = nullptr;
0125     atopLog.seek(historyOffsets.at(index));
0126     /*Read the first data header */
0127     if (atopLog.read((char *)(&rr), sizeof(RawRecord)) != sizeof(RawRecord)) {
0128         lastError = QStringLiteral("Could not read data header");
0129         return false;
0130     }
0131 
0132     if (historyTimes.at(index).first != QDateTime::fromSecsSinceEpoch(rr.curtime) || historyTimes.at(index).second != rr.interval) {
0133         lastError = QStringLiteral("INTERNAL ERROR WITH loadDataForHistory");
0134         ready = false;
0135         return false;
0136     }
0137 
0138     atopLog.seek(atopLog.pos() + rr.scomplen);
0139     QByteArray processRecord;
0140     processRecord.resize(rr.pcomplen);
0141     //    qToBigEndian( rr.pcomplen, (uchar*)processRecord.data() );
0142     unsigned int dataRead = 0;
0143     do {
0144         int ret = atopLog.read(processRecord.data() + dataRead, rr.pcomplen - dataRead);
0145         if (ret == -1) {
0146             lastError = QStringLiteral("Stream interrupted while being read");
0147             return false;
0148         }
0149         dataRead += ret;
0150     } while (dataRead < rr.pcomplen);
0151     Q_ASSERT(dataRead == rr.pcomplen);
0152     // Q_ASSERT( (index + 1 ==historyTimes.count()) || atopLog.pos() == historyTimes.at(index+1));
0153 
0154     pstats = new PStat[rr.nlist];
0155     unsigned long uncompressedLength = sizeof(struct PStat) * rr.nlist;
0156     int ret = uncompress((Byte *)pstats, &uncompressedLength, (Byte *)processRecord.constData(), rr.pcomplen);
0157     if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_NEED_DICT) {
0158         switch (ret) {
0159         case Z_MEM_ERROR:
0160             lastError = QStringLiteral("Could not uncompress record data due to lack of memory");
0161             break;
0162         case Z_BUF_ERROR:
0163             lastError = QStringLiteral("Could not uncompress record data due to lack of room in buffer");
0164             break;
0165         case Z_DATA_ERROR:
0166             lastError = QStringLiteral("Could not uncompress record data due to corrupted data");
0167             break;
0168         default:
0169             lastError = QLatin1String("Could not uncompress record data due to unexpected error: ") + QString::number(ret);
0170             break;
0171         }
0172         delete[] pstats;
0173         pstats = nullptr;
0174         return false;
0175     }
0176 
0177     pids.clear();
0178     for (uint i = 0; i < rr.nlist; i++) {
0179         pids << pstats[i].gen.pid;
0180     }
0181     return true;
0182 }
0183 
0184 ProcessesATop::ProcessesATop(bool loadDefaultFile)
0185     : d(new Private())
0186 {
0187     if (loadDefaultFile) {
0188         loadHistoryFile(QStringLiteral("/var/log/atop.log"));
0189     }
0190 }
0191 
0192 bool ProcessesATop::isHistoryAvailable() const
0193 {
0194     return d->ready;
0195 }
0196 
0197 long ProcessesATop::getParentPid(long pid)
0198 {
0199     int index = d->pids.indexOf(pid);
0200     if (index < 0) {
0201         return 0;
0202     }
0203     return d->pstats[index].gen.ppid;
0204 }
0205 
0206 bool ProcessesATop::updateProcessInfo(long pid, Process *process)
0207 {
0208     int index = d->pids.indexOf(pid);
0209     if (index < 0) {
0210         return false;
0211     }
0212     PStat &p = d->pstats[index];
0213     process->setParentPid(p.gen.ppid);
0214     process->setUid(p.gen.ruid);
0215     process->setEuid(p.gen.ruid);
0216     process->setSuid(p.gen.ruid);
0217     process->setFsuid(p.gen.ruid);
0218     process->setGid(p.gen.rgid);
0219     process->setEgid(p.gen.rgid);
0220     process->setSgid(p.gen.rgid);
0221     process->setFsgid(p.gen.rgid);
0222     process->setTracerpid(-1);
0223     process->setNumThreads(p.gen.nthr);
0224     //    process->setTty
0225     process->setUserTime(p.cpu.utime * 100 / d->rh.hertz); // check - divide by interval maybe?
0226     process->setSysTime(p.cpu.stime * 100 / d->rh.hertz); // check
0227     process->setUserUsage(process->userTime() / d->rr.interval);
0228     process->setSysUsage(process->sysTime() / d->rr.interval);
0229     process->setNiceLevel(p.cpu.nice);
0230     //    process->setscheduler(p.cpu.policy);
0231     process->setVmSize(p.mem.vmem);
0232     process->setVmRSS(p.mem.rmem);
0233     process->vmSizeChange() = p.mem.vgrow;
0234     process->vmRSSChange() = p.mem.rgrow;
0235     process->setVmURSS(0);
0236     process->vmURSSChange() = 0;
0237 
0238     /* Fill in name and command */
0239     QString name = QString::fromUtf8(p.gen.name, qstrnlen(p.gen.name, PNAMLEN));
0240     QString command = QString::fromUtf8(p.gen.cmdline, qstrnlen(p.gen.cmdline, CMDLEN));
0241     // cmdline separates parameters with the NULL character
0242     if (!command.isEmpty()) {
0243         if (command.startsWith(name)) {
0244             int index = command.indexOf(QLatin1Char('\0'));
0245             name = command.left(index);
0246         }
0247         command.replace(QLatin1Char('\0'), QLatin1Char(' '));
0248     }
0249     process->setName(name);
0250     process->setCommand(command);
0251 
0252     /* Fill in state */
0253     switch (p.gen.state) {
0254     case 'E':
0255         process->setStatus(Process::Ended);
0256         break;
0257     case 'R':
0258         process->setStatus(Process::Running);
0259         break;
0260     case 'S':
0261         process->setStatus(Process::Sleeping);
0262         break;
0263     case 'D':
0264         process->setStatus(Process::DiskSleep);
0265         break;
0266     case 'Z':
0267         process->setStatus(Process::Zombie);
0268         break;
0269     case 'T':
0270         process->setStatus(Process::Stopped);
0271         break;
0272     case 'W':
0273         process->setStatus(Process::Paging);
0274         break;
0275     default:
0276         process->setStatus(Process::OtherStatus);
0277         break;
0278     }
0279 
0280     return true;
0281 }
0282 
0283 QDateTime ProcessesATop::viewingTime() const
0284 {
0285     if (!d->ready)
0286         return QDateTime();
0287     return d->historyTimes.at(d->currentlySelectedIndex).first;
0288 }
0289 
0290 bool ProcessesATop::setViewingTime(const QDateTime &when)
0291 {
0292     QPair<QDateTime, uint> tmpWhen(when, 0);
0293     QList<QPair<QDateTime, uint>>::iterator i = std::upper_bound(d->historyTimes.begin(), d->historyTimes.end(), tmpWhen);
0294 
0295     if (i->first == when || (i->first > when && i->first.addSecs(-i->second) <= when)) {
0296         // We found the time :)
0297         d->currentlySelectedIndex = i - d->historyTimes.begin();
0298         bool success = d->loadDataForHistory(d->currentlySelectedIndex);
0299         if (!success) {
0300             qCWarning(LIBKSYSGUARD_PROCESSCORE) << d->lastError;
0301         }
0302         return success;
0303     }
0304     return false;
0305 }
0306 
0307 QList<QPair<QDateTime, uint>> ProcessesATop::historiesAvailable() const
0308 {
0309     return d->historyTimes;
0310 }
0311 
0312 QSet<long> ProcessesATop::getAllPids()
0313 {
0314     const QSet<long> pids(d->pids.cbegin(), d->pids.cend());
0315     return pids;
0316 }
0317 
0318 Processes::Error ProcessesATop::sendSignal(long pid, int sig)
0319 {
0320     Q_UNUSED(pid);
0321     Q_UNUSED(sig);
0322 
0323     return Processes::NotSupported;
0324 }
0325 
0326 Processes::Error ProcessesATop::setNiceness(long pid, int priority)
0327 {
0328     Q_UNUSED(pid);
0329     Q_UNUSED(priority);
0330 
0331     return Processes::NotSupported;
0332 }
0333 
0334 Processes::Error ProcessesATop::setScheduler(long pid, int priorityClass, int priority)
0335 {
0336     Q_UNUSED(pid);
0337     Q_UNUSED(priorityClass);
0338     Q_UNUSED(priority);
0339 
0340     return Processes::NotSupported;
0341 }
0342 
0343 Processes::Error ProcessesATop::setIoNiceness(long pid, int priorityClass, int priority)
0344 {
0345     Q_UNUSED(pid);
0346     Q_UNUSED(priorityClass);
0347     Q_UNUSED(priority);
0348 
0349     return Processes::NotSupported;
0350 }
0351 
0352 bool ProcessesATop::supportsIoNiceness()
0353 {
0354     return false;
0355 }
0356 
0357 long long ProcessesATop::totalPhysicalMemory()
0358 {
0359     return 0;
0360 }
0361 
0362 ProcessesATop::~ProcessesATop()
0363 {
0364     delete d;
0365 }
0366 
0367 }