1 /* This file is part of the KDE project
3 Copyright (C) 2007 John Tapsell <tapsell@kde.org>
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.
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.
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.
22 #include "processes_local_p.h"
31 #include <QTextStream>
35 //for kill and setNice
36 #include <sys/types.h>
38 #include <sys/resource.h>
42 #include <sys/ptrace.h>
43 #include <asm/unistd.h>
47 #define PROCESS_BUFFER_SIZE 1000
50 extern int sys_ioprio_set(int, int, int);
51 extern int sys_ioprio_get(int, int);
54 /* Check if this system has ionice */
55 #if !defined(SYS_ioprio_get) || !defined(SYS_ioprio_set)
56 /* All new kernels have SYS_ioprio_get and _set defined, but for the few that do not, here are the definitions */
58 #define __NR_ioprio_set 289
59 #define __NR_ioprio_get 290
60 #elif defined(__ppc__) || defined(__powerpc__)
61 #define __NR_ioprio_set 273
62 #define __NR_ioprio_get 274
63 #elif defined(__x86_64__)
64 #define __NR_ioprio_set 251
65 #define __NR_ioprio_get 252
66 #elif defined(__ia64__)
67 #define __NR_ioprio_set 1274
68 #define __NR_ioprio_get 1275
71 #warning "This architecture does not support IONICE. Disabling ionice feature."
75 /* Map these to SYS_ioprio_get */
76 #define SYS_ioprio_get __NR_ioprio_get
77 #define SYS_ioprio_set __NR_ioprio_set
79 #endif /* !SYS_ioprio_get */
81 /* Set up ionice functions */
83 #define IOPRIO_WHO_PROCESS 1
84 #define IOPRIO_CLASS_SHIFT 13
86 /* Expose the kernel calls to usespace via syscall
87 * See man ioprio_set and man ioprio_get for information on these functions */
88 static int ioprio_set(int which
, int who
, int ioprio
)
90 return syscall(SYS_ioprio_set
, which
, who
, ioprio
);
93 static int ioprio_get(int which
, int who
)
95 return syscall(SYS_ioprio_get
, which
, who
);
105 class ProcessesLocal::Private
108 Private() { mProcDir
= opendir( "/proc" );}
110 inline bool readProcStatus(long pid
, Process
*process
);
111 inline bool readProcStat(long pid
, Process
*process
);
112 inline bool readProcStatm(long pid
, Process
*process
);
113 inline bool readProcCmdline(long pid
, Process
*process
);
114 inline bool getNiceness(long pid
, Process
*process
);
116 char mBuffer
[PROCESS_BUFFER_SIZE
+1]; //used as a buffer to read data into
120 ProcessesLocal::Private::~Private()
125 ProcessesLocal::ProcessesLocal() : d(new Private())
130 bool ProcessesLocal::Private::readProcStatus(long pid
, Process
*process
)
132 mFile
.setFileName(QString("/proc/%1/status").arg(pid
));
133 if(!mFile
.open(QIODevice::ReadOnly
| QIODevice::Text
))
134 return false; /* process has terminated in the meantime */
138 process
->tracerpid
= 0;
141 int found
= 0; //count how many fields we found
142 while( (size
= mFile
.readLine( mBuffer
, sizeof(mBuffer
))) > 0) { //-1 indicates an error
143 switch( mBuffer
[0]) {
145 if((unsigned int)size
> sizeof("Name:") && qstrncmp(mBuffer
, "Name:", sizeof("Name:")-1) == 0) {
146 process
->name
= QString::fromLocal8Bit(mBuffer
+ sizeof("Name:")-1, size
-sizeof("Name:")+1).trimmed();
147 if(++found
== 4) goto finish
;
151 if((unsigned int)size
> sizeof("Uid:") && qstrncmp(mBuffer
, "Uid:", sizeof("Uid:")-1) == 0) {
152 sscanf(mBuffer
+ sizeof("Uid:") -1, "%Ld %Ld %Ld %Ld", &process
->uid
, &process
->euid
, &process
->suid
, &process
->fsuid
);
153 if(++found
== 4) goto finish
;
157 if((unsigned int)size
> sizeof("Gid:") && qstrncmp(mBuffer
, "Gid:", sizeof("Gid:")-1) == 0) {
158 sscanf(mBuffer
+ sizeof("Gid:")-1, "%Ld %Ld %Ld %Ld", &process
->gid
, &process
->egid
, &process
->sgid
, &process
->fsgid
);
159 if(++found
== 4) goto finish
;
163 if((unsigned int)size
> sizeof("TracerPid:") && qstrncmp(mBuffer
, "TracerPid:", sizeof("TracerPid:")-1) == 0) {
164 process
->tracerpid
= atol(mBuffer
+ sizeof("TracerPid:")-1);
165 if(++found
== 4) goto finish
;
178 long ProcessesLocal::getParentPid(long pid
) {
180 d
->mFile
.setFileName(QString("/proc/%1/stat").arg(pid
));
181 if(!d
->mFile
.open(QIODevice::ReadOnly
| QIODevice::Text
))
182 return 0; /* process has terminated in the meantime */
184 int size
; //amount of data read in
185 if( (size
= d
->mFile
.readLine( d
->mBuffer
, sizeof(d
->mBuffer
))) <= 0) { //-1 indicates nothing read
191 int current_word
= 0;
192 char *word
= d
->mBuffer
;
195 if(word
[0] == ' ' ) {
196 if(++current_word
== 3)
198 } else if(word
[0] == 0) {
199 return 0; //end of data - serious problem
206 bool ProcessesLocal::Private::readProcStat(long pid
, Process
*ps
)
208 QString filename
= QString("/proc/%1/stat").arg(pid
);
209 // As an optomization, if the last file read in was stat, then we already have this info in memory
210 if(mFile
.fileName() != filename
) {
211 mFile
.setFileName(filename
);
212 if(!mFile
.open(QIODevice::ReadOnly
| QIODevice::Text
))
213 return false; /* process has terminated in the meantime */
214 if( mFile
.readLine( mBuffer
, sizeof(mBuffer
)) <= 0) { //-1 indicates nothing read
221 int current_word
= 0; //count from 0
222 char *word
= mBuffer
;
226 while(current_word
< 23) {
227 if(word
[0] == ' ' ) {
229 switch(current_word
) {
231 status
=word
[1]; // Look at the first letter of the status.
232 // We analyze this after the while loop
236 int ttyNo
= atoi(word
+1);
237 int major
= ttyNo
>> 8;
238 int minor
= ttyNo
& 0xff;
241 ps
->setTty(QByteArray("pts/") + QByteArray::number(minor
));
244 ps
->setTty(QByteArray("tty"));
247 ps
->setTty(QByteArray("tty") + QByteArray::number(minor
));
249 ps
->setTty(QByteArray("ttyS") + QByteArray::number(minor
-64));
252 ps
->setTty(QByteArray());
257 ps
->setUserTime(atoll(word
+1));
260 ps
->setSysTime(atoll(word
+1));
263 ps
->setNiceLevel(atoi(word
+1)); /*Or should we use getPriority instead? */
266 vmSize
= atol(word
+1);
269 vmRSS
= atol(word
+1);
274 } else if(word
[0] == 0) {
275 return false; //end of data - serious problem
280 /* There was a "(ps->vmRss+3) * sysconf(_SC_PAGESIZE)" here in the original ksysguard code. I have no idea why! After comparing it to
281 * meminfo and other tools, this means we report the RSS by 12 bytes differently compared to them. So I'm removing the +3
282 * to be consistent. NEXT TIME COMMENT STRANGE THINGS LIKE THAT! :-) */
283 ps
->setVmRSS(vmRSS
* sysconf(_SC_PAGESIZE
) / 1024); /*convert to KiB*/
284 ps
->setVmSize(vmSize
/= 1024); /* convert to KiB */
288 ps
->setStatus(Process::Running
);
291 ps
->setStatus(Process::Sleeping
);
294 ps
->setStatus(Process::DiskSleep
);
297 ps
->setStatus(Process::Zombie
);
300 ps
->setStatus(Process::Stopped
);
303 ps
->setStatus(Process::Paging
);
306 ps
->setStatus(Process::OtherStatus
);
312 bool ProcessesLocal::Private::readProcStatm(long pid
, Process
*process
)
314 mFile
.setFileName(QString("/proc/%1/statm").arg(pid
));
315 if(!mFile
.open(QIODevice::ReadOnly
| QIODevice::Text
))
316 return false; /* process has terminated in the meantime */
318 if( mFile
.readLine( mBuffer
, sizeof(mBuffer
)) <= 0) { //-1 indicates nothing read
324 int current_word
= 0;
325 char *word
= mBuffer
;
328 if(word
[0] == ' ' ) {
329 if(++current_word
== 2) //number of pages that are shared
331 } else if(word
[0] == 0) {
332 return false; //end of data - serious problem
336 long shared
= atol(word
+1);
338 /* we use the rss - shared to find the amount of memory just this app uses */
339 process
->vmURSS
= process
->vmRSS
- (shared
* sysconf(_SC_PAGESIZE
) / 1024);
344 bool ProcessesLocal::Private::readProcCmdline(long pid
, Process
*process
)
346 if(!process
->command
.isNull()) return true; //only parse the cmdline once
347 mFile
.setFileName(QString("/proc/%1/cmdline").arg(pid
));
348 if(!mFile
.open(QIODevice::ReadOnly
| QIODevice::Text
))
349 return false; /* process has terminated in the meantime */
351 QTextStream
in(&mFile
);
352 process
->command
= in
.readAll();
354 //cmdline seperates parameters with the NULL character
355 process
->command
.replace('\0', ' ');
356 process
->command
= process
->command
.trimmed();
362 bool ProcessesLocal::Private::getNiceness(long pid
, Process
*process
) {
363 int sched
= sched_getscheduler(pid
);
366 process
->scheduler
= KSysGuard::Process::Other
;
369 process
->scheduler
= KSysGuard::Process::RoundRobin
;
372 process
->scheduler
= KSysGuard::Process::Fifo
;
376 process
->scheduler
= KSysGuard::Process::Batch
;
380 process
->scheduler
= KSysGuard::Process::Other
;
382 if(sched
== SCHED_FIFO
|| sched
== SCHED_RR
) {
383 struct sched_param param
;
384 if(sched_getparam(pid
, ¶m
) == 0)
385 process
->niceLevel
= param
.sched_priority
;
387 process
->niceLevel
= 0; //Error getting scheduler parameters.
391 int ioprio
= ioprio_get(IOPRIO_WHO_PROCESS
, pid
); /* Returns from 0 to 7 for the iopriority, and -1 if there's an error */
393 process
->ioniceLevel
= -1;
394 process
->ioPriorityClass
= KSysGuard::Process::None
;
395 return false; /* Error. Just give up. */
397 process
->ioniceLevel
= ioprio
& 0xff; /* Bottom few bits are the priority */
398 process
->ioPriorityClass
= (KSysGuard::Process::IoPriorityClass
)(ioprio
>> IOPRIO_CLASS_SHIFT
); /* Top few bits are the class */
400 return false; /* Do nothing, if we do not support this architecture */
405 bool ProcessesLocal::updateProcessInfo( long pid
, Process
*process
)
407 if(!d
->readProcStat(pid
, process
)) return false;
408 if(!d
->readProcStatus(pid
, process
)) return false;
409 if(!d
->readProcStatm(pid
, process
)) return false;
410 if(!d
->readProcCmdline(pid
, process
)) return false;
411 if(!d
->getNiceness(pid
, process
)) return false;
416 QSet
<long> ProcessesLocal::getAllPids( )
419 if(d
->mProcDir
==NULL
) return pids
; //There's not much we can do without /proc
420 struct dirent
* entry
;
421 rewinddir(d
->mProcDir
);
422 while ( ( entry
= readdir( d
->mProcDir
) ) )
423 if ( entry
->d_name
[ 0 ] >= '0' && entry
->d_name
[ 0 ] <= '9' )
424 pids
.insert(atol( entry
->d_name
));
428 bool ProcessesLocal::sendSignal(long pid
, int sig
) {
429 if ( kill( (pid_t
)pid
, sig
) ) {
436 bool ProcessesLocal::setNiceness(long pid
, int priority
) {
437 if(pid
<= 0) return false; // check the parameters
438 if ( setpriority( PRIO_PROCESS
, pid
, priority
) ) {
439 //set niceness failed
445 bool ProcessesLocal::setScheduler(long pid
, int priorityClass
, int priority
) {
446 if(priorityClass
== KSysGuard::Process::Other
|| priorityClass
== KSysGuard::Process::Batch
)
448 if(pid
<= 0) return false; // check the parameters
449 struct sched_param params
;
450 params
.sched_priority
= priority
;
451 switch(priorityClass
) {
452 case (KSysGuard::Process::Other
):
453 return (sched_setscheduler( pid
, SCHED_OTHER
, ¶ms
) == 0);
454 case (KSysGuard::Process::RoundRobin
):
455 return (sched_setscheduler( pid
, SCHED_RR
, ¶ms
) == 0);
456 case (KSysGuard::Process::Fifo
):
457 return (sched_setscheduler( pid
, SCHED_FIFO
, ¶ms
) == 0);
459 case (KSysGuard::Process::Batch
):
460 return (sched_setscheduler( pid
, SCHED_BATCH
, ¶ms
) == 0);
468 bool ProcessesLocal::setIoNiceness(long pid
, int priorityClass
, int priority
) {
470 if (ioprio_set(IOPRIO_WHO_PROCESS
, pid
, priority
| priorityClass
<< IOPRIO_CLASS_SHIFT
) == -1) {
471 //set io niceness failed
480 bool ProcessesLocal::supportsIoNiceness() {
488 long long ProcessesLocal::totalPhysicalMemory() {
489 //Try to get the memory via sysconf. Note the cast to long long to try to avoid a long overflow
490 //Should we use sysconf(_SC_PAGESIZE) or getpagesize() ?
491 long long memory
= ((long long)sysconf(_SC_PHYS_PAGES
)) * (sysconf(_SC_PAGESIZE
)/1024);
492 if(memory
> 0) return memory
;
494 //This is backup code incase the above failed. It should never fail on a linux system.
496 d
->mFile
.setFileName("/proc/meminfo");
497 if(!d
->mFile
.open(QIODevice::ReadOnly
| QIODevice::Text
))
501 while( (size
= d
->mFile
.readLine( d
->mBuffer
, sizeof(d
->mBuffer
))) > 0) { //-1 indicates an error
502 switch( d
->mBuffer
[0]) {
504 if((unsigned int)size
> sizeof("MemTotal:") && qstrncmp(d
->mBuffer
, "MemTotal:", sizeof("MemTotal:")-1) == 0) {
506 return atoll(d
->mBuffer
+ sizeof("MemTotal:")-1);
510 return 0; // Not found. Probably will never happen
512 ProcessesLocal::~ProcessesLocal()