~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to libs/ksysguard/processcore/processes.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.h"
 
23
#include "processes_base_p.h"
 
24
#include "processes_local_p.h"
 
25
#include "processes_remote_p.h"
 
26
#include "processes_atop_p.h"
 
27
#include "process.h"
 
28
 
 
29
#include <klocale.h>
 
30
#include <kglobal.h>
 
31
#include <kdebug.h>
 
32
 
 
33
#include <QHash>
 
34
#include <QSet>
 
35
#include <QMutableSetIterator>
 
36
#include <QByteArray>
 
37
 
 
38
//for sysconf
 
39
#include <unistd.h>
 
40
 
 
41
/* if porting to an OS without signal.h  please #define SIGTERM to something */
 
42
#include <signal.h>
 
43
 
 
44
 
 
45
namespace KSysGuard
 
46
{
 
47
  class Processes::Private
 
48
  {
 
49
    public:
 
50
      Private(Processes *q_ptr) {
 
51
        mAbstractProcesses = 0;
 
52
        mHistoricProcesses = 0;
 
53
        mIsLocalHost = true;
 
54
        mProcesses.insert(-1, &mFakeProcess);
 
55
        mElapsedTimeMilliSeconds = 0;
 
56
        mHavePreviousIoValues = false;
 
57
        mUpdateFlags = 0;
 
58
        mUsingHistoricalData = false;
 
59
        q = q_ptr;
 
60
    }
 
61
      ~Private();
 
62
      void markProcessesAsEnded(long pid);
 
63
 
 
64
      QSet<long> mToBeProcessed;
 
65
      QSet<long> mProcessedLastTime;
 
66
      QSet<long> mEndedProcesses; ///< Processes that have finished
 
67
 
 
68
      QHash<long, Process *> mProcesses; ///< This must include mFakeProcess at pid -1
 
69
      QList<Process *> mListProcesses;   ///< A list of the processes.  Does not include mFakeProcesses
 
70
      Process mFakeProcess; ///< A fake process with pid -1 just so that even init points to a parent
 
71
 
 
72
      AbstractProcesses *mAbstractProcesses; ///< The OS specific code to get the process information
 
73
      ProcessesATop *mHistoricProcesses; ///< A way to get historic information about processes
 
74
      bool mIsLocalHost; ///< Whether this is localhost or not
 
75
 
 
76
      QTime mLastUpdated; ///< This is the time we last updated.  Used to calculate cpu usage.
 
77
      long mElapsedTimeMilliSeconds; ///< The number of milliseconds  (1000ths of a second) that passed since the last update
 
78
 
 
79
      Processes::UpdateFlags mUpdateFlags;
 
80
      bool mHavePreviousIoValues; ///< This is whether we updated the IO value on the last update
 
81
      bool mUsingHistoricalData; ///< Whether to return historical data for updateProcess() etc
 
82
      Processes *q;
 
83
  };
 
84
 
 
85
Processes::Private::~Private() {
 
86
  Q_FOREACH(Process *process, mProcesses) {
 
87
    if(process != &mFakeProcess)
 
88
      delete process;
 
89
  }
 
90
  mProcesses.clear();
 
91
  mListProcesses.clear();
 
92
  delete mAbstractProcesses;
 
93
  mAbstractProcesses = NULL;
 
94
}
 
95
 
 
96
Processes::Processes(const QString &host, QObject *parent) : QObject(parent), d(new Private(this))
 
97
{
 
98
    KGlobal::locale()->insertCatalog("processcore");  //Make sure we include the translation stuff.  This needs to be run before any i18n call here
 
99
    if(host.isEmpty()) {
 
100
        d->mAbstractProcesses = new ProcessesLocal();
 
101
    } else {
 
102
        ProcessesRemote *remote = new ProcessesRemote(host);
 
103
        d->mAbstractProcesses = remote;
 
104
        connect(remote, SIGNAL(runCommand(QString,int)), this, SIGNAL(runCommand(QString,int)));
 
105
    }
 
106
    d->mIsLocalHost = host.isEmpty();
 
107
    connect( d->mAbstractProcesses, SIGNAL(processesUpdated()), SLOT(processesUpdated()));
 
108
}
 
109
Processes::~Processes()
 
110
{
 
111
    delete d;
 
112
}
 
113
 
 
114
Processes::Error Processes::lastError() const
 
115
{
 
116
    return d->mAbstractProcesses->errorCode;
 
117
}
 
118
Process *Processes::getProcess(long pid) const
 
119
{
 
120
    return d->mProcesses.value(pid);
 
121
}
 
122
 
 
123
const QList<Process *> &Processes::getAllProcesses() const
 
124
{
 
125
    return d->mListProcesses;
 
126
}
 
127
 
 
128
int Processes::processCount() const
 
129
{
 
130
    return d->mListProcesses.count();
 
131
}
 
132
 
 
133
bool Processes::updateProcess( Process *ps, long ppid)
 
134
{
 
135
    Process *parent = d->mProcesses.value(ppid);
 
136
    if(!parent)
 
137
        parent = &d->mFakeProcess;
 
138
    Q_ASSERT(parent);  //even init has a non-null parent - the mFakeProcess
 
139
 
 
140
    if(ps->parent != parent) {
 
141
        emit beginMoveProcess(ps, parent/*new parent*/);
 
142
        //Processes has been reparented
 
143
        Process *p = ps;
 
144
        do {
 
145
            p = p->parent;
 
146
            p->numChildren--;
 
147
        } while (p->pid!= -1);
 
148
        Q_ASSERT(ps != parent);
 
149
        ps->parent->children.removeAll(ps);
 
150
        ps->parent = parent;  //the parent has changed
 
151
        parent->children.append(ps);
 
152
        p = ps;
 
153
        do {
 
154
            p = p->parent;
 
155
            p->numChildren++;
 
156
        } while (p->pid!= -1);
 
157
        emit endMoveProcess();
 
158
        Q_ASSERT(ps != parent);
 
159
        ps->parent = parent;
 
160
    }
 
161
 
 
162
    ps->parent_pid = ppid;
 
163
 
 
164
    bool success = updateProcessInfo(ps);
 
165
    emit processChanged(ps, false);
 
166
 
 
167
    return success;
 
168
}
 
169
 
 
170
bool Processes::updateProcessInfo(Process *ps) {
 
171
    //Now we can actually get the process info
 
172
    qlonglong oldUserTime = ps->userTime;
 
173
    qlonglong oldSysTime = ps->sysTime;
 
174
 
 
175
    qlonglong oldIoCharactersRead = 0;
 
176
    qlonglong oldIoCharactersWritten = 0;
 
177
    qlonglong oldIoReadSyscalls = 0;
 
178
    qlonglong oldIoWriteSyscalls = 0;
 
179
    qlonglong oldIoCharactersActuallyRead = 0;
 
180
    qlonglong oldIoCharactersActuallyWritten = 0;
 
181
 
 
182
    if(d->mUpdateFlags.testFlag(Processes::IOStatistics)) {
 
183
        oldIoCharactersRead = ps->ioCharactersRead;
 
184
        oldIoCharactersWritten = ps->ioCharactersWritten;
 
185
        oldIoReadSyscalls = ps->ioReadSyscalls;
 
186
        oldIoWriteSyscalls = ps->ioWriteSyscalls;
 
187
        oldIoCharactersActuallyRead = ps->ioCharactersActuallyRead;
 
188
        oldIoCharactersActuallyWritten = ps->ioCharactersActuallyWritten;
 
189
    }
 
190
 
 
191
    ps->changes = Process::Nothing;
 
192
    bool success;
 
193
    if(d->mUsingHistoricalData)
 
194
        success = d->mHistoricProcesses->updateProcessInfo(ps->pid, ps);
 
195
    else
 
196
        success = d->mAbstractProcesses->updateProcessInfo(ps->pid, ps);
 
197
 
 
198
    //Now we have the process info.  Calculate the cpu usage and total cpu usage for itself and all its parents
 
199
    if(!d->mUsingHistoricalData && d->mElapsedTimeMilliSeconds != 0) {  //Update the user usage and sys usage
 
200
#ifndef Q_OS_NETBSD
 
201
        /* The elapsed time is the d->mElapsedTimeMilliSeconds
 
202
         * (which is of the order 2 seconds or so) plus a small
 
203
         * correction where we get the amount of time elapsed since
 
204
         * we start processing. This is because the processing itself
 
205
         * can take a non-trivial amount of time.  */
 
206
        int elapsedTime = ps->elapsedTimeMilliSeconds;
 
207
        ps->elapsedTimeMilliSeconds = d->mLastUpdated.elapsed();
 
208
        elapsedTime = ps->elapsedTimeMilliSeconds - elapsedTime + d->mElapsedTimeMilliSeconds;
 
209
        if(elapsedTime) {
 
210
            ps->setUserUsage((int)(((ps->userTime - oldUserTime)*1000.0) / elapsedTime));
 
211
            ps->setSysUsage((int)(((ps->sysTime - oldSysTime)*1000.0) / elapsedTime));
 
212
        }
 
213
#endif
 
214
        if(d->mUpdateFlags.testFlag(Processes::IOStatistics)) {
 
215
            if( d->mHavePreviousIoValues ) {
 
216
                ps->setIoCharactersReadRate((ps->ioCharactersRead - oldIoCharactersRead) * 1000.0 / elapsedTime);
 
217
                ps->setIoCharactersWrittenRate((ps->ioCharactersWritten - oldIoCharactersWritten) * 1000.0 / elapsedTime);
 
218
                ps->setIoReadSyscallsRate((ps->ioReadSyscalls - oldIoReadSyscalls) * 1000.0 / elapsedTime);
 
219
                ps->setIoWriteSyscallsRate((ps->ioWriteSyscalls - oldIoWriteSyscalls) * 1000.0 / elapsedTime);
 
220
                ps->setIoCharactersActuallyReadRate((ps->ioCharactersActuallyRead - oldIoCharactersActuallyRead) * 1000.0 / elapsedTime);
 
221
                ps->setIoCharactersActuallyWrittenRate((ps->ioCharactersActuallyWritten - oldIoCharactersActuallyWritten) * 1000.0 / elapsedTime);
 
222
            } else
 
223
                d->mHavePreviousIoValues = true;
 
224
        } else if(d->mHavePreviousIoValues) {
 
225
            d->mHavePreviousIoValues = false;
 
226
            ps->setIoCharactersReadRate(0);
 
227
            ps->setIoCharactersWrittenRate(0);
 
228
            ps->setIoReadSyscallsRate(0);
 
229
            ps->setIoWriteSyscallsRate(0);
 
230
            ps->setIoCharactersActuallyReadRate(0);
 
231
            ps->setIoCharactersActuallyWrittenRate(0);
 
232
        }
 
233
    }
 
234
    if(d->mUsingHistoricalData || d->mElapsedTimeMilliSeconds != 0) {
 
235
        ps->setTotalUserUsage(ps->userUsage);
 
236
        ps->setTotalSysUsage(ps->sysUsage);
 
237
        if(ps->userUsage != 0 || ps->sysUsage != 0) {
 
238
            Process *p = ps->parent;
 
239
            while(p->pid != -1) {
 
240
                p->totalUserUsage += ps->userUsage;
 
241
                p->totalSysUsage += ps->sysUsage;
 
242
                emit processChanged(p, true);
 
243
                p = p->parent;
 
244
            }
 
245
        }
 
246
    }
 
247
 
 
248
    return success;
 
249
}
 
250
 
 
251
bool Processes::addProcess(long pid, long ppid)
 
252
{
 
253
    Process *parent = d->mProcesses.value(ppid);
 
254
    if(!parent) {
 
255
        //Under race conditions, the parent could have already quit
 
256
        //In this case, attach to top leaf
 
257
        parent = &d->mFakeProcess;
 
258
        Q_ASSERT(parent);  //even init has a non-null parent - the mFakeProcess
 
259
    }
 
260
    //it's a new process - we need to set it up
 
261
    Process *ps = new Process(pid, ppid, parent);
 
262
 
 
263
    emit beginAddProcess(ps);
 
264
 
 
265
    d->mProcesses.insert(pid, ps);
 
266
 
 
267
    ps->index = d->mListProcesses.count();
 
268
    d->mListProcesses.append(ps);
 
269
 
 
270
    ps->parent->children.append(ps);
 
271
    Process *p = ps;
 
272
    do {
 
273
        Q_ASSERT(p);
 
274
        p = p->parent;
 
275
        p->numChildren++;
 
276
    } while (p->pid != -1);
 
277
    ps->parent_pid = ppid;
 
278
 
 
279
    //Now we can actually get the process info
 
280
    bool success = updateProcessInfo(ps);
 
281
    emit endAddProcess();
 
282
    return success;
 
283
 
 
284
}
 
285
bool Processes::updateOrAddProcess( long pid)
 
286
{
 
287
    long ppid;
 
288
    if(d->mUsingHistoricalData)
 
289
        ppid = d->mHistoricProcesses->getParentPid(pid);
 
290
    else
 
291
        ppid = d->mAbstractProcesses->getParentPid(pid);
 
292
 
 
293
    if (ppid == pid) //Shouldn't ever happen
 
294
        ppid = -1;
 
295
 
 
296
    if(d->mToBeProcessed.contains(ppid)) {
 
297
        //Make sure that we update the parent before we update this one.  Just makes things a bit easier.
 
298
        d->mToBeProcessed.remove(ppid);
 
299
        d->mProcessedLastTime.remove(ppid); //It may or may not be here - remove it if it is there
 
300
        updateOrAddProcess(ppid);
 
301
    }
 
302
 
 
303
    Process *ps = d->mProcesses.value(pid);
 
304
    if(!ps)
 
305
        return addProcess(pid, ppid);
 
306
    else
 
307
        return updateProcess(ps, ppid);
 
308
}
 
309
 
 
310
void Processes::updateAllProcesses(long updateDurationMS, Processes::UpdateFlags updateFlags)
 
311
{
 
312
    d->mUpdateFlags = updateFlags;
 
313
 
 
314
    if(d->mUsingHistoricalData || d->mLastUpdated.elapsed() >= updateDurationMS || !d->mLastUpdated.isValid())  {
 
315
        d->mElapsedTimeMilliSeconds = d->mLastUpdated.restart();
 
316
        if(d->mUsingHistoricalData)
 
317
            d->mHistoricProcesses->updateAllProcesses(d->mUpdateFlags);
 
318
        else
 
319
            d->mAbstractProcesses->updateAllProcesses(d->mUpdateFlags);  //For a local machine, this will directly call Processes::processesUpdated()
 
320
    }
 
321
}
 
322
 
 
323
void Processes::processesUpdated() {
 
324
    //First really delete any processes that ended last time
 
325
    long pid;
 
326
    {
 
327
        QSetIterator<long> i(d->mEndedProcesses);
 
328
        while( i.hasNext() ) {
 
329
            pid = i.next();
 
330
            deleteProcess(pid);
 
331
        }
 
332
    }
 
333
 
 
334
    if(d->mUsingHistoricalData)
 
335
        d->mToBeProcessed = d->mHistoricProcesses->getAllPids();
 
336
    else
 
337
        d->mToBeProcessed = d->mAbstractProcesses->getAllPids();
 
338
 
 
339
 
 
340
    QSet<long> beingProcessed(d->mToBeProcessed); //keep a copy so that we can replace mProcessedLastTime with this at the end of this function
 
341
 
 
342
    {
 
343
        QMutableSetIterator<long> i(d->mToBeProcessed);
 
344
        while( i.hasNext()) {
 
345
            pid = i.next();
 
346
            i.remove();
 
347
            d->mProcessedLastTime.remove(pid); //It may or may not be here - remove it if it is there
 
348
            updateOrAddProcess(pid);  //This adds the process or changes an existing one
 
349
            i.toFront(); //we can remove entries from this set elsewhere, so our iterator might be invalid.  Reset it back to the start of the set
 
350
        }
 
351
    }
 
352
    {
 
353
        QSetIterator<long> i(d->mProcessedLastTime);
 
354
        while( i.hasNext()) {
 
355
            //We saw these pids last time, but not this time.  That means we have to mark them for deletion now
 
356
            pid = i.next();
 
357
            d->markProcessesAsEnded(pid);
 
358
        }
 
359
        d->mEndedProcesses = d->mProcessedLastTime;
 
360
    }
 
361
 
 
362
    d->mProcessedLastTime = beingProcessed;  //update the set for next time this function is called
 
363
    return;
 
364
}
 
365
 
 
366
void Processes::Private::markProcessesAsEnded(long pid)
 
367
{
 
368
    Q_ASSERT(pid >= 0);
 
369
 
 
370
    Process *process = mProcesses.value(pid);
 
371
    if(!process)
 
372
        return;
 
373
    process->status = Process::Ended;
 
374
    emit q->processChanged(process, false);
 
375
}
 
376
void Processes::deleteProcess(long pid)
 
377
{
 
378
    Q_ASSERT(pid >= 0);
 
379
 
 
380
    Process *process = d->mProcesses.value(pid);
 
381
    if(!process)
 
382
        return;
 
383
    Q_FOREACH( Process *it, process->children) {
 
384
        d->mProcessedLastTime.remove(it->pid);
 
385
        deleteProcess(it->pid);
 
386
    }
 
387
 
 
388
    emit beginRemoveProcess(process);
 
389
 
 
390
    d->mProcesses.remove(pid);
 
391
    d->mListProcesses.removeAll(process);
 
392
    process->parent->children.removeAll(process);
 
393
    Process *p = process;
 
394
    do {
 
395
        Q_ASSERT(p);
 
396
        p = p->parent;
 
397
        p->numChildren--;
 
398
    } while (p->pid != -1);
 
399
#ifndef QT_NO_DEBUG
 
400
    int i = 0;
 
401
#endif
 
402
    Q_FOREACH( Process *it, d->mListProcesses ) {
 
403
        if(it->index > process->index)
 
404
            it->index--;
 
405
        Q_ASSERT(it->index == i++);
 
406
    }
 
407
 
 
408
    delete process;
 
409
    emit endRemoveProcess();
 
410
}
 
411
 
 
412
 
 
413
bool Processes::killProcess(long pid) {
 
414
    return sendSignal(pid, SIGTERM);
 
415
}
 
416
 
 
417
bool Processes::sendSignal(long pid, int sig) {
 
418
    d->mAbstractProcesses->errorCode = Unknown;
 
419
    if(d->mUsingHistoricalData) {
 
420
        d->mAbstractProcesses->errorCode = NotSupported;
 
421
        return false;
 
422
    }
 
423
    return d->mAbstractProcesses->sendSignal(pid, sig);
 
424
}
 
425
 
 
426
bool Processes::setNiceness(long pid, int priority) {
 
427
    d->mAbstractProcesses->errorCode = Unknown;
 
428
    if(d->mUsingHistoricalData) {
 
429
        d->mAbstractProcesses->errorCode = NotSupported;
 
430
        return false;
 
431
    }
 
432
    return d->mAbstractProcesses->setNiceness(pid, priority);
 
433
}
 
434
 
 
435
bool Processes::setScheduler(long pid, KSysGuard::Process::Scheduler priorityClass, int priority) {
 
436
    d->mAbstractProcesses->errorCode = Unknown;
 
437
    if(d->mUsingHistoricalData) {
 
438
        d->mAbstractProcesses->errorCode = NotSupported;
 
439
        return false;
 
440
    }
 
441
    return d->mAbstractProcesses->setScheduler(pid, priorityClass, priority);
 
442
}
 
443
 
 
444
bool Processes::setIoNiceness(long pid, KSysGuard::Process::IoPriorityClass priorityClass, int priority) {
 
445
    d->mAbstractProcesses->errorCode = Unknown;
 
446
    if(d->mUsingHistoricalData) {
 
447
        d->mAbstractProcesses->errorCode = NotSupported;
 
448
        return false;
 
449
    }
 
450
    return d->mAbstractProcesses->setIoNiceness(pid, priorityClass, priority);
 
451
}
 
452
 
 
453
bool Processes::supportsIoNiceness() {
 
454
    if(d->mUsingHistoricalData)
 
455
        return false;
 
456
    return d->mAbstractProcesses->supportsIoNiceness();
 
457
}
 
458
 
 
459
long long Processes::totalPhysicalMemory() {
 
460
    return d->mAbstractProcesses->totalPhysicalMemory();
 
461
}
 
462
 
 
463
long Processes::numberProcessorCores() {
 
464
    return d->mAbstractProcesses->numberProcessorCores();
 
465
}
 
466
 
 
467
void Processes::answerReceived( int id, const QList<QByteArray>& answer ) {
 
468
    KSysGuard::ProcessesRemote *processes = qobject_cast<KSysGuard::ProcessesRemote *>(d->mAbstractProcesses);
 
469
    if(processes)
 
470
        processes->answerReceived(id, answer);
 
471
}
 
472
 
 
473
QList< QPair<QDateTime,uint> > Processes::historiesAvailable() const
 
474
{
 
475
    if(!d->mIsLocalHost)
 
476
        return QList< QPair<QDateTime,uint> >();
 
477
    if(!d->mHistoricProcesses)
 
478
        d->mHistoricProcesses = new ProcessesATop();
 
479
 
 
480
    return d->mHistoricProcesses->historiesAvailable();
 
481
}
 
482
 
 
483
void Processes::useCurrentData()
 
484
{
 
485
    if(d->mUsingHistoricalData) {
 
486
        delete d->mHistoricProcesses;
 
487
        d->mHistoricProcesses = NULL;
 
488
        connect( d->mAbstractProcesses, SIGNAL(processesUpdated()), SLOT(processesUpdated()));
 
489
        d->mUsingHistoricalData = false;
 
490
    }
 
491
}
 
492
 
 
493
bool Processes::setViewingTime(const QDateTime &when)
 
494
{
 
495
    d->mAbstractProcesses->errorCode = Unknown;
 
496
    if(!d->mIsLocalHost) {
 
497
        d->mAbstractProcesses->errorCode = NotSupported;
 
498
        return false;
 
499
    }
 
500
    if(!d->mUsingHistoricalData) {
 
501
        if(!d->mHistoricProcesses)
 
502
            d->mHistoricProcesses = new ProcessesATop();
 
503
        disconnect( d->mAbstractProcesses, SIGNAL(processesUpdated()), this, SLOT(processesUpdated()));
 
504
        connect( d->mHistoricProcesses, SIGNAL(processesUpdated()), SLOT(processesUpdated()));
 
505
        d->mUsingHistoricalData = true;
 
506
    }
 
507
    return d->mHistoricProcesses->setViewingTime(when);
 
508
}
 
509
 
 
510
bool Processes::loadHistoryFile(const QString &filename)
 
511
{
 
512
    d->mAbstractProcesses->errorCode = Unknown;
 
513
    if(!d->mIsLocalHost) {
 
514
        d->mAbstractProcesses->errorCode = NotSupported;
 
515
        return false;
 
516
    }
 
517
    if(!d->mHistoricProcesses)
 
518
        d->mHistoricProcesses = new ProcessesATop(false);
 
519
 
 
520
    return d->mHistoricProcesses->loadHistoryFile(filename);
 
521
}
 
522
 
 
523
QString Processes::historyFileName() const
 
524
{
 
525
    if(!d->mIsLocalHost || !d->mHistoricProcesses)
 
526
        return QString::null;
 
527
    return d->mHistoricProcesses->historyFileName();
 
528
}
 
529
QDateTime Processes::viewingTime() const
 
530
{
 
531
    if(!d->mIsLocalHost || !d->mHistoricProcesses)
 
532
        return QDateTime();
 
533
    return d->mHistoricProcesses->viewingTime();
 
534
}
 
535
 
 
536
bool Processes::isHistoryAvailable() const
 
537
{
 
538
    if(!d->mIsLocalHost)
 
539
        return false;
 
540
    if(!d->mHistoricProcesses)
 
541
        d->mHistoricProcesses = new ProcessesATop();
 
542
 
 
543
    return d->mHistoricProcesses->isHistoryAvailable();
 
544
}
 
545
 
 
546
}
 
547
#include "processes.moc"
 
548