1/* This file is part of the KDE project
2
3 Copyright (C) 2007 John Tapsell <tapsell@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19
20*/
21
22#include "processes_atop_p.h"
23#include "process.h"
24#include "atop_p.h"
25
26#include <klocale.h>
27#include <zlib.h>
28
29#include <QFile>
30#include <QHash>
31#include <QSet>
32#include <QByteArray>
33#include <QTextStream>
34#include <QtEndian>
35
36#include <QDebug>
37
38namespace KSysGuard
39{
40
41 class ProcessesATop::Private
42 {
43 public:
44 Private();
45 ~Private();
46 QFile atopLog;
47 bool ready;
48
49 bool loadDataForHistory(int index);
50 bool loadHistoryFile(const QString &filename);
51
52 RawHeader rh;
53 RawRecord rr;
54// SStat sstats;
55 PStat *pstats;
56 QList<long> pids; //This is a list of process pid's, in the exact same order as pstats
57 QString lastError;
58
59 QList<long> historyOffsets; //< The file offset where each history is stored
60 QList< QPair<QDateTime, uint> > historyTimes; //< The end time for each history record and its interval, probably in order from oldest to newest
61 int currentlySelectedIndex;
62 };
63
64ProcessesATop::Private::Private() :
65 ready(false),
66 pstats(NULL),
67 currentlySelectedIndex(-1)
68{
69}
70
71ProcessesATop::Private::~Private()
72{
73}
74
75QString ProcessesATop::historyFileName() const {
76 return d->atopLog.fileName();
77}
78
79bool ProcessesATop::loadHistoryFile(const QString &filename)
80{
81 return d->loadHistoryFile(filename);
82}
83
84bool ProcessesATop::Private::loadHistoryFile(const QString &filename) {
85 atopLog.setFileName(filename);
86 ready = false;
87 currentlySelectedIndex = -1;
88 if(!atopLog.exists()) {
89 lastError = "File " + filename + " does not exist";
90 return false;
91 }
92
93 if(!atopLog.open(QIODevice::ReadOnly)) {
94 lastError = "Could not open file" + filename;
95 return false;
96 }
97
98 int sizeRead = atopLog.read((char*)(&rh), sizeof(RawHeader));
99 if(sizeRead != sizeof(RawHeader)) {
100 lastError = "Could not read header from file" + filename;
101 return false;
102 }
103 if(rh.magic != ATOPLOGMAGIC) {
104 lastError = "File " + filename + " does not contain raw atop/atopsar output (wrong magic number)";
105 return false;
106 }
107 if (/*rh.sstatlen != sizeof(SStat) ||*/
108 rh.pstatlen != sizeof(PStat) ||
109 rh.rawheadlen != sizeof(RawHeader) ||
110 rh.rawreclen != sizeof(RawRecord) )
111 {
112 lastError = "File " + filename + " has incompatible format";
113 if (rh.aversion & 0x8000) {
114 lastError = QString("(created by version %1.%2. This program understands the format written by version 1.23")
115 .arg((rh.aversion >> 8) & 0x7f, rh.aversion & 0xff);
116 }
117 return false;
118 }
119
120
121 /* Read the first data header */
122 int offset = atopLog.pos();
123 historyTimes.clear();
124 historyOffsets.clear();
125 while( !atopLog.atEnd() && atopLog.read((char*)(&rr), sizeof(RawRecord)) == sizeof(RawRecord) ) {
126 historyOffsets << offset;
127 historyTimes << QPair<QDateTime,uint>(QDateTime::fromTime_t(rr.curtime), rr.interval);
128 offset += sizeof(RawRecord) + rr.scomplen + rr.pcomplen;
129 atopLog.seek(offset);
130 }
131 if(currentlySelectedIndex >= historyOffsets.size())
132 currentlySelectedIndex = historyOffsets.size() - 1;
133
134 ready = true;
135 return true;
136}
137
138bool ProcessesATop::Private::loadDataForHistory(int index)
139{
140 delete [] pstats;
141 pstats = NULL;
142 atopLog.seek(historyOffsets.at(index));
143 /*Read the first data header */
144 if( atopLog.read((char*)(&rr), sizeof(RawRecord)) != sizeof(RawRecord) ) {
145 lastError = "Could not read data header";
146 return false;
147 }
148
149 if( historyTimes.at(index).first != QDateTime::fromTime_t(rr.curtime) ||
150 historyTimes.at(index).second != rr.interval) {
151 lastError = "INTERNAL ERROR WITH loadDataForHistory";
152 ready = false;
153 return false;
154 }
155
156 atopLog.seek(atopLog.pos() + rr.scomplen);
157 QByteArray processRecord;
158 processRecord.resize(rr.pcomplen);
159// qToBigEndian( rr.pcomplen, (uchar*)processRecord.data() );
160 unsigned int dataRead = 0;
161 do {
162 int ret = atopLog.read( processRecord.data() + dataRead, rr.pcomplen - dataRead);
163 if(ret == -1) {
164 lastError = "Stream interrupted while being read";
165 return false;
166 }
167 dataRead += ret;
168 } while(dataRead < rr.pcomplen);
169 Q_ASSERT(dataRead == rr.pcomplen);
170 //Q_ASSERT( (index + 1 ==historyTimes.count()) || atopLog.pos() == historyTimes.at(index+1));
171
172 pstats = new PStat[ rr.nlist ];
173 unsigned long uncompressedLength= sizeof(struct PStat) * rr.nlist;
174 int ret = uncompress((Byte *)pstats, &uncompressedLength, (Byte *)processRecord.constData(), rr.pcomplen);
175 if(ret != Z_OK && ret != Z_STREAM_END && ret != Z_NEED_DICT) {
176 switch(ret) {
177 case Z_MEM_ERROR:
178 lastError = "Could not uncompress record data due to lack of memory";
179 break;
180 case Z_BUF_ERROR:
181 lastError = "Could not uncompress record data due to lack of room in buffer";
182 break;
183 case Z_DATA_ERROR:
184 lastError = "Could not uncompress record data due to corrupted data";
185 break;
186 default:
187 lastError = "Could not uncompress record data due to unexpected error: " + QString::number(ret);
188 break;
189 }
190 delete [] pstats;
191 pstats = NULL;
192 return false;
193 }
194
195 pids.clear();
196 for(uint i = 0; i < rr.nlist; i++) {
197 pids << pstats[i].gen.pid;
198 }
199 return true;
200}
201
202ProcessesATop::ProcessesATop(bool loadDefaultFile) : d(new Private())
203{
204 if(loadDefaultFile)
205 loadHistoryFile("/var/log/atop.log");
206}
207
208bool ProcessesATop::isHistoryAvailable() const
209{
210 return d->ready;
211}
212
213long ProcessesATop::getParentPid(long pid) {
214 int index = d->pids.indexOf(pid);
215 if(index < 0)
216 return 0;
217 return d->pstats[index].gen.ppid;
218}
219
220bool ProcessesATop::updateProcessInfo( long pid, Process *process)
221{
222 int index = d->pids.indexOf(pid);
223 if(index < 0)
224 return false;
225 PStat &p = d->pstats[index];
226 process->parent_pid = p.gen.ppid;
227 process->setUid(p.gen.ruid);
228 process->setEuid(p.gen.ruid);
229 process->setSuid(p.gen.ruid);
230 process->setFsuid(p.gen.ruid);
231 process->setGid(p.gen.rgid);
232 process->setEgid(p.gen.rgid);
233 process->setSgid(p.gen.rgid);
234 process->setFsgid(p.gen.rgid);
235 process->setTracerpid(-1);
236 process->setNumThreads(p.gen.nthr);
237// process->setTty
238 process->setUserTime(p.cpu.utime * 100/d->rh.hertz);//check - divide by interval maybe?
239 process->setSysTime(p.cpu.stime * 100/d->rh.hertz); //check
240 process->setUserUsage( process->userTime / d->rr.interval );
241 process->setSysUsage( process->sysTime / d->rr.interval );
242 process->setNiceLevel(p.cpu.nice);
243// process->setscheduler(p.cpu.policy);
244 process->setVmSize(p.mem.vmem);
245 process->setVmRSS(p.mem.rmem);
246 process->vmSizeChange = p.mem.vgrow;
247 process->vmRSSChange = p.mem.rgrow;
248 process->setVmURSS(0);
249 process->vmURSSChange = 0;
250
251 /* Fill in name and command */
252 QString name = QString::fromUtf8(p.gen.name, qstrnlen(p.gen.name,PNAMLEN));
253 QString command = QString::fromUtf8(p.gen.cmdline, qstrnlen(p.gen.cmdline,CMDLEN));
254 //cmdline separates parameters with the NULL character
255 if(!command.isEmpty()) {
256 if(command.startsWith(name)) {
257 int index = command.indexOf(QChar('\0'));
258 name = command.left(index);
259 }
260 command.replace('\0', ' ');
261 }
262 process->setName(name);
263 process->setCommand(command);
264
265
266 /* Fill in state */
267 switch(p.gen.state) {
268 case 'E':
269 process->setStatus(Process::Ended);
270 break;
271 case 'R':
272 process->setStatus(Process::Running);
273 break;
274 case 'S':
275 process->setStatus(Process::Sleeping);
276 break;
277 case 'D':
278 process->setStatus(Process::DiskSleep);
279 break;
280 case 'Z':
281 process->setStatus(Process::Zombie);
282 break;
283 case 'T':
284 process->setStatus(Process::Stopped);
285 break;
286 case 'W':
287 process->setStatus(Process::Paging);
288 break;
289 default:
290 process->setStatus(Process::OtherStatus);
291 break;
292 }
293
294
295 return true;
296}
297QDateTime ProcessesATop::viewingTime() const
298{
299 if(!d->ready)
300 return QDateTime();
301 return d->historyTimes.at(d->currentlySelectedIndex).first;
302}
303bool ProcessesATop::setViewingTime(const QDateTime &when)
304{
305 QPair<QDateTime, uint> tmpWhen(when, 0);
306 QList< QPair<QDateTime,uint> >::iterator i = qUpperBound(d->historyTimes.begin(), d->historyTimes.end(), tmpWhen);
307
308 if(i->first == when || (i->first > when && i->first.addSecs(-i->second) <= when)) {
309 //We found the time :)
310 d->currentlySelectedIndex = i - d->historyTimes.begin();
311 bool success = d->loadDataForHistory(d->currentlySelectedIndex);
312 if(!success)
313 qWarning() << d->lastError;
314 return success;
315 }
316 return false;
317}
318QList< QPair<QDateTime, uint> > ProcessesATop::historiesAvailable() const
319{
320 return d->historyTimes;
321}
322
323QSet<long> ProcessesATop::getAllPids( )
324{
325 return d->pids.toSet();
326}
327
328bool ProcessesATop::sendSignal(long pid, int sig) {
329 Q_UNUSED(pid);
330 Q_UNUSED(sig);
331
332 errorCode = Processes::NotSupported;
333 return false;
334}
335
336bool ProcessesATop::setNiceness(long pid, int priority) {
337 Q_UNUSED(pid);
338 Q_UNUSED(priority);
339
340 errorCode = Processes::NotSupported;
341 return false;
342}
343
344bool ProcessesATop::setScheduler(long pid, int priorityClass, int priority) {
345 Q_UNUSED(pid);
346 Q_UNUSED(priorityClass);
347 Q_UNUSED(priority);
348
349 errorCode = Processes::NotSupported;
350 return false;
351}
352
353
354bool ProcessesATop::setIoNiceness(long pid, int priorityClass, int priority) {
355 Q_UNUSED(pid);
356 Q_UNUSED(priorityClass);
357 Q_UNUSED(priority);
358
359 errorCode = Processes::NotSupported;
360 return false;
361}
362
363bool ProcessesATop::supportsIoNiceness() {
364 return false;
365}
366
367long long ProcessesATop::totalPhysicalMemory() {
368 return 0;
369}
370ProcessesATop::~ProcessesATop()
371{
372 delete d;
373}
374
375}
376