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 }